ზ3በંવવხްុլRxስសلçໂH℅૩/usr/local/bin/
Upload File :
Current File : //usr/local/bin/drush
#!/usr/bin/env php
<?php
/**
 * Generated by Box.
 *
 * @link https://github.com/herrera-io/php-box/
 */
if (class_exists('Phar')) {
Phar::mapPhar('drush.phar');
require 'phar://' . __FILE__ . '/drush';
}
__HALT_COMPILER(); ?>
�t1vendor/symfony/finder/Adapter/AbstractAdapter.php�t$zZ���Ѷ5vendor/symfony/finder/Adapter/AbstractFindAdapter.php]*t$zZ]*��%�2vendor/symfony/finder/Adapter/AdapterInterface.php/
t$zZ/
.k��0vendor/symfony/finder/Adapter/BsdFindAdapter.php�t$zZ�O��H�0vendor/symfony/finder/Adapter/GnuFindAdapter.php�t$zZ��B�m�,vendor/symfony/finder/Adapter/PhpAdapter.phpqt$zZq���/vendor/symfony/finder/Comparator/Comparator.phpt$zZ���3vendor/symfony/finder/Comparator/DateComparator.php�t$zZ���Gö5vendor/symfony/finder/Comparator/NumberComparator.php
t$zZ
����9vendor/symfony/finder/Exception/AccessDeniedException.php�t$zZ��cW޶;vendor/symfony/finder/Exception/AdapterFailureException.phpKt$zZK|^�6vendor/symfony/finder/Exception/ExceptionInterface.php�t$zZ��7�Avendor/symfony/finder/Exception/OperationNotPermitedException.phprt$zZrc
���@vendor/symfony/finder/Exception/ShellCommandFailureException.phpt$zZ~˶/vendor/symfony/finder/Expression/Expression.php�
t$zZ�
c��)vendor/symfony/finder/Expression/Glob.phpDt$zZD^�b�*vendor/symfony/finder/Expression/Regex.php~t$zZ~��9�3vendor/symfony/finder/Expression/ValueInterface.php�t$zZ�1GE�� vendor/symfony/finder/Finder.php�_t$zZ�_�M�vendor/symfony/finder/Glob.php�t$zZ��R�`�7vendor/symfony/finder/Iterator/CustomFilterIterator.php�t$zZ�cˀS�:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php�t$zZ���@��;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php�t$zZ����Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php�	t$zZ�	,��4vendor/symfony/finder/Iterator/FilePathsIterator.phpPt$zZP����9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php>t$zZ>p��Y�<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php�t$zZ�r�~�9vendor/symfony/finder/Iterator/FilenameFilterIterator.php�t$zZ��p��1vendor/symfony/finder/Iterator/FilterIterator.php�t$zZ�8=/��=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php�t$zZ�5���5vendor/symfony/finder/Iterator/PathFilterIterator.php�t$zZ���=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php�t$zZ�Z@
�:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php�t$zZ��:~ж3vendor/symfony/finder/Iterator/SortableIterator.php
t$zZ
!ư�'vendor/symfony/finder/Shell/Command.phpht$zZhF���%vendor/symfony/finder/Shell/Shell.phpKt$zZK���5�%vendor/symfony/finder/SplFileInfo.phpZt$zZZiq�p�Avendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php�t$zZ���]�Bvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php;-t$zZ;-���Kvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php#t$zZ#�Ӗc�9vendor/symfony/event-dispatcher/Debug/WrappedListener.php�t$zZ��M}�Mvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php=t$zZ=ȊbX�)vendor/symfony/event-dispatcher/Event.php�
t$zZ�
<Bz�3vendor/symfony/event-dispatcher/EventDispatcher.phpt$zZ07ݶ<vendor/symfony/event-dispatcher/EventDispatcherInterface.php�t$zZ�ȋ�ζ<vendor/symfony/event-dispatcher/EventSubscriberInterface.phpt$zZ����0vendor/symfony/event-dispatcher/GenericEvent.phpt$zZ�ټ>�<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php~	t$zZ~	�(��-vendor/symfony/polyfill-mbstring/Mbstring.phpjVt$zZjV�v���@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�It$zZ�I�҈�@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php9Jt$zZ9J|Q��.vendor/symfony/polyfill-mbstring/bootstrap.php't$zZ'p#Di�(vendor/symfony/debug/BufferingLogger.php�t$zZ�M0	=�vendor/symfony/debug/Debug.php�t$zZ��-�)vendor/symfony/debug/DebugClassLoader.phpW4t$zZW4���%vendor/symfony/debug/ErrorHandler.php�{t$zZ�{,Ю�9vendor/symfony/debug/Exception/ClassNotFoundException.php<t$zZ<'D���8vendor/symfony/debug/Exception/ContextErrorException.phpkt$zZkG
�1vendor/symfony/debug/Exception/DummyException.phpOt$zZO�"n
�6vendor/symfony/debug/Exception/FatalErrorException.php
t$zZ
[ }��6vendor/symfony/debug/Exception/FatalThrowableError.php4t$zZ4��3vendor/symfony/debug/Exception/FlattenException.phpW t$zZW �B�7vendor/symfony/debug/Exception/OutOfMemoryException.php�t$zZ���h�=vendor/symfony/debug/Exception/UndefinedFunctionException.php+t$zZ+�O��;vendor/symfony/debug/Exception/UndefinedMethodException.php&t$zZ&ïy߶)vendor/symfony/debug/ExceptionHandler.php#Kt$zZ#K筆5�Ivendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.phpmt$zZm�S�Evendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php�t$zZ��iA��Mvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php�t$zZ��g���Kvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpZt$zZZ�2�&vendor/symfony/console/Application.php`�t$zZ`���d�*vendor/symfony/console/Command/Command.phpcPt$zZcP�~�.vendor/symfony/console/Command/HelpCommand.phpt$zZ'���.vendor/symfony/console/Command/ListCommand.phpot$zZo�qX�(vendor/symfony/console/ConsoleEvents.php7t$zZ7E����<vendor/symfony/console/Descriptor/ApplicationDescription.php�t$zZ�!�g��0vendor/symfony/console/Descriptor/Descriptor.phpt$zZ�ɠ�9vendor/symfony/console/Descriptor/DescriptorInterface.php�t$zZ�JZ0<�4vendor/symfony/console/Descriptor/JsonDescriptor.php�t$zZ�w�t��8vendor/symfony/console/Descriptor/MarkdownDescriptor.php�t$zZ��_�5�4vendor/symfony/console/Descriptor/TextDescriptor.phpk*t$zZk*�� n�3vendor/symfony/console/Descriptor/XmlDescriptor.phpS%t$zZS%�J�a�4vendor/symfony/console/Event/ConsoleCommandEvent.php=t$zZ=%mf�-vendor/symfony/console/Event/ConsoleEvent.php�t$zZ���
��6vendor/symfony/console/Event/ConsoleExceptionEvent.php=t$zZ=��Ŷ6vendor/symfony/console/Event/ConsoleTerminateEvent.phpt$zZ{e��=vendor/symfony/console/Exception/CommandNotFoundException.php�t$zZ����7vendor/symfony/console/Exception/ExceptionInterface.php�t$zZ����U�=vendor/symfony/console/Exception/InvalidArgumentException.php�t$zZ��u i�;vendor/symfony/console/Exception/InvalidOptionException.php�t$zZ��;�3vendor/symfony/console/Exception/LogicException.php�t$zZ�SML��5vendor/symfony/console/Exception/RuntimeException.php�t$zZ��*b�4vendor/symfony/console/Formatter/OutputFormatter.php�t$zZ�����=vendor/symfony/console/Formatter/OutputFormatterInterface.php�t$zZ�#7�u�9vendor/symfony/console/Formatter/OutputFormatterStyle.php�t$zZ�D1��Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php[t$zZ[f@���>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�
t$zZ�
1+C�6vendor/symfony/console/Helper/DebugFormatterHelper.phpVt$zZV�ݔ��2vendor/symfony/console/Helper/DescriptorHelper.php1
t$zZ1
i��.vendor/symfony/console/Helper/DialogHelper.php�Dt$zZ�D~�o�1vendor/symfony/console/Helper/FormatterHelper.php	t$zZ	"�*��(vendor/symfony/console/Helper/Helper.phpU
t$zZU
�s��1vendor/symfony/console/Helper/HelperInterface.php�t$zZ����+vendor/symfony/console/Helper/HelperSet.php�
t$zZ�
4����2vendor/symfony/console/Helper/InputAwareHelper.php�t$zZ���˶/vendor/symfony/console/Helper/ProcessHelper.phpt$zZt���-vendor/symfony/console/Helper/ProgressBar.phpFt$zZF����0vendor/symfony/console/Helper/ProgressHelper.phpO1t$zZO1Ҟ�6�3vendor/symfony/console/Helper/ProgressIndicator.php�!t$zZ�!���"�0vendor/symfony/console/Helper/QuestionHelper.php�5t$zZ�5{,�ζ7vendor/symfony/console/Helper/SymfonyQuestionHelper.php�t$zZ���歶'vendor/symfony/console/Helper/Table.php�It$zZ�Ivbڶ+vendor/symfony/console/Helper/TableCell.php�t$zZ�I��-vendor/symfony/console/Helper/TableHelper.php3t$zZ3��I"�0vendor/symfony/console/Helper/TableSeparator.phpEt$zZEp|ն,vendor/symfony/console/Helper/TableStyle.php�t$zZ�w�U\�*vendor/symfony/console/Input/ArgvInput.phpt't$zZt'�퐶+vendor/symfony/console/Input/ArrayInput.phpt$zZ��s�&vendor/symfony/console/Input/Input.php�t$zZ�݂Y��.vendor/symfony/console/Input/InputArgument.php�t$zZ�	��4vendor/symfony/console/Input/InputAwareInterface.php^t$zZ^9K�h�0vendor/symfony/console/Input/InputDefinition.php&3t$zZ&3�+�/vendor/symfony/console/Input/InputInterface.phpNt$zZN�(Γ�,vendor/symfony/console/Input/InputOption.php\t$zZ\�k[�,vendor/symfony/console/Input/StringInput.php�t$zZ��(�2�/vendor/symfony/console/Logger/ConsoleLogger.php�t$zZ��C�v�0vendor/symfony/console/Output/BufferedOutput.phpkt$zZk�3��/vendor/symfony/console/Output/ConsoleOutput.php�t$zZ�B!�ɶ8vendor/symfony/console/Output/ConsoleOutputInterface.phpKt$zZK��0��,vendor/symfony/console/Output/NullOutput.phpnt$zZnt�D��(vendor/symfony/console/Output/Output.php{t$zZ{�m�n�1vendor/symfony/console/Output/OutputInterface.phpu
t$zZu
�ʶ.vendor/symfony/console/Output/StreamOutput.php�t$zZ�����2vendor/symfony/console/Question/ChoiceQuestion.php�t$zZ���ֶ8vendor/symfony/console/Question/ConfirmationQuestion.phpt$zZ�����,vendor/symfony/console/Question/Question.phpSt$zZSR�'"� vendor/symfony/console/Shell.php�t$zZ�����,vendor/symfony/console/Style/OutputStyle.php0	t$zZ0	B޿�/vendor/symfony/console/Style/StyleInterface.php�t$zZ�3��Z�-vendor/symfony/console/Style/SymfonyStyle.php�/t$zZ�/�]\�/vendor/symfony/var-dumper/Caster/AmqpCaster.php%t$zZ%_��+vendor/symfony/var-dumper/Caster/Caster.php)t$zZ)���!�.vendor/symfony/var-dumper/Caster/ConstStub.phpEt$zZE�R���1vendor/symfony/var-dumper/Caster/CutArrayStub.php�t$zZ�*�Ƭ�,vendor/symfony/var-dumper/Caster/CutStub.php�t$zZ�|R��.vendor/symfony/var-dumper/Caster/DOMCaster.php�&t$zZ�&W$>��3vendor/symfony/var-dumper/Caster/DoctrineCaster.phpxt$zZx��N�-vendor/symfony/var-dumper/Caster/EnumStub.php#t$zZ#��g(�4vendor/symfony/var-dumper/Caster/ExceptionCaster.phpz*t$zZz*�dD��.vendor/symfony/var-dumper/Caster/FrameStub.php�t$zZ�S�,�0vendor/symfony/var-dumper/Caster/MongoCaster.php;t$zZ;�ouԶ.vendor/symfony/var-dumper/Caster/PdoCaster.php�t$zZ�mj@�0vendor/symfony/var-dumper/Caster/PgSqlCaster.phpRt$zZR�?0��5vendor/symfony/var-dumper/Caster/ReflectionCaster.php3,t$zZ3,��ն3vendor/symfony/var-dumper/Caster/ResourceCaster.php�t$zZ�y���.vendor/symfony/var-dumper/Caster/SplCaster.php�t$zZ�f�տ�/vendor/symfony/var-dumper/Caster/StubCaster.php�t$zZ�{�X�.vendor/symfony/var-dumper/Caster/TraceStub.php�t$zZ���~�6vendor/symfony/var-dumper/Caster/XmlResourceCaster.php�	t$zZ�	#w��3vendor/symfony/var-dumper/Cloner/AbstractCloner.php�8t$zZ�8�]̶4vendor/symfony/var-dumper/Cloner/ClonerInterface.php<t$zZ<�0�F�+vendor/symfony/var-dumper/Cloner/Cursor.php�t$zZ��ACx�)vendor/symfony/var-dumper/Cloner/Data.php� t$zZ� ��w��4vendor/symfony/var-dumper/Cloner/DumperInterface.php�t$zZ�^��Y�)vendor/symfony/var-dumper/Cloner/Stub.phput$zZu�
��.vendor/symfony/var-dumper/Cloner/VarCloner.php�8t$zZ�8���A�3vendor/symfony/var-dumper/Dumper/AbstractDumper.phpJt$zZJ`I+��.vendor/symfony/var-dumper/Dumper/CliDumper.php>t$zZ>��&�8vendor/symfony/var-dumper/Dumper/DataDumperInterface.phpSt$zZS�v�%�/vendor/symfony/var-dumper/Dumper/HtmlDumper.php6t$zZ6����?vendor/symfony/var-dumper/Exception/ThrowingCasterException.php�t$zZ��#���6vendor/symfony/var-dumper/Resources/functions/dump.php�t$zZ�K]�'vendor/symfony/var-dumper/VarDumper.phpGt$zZG���vendor/symfony/yaml/Dumper.php�	t$zZ�	���vendor/symfony/yaml/Escaper.phpgt$zZgbڔ��/vendor/symfony/yaml/Exception/DumpException.php�t$zZ���4vendor/symfony/yaml/Exception/ExceptionInterface.php�t$zZ��^KA�0vendor/symfony/yaml/Exception/ParseException.php�
t$zZ�
�c��2vendor/symfony/yaml/Exception/RuntimeException.php�t$zZ��_q��vendor/symfony/yaml/Inline.php�St$zZ�Sg����vendor/symfony/yaml/Parser.php(�t$zZ(���K��!vendor/symfony/yaml/Unescaper.phpDt$zZD�x�vendor/symfony/yaml/Yaml.php�t$zZ�;֭R�'vendor/composer/autoload_namespaces.php�t$zZ��c��!vendor/composer/autoload_psr4.php?t$zZ?�d���%vendor/composer/autoload_classmap.php�Jt$zZ�J��"vendor/composer/autoload_files.php�t$zZ�>	e�#vendor/composer/autoload_static.phpwt$zZw��6�!vendor/composer/autoload_real.phpn	t$zZn	��Ķvendor/composer/ClassLoader.phpl4t$zZl4��[��)vendor/psr/log/Psr/Log/AbstractLogger.phpt$zZ�Gl�3vendor/psr/log/Psr/Log/InvalidArgumentException.php`t$zZ` �X1�#vendor/psr/log/Psr/Log/LogLevel.phpPt$zZP���/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)t$zZ)�j��+vendor/psr/log/Psr/Log/LoggerAwareTrait.php�t$zZ�z%��*vendor/psr/log/Psr/Log/LoggerInterface.php�t$zZ�?}�&vendor/psr/log/Psr/Log/LoggerTrait.php
t$zZ
�ý��%vendor/psr/log/Psr/Log/NullLogger.php�t$zZ���Zf�Tvendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php�t$zZ���WP�Rvendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.php�t$zZ����Ovendor/consolidation/output-formatters/src/Exception/InvalidFormatException.php9t$zZ9���Nvendor/consolidation/output-formatters/src/Exception/UnknownFieldException.phpPt$zZP��Y�Ovendor/consolidation/output-formatters/src/Exception/UnknownFormatException.phpVt$zZV�fӖ�?vendor/consolidation/output-formatters/src/FormatterManager.php�=t$zZ�=����Fvendor/consolidation/output-formatters/src/Formatters/CsvFormatter.php{t$zZ{�5`��Lvendor/consolidation/output-formatters/src/Formatters/FormatterInterface.php�t$zZ�J�?^�Gvendor/consolidation/output-formatters/src/Formatters/JsonFormatter.phpt$zZ�xƻ�Gvendor/consolidation/output-formatters/src/Formatters/ListFormatter.php&t$zZ&Y�iɶIvendor/consolidation/output-formatters/src/Formatters/PrintRFormatter.php�t$zZ�=k�>�Mvendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php>t$zZ>�Tz۶Nvendor/consolidation/output-formatters/src/Formatters/RenderTableDataTrait.php�t$zZ�.՗�Kvendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.php�	t$zZ�	�vނ�Lvendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.php�t$zZ���Ivendor/consolidation/output-formatters/src/Formatters/StringFormatter.php�
t$zZ�
uL�|�Hvendor/consolidation/output-formatters/src/Formatters/TableFormatter.php�t$zZ�:j�1�Fvendor/consolidation/output-formatters/src/Formatters/TsvFormatter.php�t$zZ��Ė��Lvendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.php�t$zZ���֋�Fvendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php,	t$zZ,	d�r�Gvendor/consolidation/output-formatters/src/Formatters/YamlFormatter.phpCt$zZCG���Gvendor/consolidation/output-formatters/src/Options/FormatterOptions.php�)t$zZ�)�k�k�Ovendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.phpt$zZOk�t�Tvendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php$t$zZ$��r�Mvendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php�t$zZ��/��Nvendor/consolidation/output-formatters/src/StructuredData/CallableRenderer.phpJt$zZJz�(�Jvendor/consolidation/output-formatters/src/StructuredData/HelpDocument.phpEt$zZE(T/�Nvendor/consolidation/output-formatters/src/StructuredData/ListDataFromKeys.php-t$zZ-�ƴ�Ovendor/consolidation/output-formatters/src/StructuredData/ListDataInterface.php�t$zZ��'1"�Svendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.phpt$zZ�a��Jvendor/consolidation/output-formatters/src/StructuredData/PropertyList.php�t$zZ�2,ֶ[vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.phpt$zZ;��۶Wvendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionTrait.phpkt$zZk]�öQvendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php�t$zZ��p<}�Rvendor/consolidation/output-formatters/src/StructuredData/RestructureInterface.php�t$zZ���c{�Jvendor/consolidation/output-formatters/src/StructuredData/RowsOfFields.php�t$zZ��D)_�Pvendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php"t$zZ"nF�8�Rvendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php�t$zZ��ֲ��Kvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchema.phpCt$zZC�7:�Tvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.phpt$zZM�}�Svendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php�t$zZ��ma	�[vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.phpt$zZ����^vendor/consolidation/output-formatters/src/Transformations/PropertyListTableTransformation.php�t$zZ�c��Mvendor/consolidation/output-formatters/src/Transformations/PropertyParser.phpt$zZ5���Lvendor/consolidation/output-formatters/src/Transformations/ReorderFields.php�t$zZ����'�Wvendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php�t$zZ�2y��Rvendor/consolidation/output-formatters/src/Transformations/TableTransformation.php�t$zZ�H����Jvendor/consolidation/output-formatters/src/Transformations/WordWrapper.phpt$zZ</t�Svendor/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.phpt$zZ;U>�Pvendor/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php�t$zZ��$��Ovendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.phpjt$zZj9P��Kvendor/consolidation/output-formatters/src/Validate/ValidDataTypesTrait.php�t$zZ���"�Kvendor/consolidation/output-formatters/src/Validate/ValidationInterface.php�t$zZ���U��/vendor/consolidation/output-formatters/test.php�t$zZ���x��?vendor/consolidation/annotated-command/src/AnnotatedCommand.php�=t$zZ�=�0�!�Fvendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php�@t$zZ�@ҭY�=vendor/consolidation/annotated-command/src/AnnotationData.phpVt$zZVZ�޿�Avendor/consolidation/annotated-command/src/Cache/CacheWrapper.phpt$zZ�'��>vendor/consolidation/annotated-command/src/Cache/NullCache.php�t$zZ�pq�-�Ivendor/consolidation/annotated-command/src/Cache/SimpleCacheInterface.phpct$zZc �<�Fvendor/consolidation/annotated-command/src/CommandCreationListener.php�t$zZ�[�Ovendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php�t$zZ�"\4��:vendor/consolidation/annotated-command/src/CommandData.phpt$zZ��\�;vendor/consolidation/annotated-command/src/CommandError.phpPt$zZP��9��Cvendor/consolidation/annotated-command/src/CommandFileDiscovery.php$1t$zZ$1jT!��Jvendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php�t$zZ�э���?vendor/consolidation/annotated-command/src/CommandProcessor.php�0t$zZ�0����Ovendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.phpt$zZ%�Ct�Kvendor/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php\t$zZ\�6s'�@vendor/consolidation/annotated-command/src/ExitCodeInterface.php t$zZ ���+�?vendor/consolidation/annotated-command/src/Help/HelpCommand.php_t$zZ_���@vendor/consolidation/annotated-command/src/Help/HelpDocument.php�t$zZ�����Evendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php�t$zZ��U֑�Ivendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php#t$zZ#~��u�[vendor/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php�t$zZ�}���Xvendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php�t$zZ��f��Ovendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.phpat$zZa7<0�Yvendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php�t$zZ�%̀��Wvendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.phpt$zZ����Vvendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php�t$zZ��.��\vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php�t$zZ�A	��]vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php�t$zZ��!3�_vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.phpPt$zZP��G�Wvendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php�t$zZ�?�ƶKvendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.phpht$zZh�ȉ�@vendor/consolidation/annotated-command/src/Hooks/HookManager.php�7t$zZ�7�Xn�Lvendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php�t$zZ��g�Hvendor/consolidation/annotated-command/src/Hooks/InteractorInterface.php:t$zZ:�Í�Hvendor/consolidation/annotated-command/src/Hooks/OptionHookInterface.php�t$zZ��k��Kvendor/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php�t$zZ��2Un�Nvendor/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php�t$zZ���x<�Gvendor/consolidation/annotated-command/src/Hooks/ValidatorInterface.php#t$zZ#��wP�Ovendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php�
t$zZ�
���Xvendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.phpmt$zZm�I�f�Gvendor/consolidation/annotated-command/src/Options/PrepareFormatter.phpt$zZԍRq�Qvendor/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.php�	t$zZ�	r���Bvendor/consolidation/annotated-command/src/OutputDataInterface.php?t$zZ?���Avendor/consolidation/annotated-command/src/Parser/CommandInfo.php Vt$zZ V�@M�Mvendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.phpt$zZ�Su�Kvendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php�t$zZ��XY�Nvendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.phpmt$zZm��d�Tvendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php�(t$zZ�(
D��[vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php/t$zZ/ѝ��Gvendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php^t$zZ^<O��Jvendor/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php�t$zZ��,&�Wvendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php
t$zZ
1�V!�Ivendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php�t$zZ��M7�2vendor/jakub-onderka/php-console-color/example.php�t$zZ�și��Xvendor/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/ConsoleColor.php+t$zZ+W�py�avendor/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/InvalidStyleException.php�t$zZ���z�Avendor/jakub-onderka/php-console-highlighter/examples/snippet.php,t$zZ,7d߇�Dvendor/jakub-onderka/php-console-highlighter/examples/whole_file.php't$zZ'�b�Qvendor/jakub-onderka/php-console-highlighter/examples/whole_file_line_numbers.php6t$zZ6�=�@�cvendor/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php�t$zZ�[	
�#vendor/pear/console_table/Table.phprt$zZrl��2�+vendor/nikic/php-parser/grammar/analyze.phpqt$zZq�]ὶ2vendor/nikic/php-parser/grammar/rebuildParsers.php�!t$zZ�!�j�4vendor/nikic/php-parser/lib/PhpParser/Autoloader.php�t$zZ�+����1vendor/nikic/php-parser/lib/PhpParser/Builder.php�t$zZ�D\�8vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php�t$zZ�`��=vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php�t$zZ�3�a��>vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.phplt$zZl
���;vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.phpLt$zZL]%o��<vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php�t$zZ�K j�8vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php�t$zZ����<vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php�t$zZ�@�/��7vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php�t$zZ�+��`�:vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php�	t$zZ�	���Y�8vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.phpt$zZo��6vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.phpt$zZ-�;�9vendor/nikic/php-parser/lib/PhpParser/BuilderAbstract.phpMt$zZM�o#��8vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.phpt
t$zZt
�Ӂ�1vendor/nikic/php-parser/lib/PhpParser/Comment.php�t$zZ�܋�P�5vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.phpMt$zZM��MӶ/vendor/nikic/php-parser/lib/PhpParser/Error.php�t$zZ�4sB�/vendor/nikic/php-parser/lib/PhpParser/Lexer.php�)t$zZ�)�9vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php�t$zZ���1@�.vendor/nikic/php-parser/lib/PhpParser/Node.php�t$zZ��%�2vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php�t$zZ�f@~�5vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php�t$zZ�ܲ��3vendor/nikic/php-parser/lib/PhpParser/Node/Expr.phpkt$zZk�Pp@�Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php�t$zZ��*��=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.phpht$zZh�C�¶:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php�t$zZ�����:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php�t$zZ����<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php�t$zZ��%���Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.phpxt$zZx�*�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.phpwt$zZw���!�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.phpxt$zZxu�0F�Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.phptt$zZt��&�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.phpqt$zZqx�EֶBvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.phpst$zZs���@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.phpqt$zZqZI�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.phpqt$zZq�y��Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.phprt$zZr[Wr�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.phpqt$zZq��:�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.phpwt$zZw!���Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.phpxt$zZx2�ֶ=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php�t$zZ��&%�<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.phpt$zZ��X�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.phpxt$zZxH�遶Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.phpwt$zZw�p׶Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.phpxt$zZx���Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.phpxt$zZx����Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.phpwt$zZw���@�Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.phpwt$zZwW|�]�Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.phptt$zZt�G~�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.phpqt$zZq��p�Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.phpst$zZs€V�Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.phput$zZu�>�m�Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php|t$zZ|ق��Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.phpwt$zZw��2�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.phpxt$zZx��J@�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.phpwt$zZw�<U�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.phpxt$zZxL|,�Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.phpst$zZs҆
��@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.phpqt$zZq�59�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.phpqt$zZq2+l�Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.phpvt$zZv��<�Ivendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpzt$zZz�C::�Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.phprt$zZr��p�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.phpqt$zZqc]��Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.phpwt$zZwI�
�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.phpxt$zZx��}�Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.phput$zZu��˶Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php|t$zZ|�Lwu�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.phpxt$zZx�(|D�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.phpt$zZ�
Ok�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php&t$zZ&�Wbc�8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.phpt$zZ0�3��?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.phpht$zZh����>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.phpht$zZh�8g�?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.phpit$zZi�ۯڶ=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.phpgt$zZg��6��@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.phpjt$zZj�>JW�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.phpjt$zZjj�*�?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.phpht$zZhb��!�Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php�t$zZ�,��۶:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.phpt$zZ�G�;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php�t$zZ�6n�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php�t$zZ��'�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php8t$zZ8�:�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.phpt$zZ
ܾn�Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php t$zZ c���9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.phpt$zZ�\�9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php�t$zZ�H����<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.phpt$zZ\DF�<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php+t$zZ+W�_T�?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php�t$zZ���^��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.phpt$zZf&q��9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.phpJt$zZJ�Qr�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php�t$zZ�C�ɀ�8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.phpUt$zZU��h
�;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.phpt$zZ�
X�;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.phpt$zZ9=��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.phpt$zZjIT��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.phpt$zZ�S,��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.phpt$zZ�0ͶAvendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php�t$zZ�m��Ͷ=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php?t$zZ?��]V�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php�t$zZ����Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php�t$zZ��»ɶ;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.phppt$zZp�C�b�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.phpt$zZ=K���=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php$t$zZ$k���<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php%t$zZ%��a!�=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php(t$zZ(5��ڶ:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php�t$zZ��6��;vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php>t$zZ>�wA�3vendor/nikic/php-parser/lib/PhpParser/Node/Name.php�t$zZ�i3�R�Bvendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php�t$zZ�ĝi��<vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php�t$zZ����w�4vendor/nikic/php-parser/lib/PhpParser/Node/Param.php�t$zZ��*�5vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.phpHt$zZH/��=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php�t$zZ���(��>vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php+t$zZ+��s�Hvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.phpTt$zZT 	��=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php,t$zZ,	�c�@vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php.t$zZ.Ez��Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php�t$zZ�-(+<�Dvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php�t$zZ�}u�Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php�t$zZ�J�V�Jvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php�t$zZ�B��Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php�t$zZ��M��Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php�t$zZ����Kvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php�t$zZ��"8F�Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php�t$zZ������=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php�t$zZ���&϶3vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.phpkt$zZk��D�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.phpNt$zZN���Ŷ9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php�t$zZ�s;}۶:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.phpwt$zZw"�3�>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.phpXt$zZX�I�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.phput$zZuWڳ��?vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php_
t$zZ_
� Gж:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php(t$zZ(�o�Զ:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.phpNt$zZN$�k�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.phpZt$zZZ�<��Bvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php�t$zZ�/��<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php�t$zZ����7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php�t$zZ�R�z�9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php)t$zZ)6+�;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php�t$zZ��^(y�9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php"t$zZ"]}J��8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.phpAt$zZA�C���<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.phpGt$zZG�9�Ƕ=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.phpxt$zZx��u��;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php;t$zZ;��ն9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php(t$zZ(+Å�<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.phpt$zZf��Y�@vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php}t$zZ}���7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.phpt$zZ��*��>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.phpt$zZ�=\�>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.phpwt$zZw�iI�9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.phpt$zZ���>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.phptt$zZt�%��7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php�t$zZ��‘�<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php�t$zZ��H��Dvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php�t$zZ�&y���;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php9t$zZ9�B
�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php�t$zZ�1ា;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.phpHt$zZHc�L�;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php�t$zZ��y�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php!t$zZ!z��<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.phpt$zZ5hW��Fvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php�t$zZ�݄嘶Lvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php�t$zZ���#�Qvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.phpet$zZenQ�L�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php?t$zZ?��@��<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.phpnt$zZn�U�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php4t$zZ4��I�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php�t$zZ��dU߶8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php�t$zZ�V��x�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php�t$zZ���ӥ�6vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php�t$zZ��x4��4vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php�t$zZ�A#y��7vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php�t$zZ��1�@vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php�t$zZ���j��5vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php�t$zZ��%�(�Bvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php�!t$zZ�! LW/�=vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php<t$zZ<!S(̶0vendor/nikic/php-parser/lib/PhpParser/Parser.phptt$zZt-C��9vendor/nikic/php-parser/lib/PhpParser/Parser/Multiple.phpt$zZ�F-P�5vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.phpkft$zZkf3�Z��5vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.phpt$zZ�N=��7vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.phpht$zZh�VKѶ8vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php�Pt$zZ�PO�{�7vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php-t$zZ-*�R@�@vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php�t$zZ�f�2�?vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php�-t$zZ�-�ݪc�4vendor/nikic/php-parser/lib/PhpParser/Serializer.phpt$zZ�{�8vendor/nikic/php-parser/lib/PhpParser/Serializer/XML.phpCt$zZCyʲS�6vendor/nikic/php-parser/lib/PhpParser/Unserializer.phpt$zZ�v�߶:vendor/nikic/php-parser/lib/PhpParser/Unserializer/XML.php8t$zZ8�}Ϳ�)vendor/nikic/php-parser/lib/bootstrap.php�t$zZ�]����(vendor/nikic/php-parser/test_old/run.php*t$zZ*3��+vendor/dnoegel/php-xdg-base-dir/src/Xdg.phpnt$zZn̑�w�'vendor/psy/psysh/src/Psy/Autoloader.phpQt$zZQ}���(vendor/psy/psysh/src/Psy/CodeCleaner.php�t$zZ��A׶:vendor/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php�t$zZ�#��?vendor/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.phpt$zZ�*ѭ�Dvendor/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php�t$zZ�}蜶8vendor/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.phpS	t$zZS	�1�˶8vendor/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php�t$zZ�3���1vendor/psy/psysh/src/Psy/CodeCleaner/ExitPass.php�t$zZ�yG�Ѷ7vendor/psy/psysh/src/Psy/CodeCleaner/FinalClassPass.php�t$zZ�ŝY;�<vendor/psy/psysh/src/Psy/CodeCleaner/FunctionContextPass.php;t$zZ;M��a�Ivendor/psy/psysh/src/Psy/CodeCleaner/FunctionReturnInWriteContextPass.php�t$zZ��;׶;vendor/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php�
t$zZ�
u�7vendor/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php2t$zZ2��X�<vendor/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php�t$zZ��f��8vendor/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php"t$zZ"���R�8vendor/psy/psysh/src/Psy/CodeCleaner/LoopContextPass.phpAt$zZAܟI��;vendor/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php1t$zZ1\-r�;vendor/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.phpPt$zZP����6vendor/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php�t$zZ�o�_v�6vendor/psy/psysh/src/Psy/CodeCleaner/NoReturnValue.php@t$zZ@��(�@vendor/psy/psysh/src/Psy/CodeCleaner/PassableByReferencePass.phpt$zZ����4vendor/psy/psysh/src/Psy/CodeCleaner/RequirePass.phpt$zZ��U��>vendor/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php�
t$zZ�

=���8vendor/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.phpM	t$zZM	�/��9vendor/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php�t$zZ���ȶ;vendor/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php�-t$zZ�-]��:vendor/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php�t$zZ��3Mo�>vendor/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php�t$zZ��%q�2vendor/psy/psysh/src/Psy/Command/BufferCommand.php�t$zZ�w3G�1vendor/psy/psysh/src/Psy/Command/ClearCommand.phpt$zZGp�ж,vendor/psy/psysh/src/Psy/Command/Command.php�t$zZ��*@�/vendor/psy/psysh/src/Psy/Command/DocCommand.php�t$zZ��"���0vendor/psy/psysh/src/Psy/Command/DumpCommand.php�t$zZ���Mi�0vendor/psy/psysh/src/Psy/Command/EditCommand.phpAt$zZAy��0vendor/psy/psysh/src/Psy/Command/ExitCommand.phpGt$zZGi}^0�0vendor/psy/psysh/src/Psy/Command/HelpCommand.php�
t$zZ�
K���3vendor/psy/psysh/src/Psy/Command/HistoryCommand.php�t$zZ��~.�0vendor/psy/psysh/src/Psy/Command/ListCommand.php�&t$zZ�&�g{��Hvendor/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.phpv
t$zZv
��Q�@vendor/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php
t$zZ
 �x��Cvendor/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php+t$zZ+�9G�;vendor/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php
t$zZ
���Cvendor/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php�
t$zZ�
eL�l�Ivendor/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php�t$zZ���p�Dvendor/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.phpAt$zZA�cqͶAvendor/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.phpt$zZ��̶Cvendor/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.phpt$zZm� �@vendor/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php�t$zZ����0�Cvendor/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php/t$zZ/gB_��1vendor/psy/psysh/src/Psy/Command/ParseCommand.php;t$zZ;4���6vendor/psy/psysh/src/Psy/Command/PsyVersionCommand.php�t$zZ�����6vendor/psy/psysh/src/Psy/Command/ReflectingCommand.php�"t$zZ�"xv�M�0vendor/psy/psysh/src/Psy/Command/ShowCommand.php�"t$zZ�"����0vendor/psy/psysh/src/Psy/Command/SudoCommand.php�t$zZ��9�;�3vendor/psy/psysh/src/Psy/Command/ThrowUpCommand.php�t$zZ��7�T�1vendor/psy/psysh/src/Psy/Command/TraceCommand.php�t$zZ�7�x��4vendor/psy/psysh/src/Psy/Command/WhereamiCommand.php�t$zZ�=E�/vendor/psy/psysh/src/Psy/Command/WtfCommand.php<t$zZ<�_�%�%vendor/psy/psysh/src/Psy/Compiler.php�t$zZ�����(vendor/psy/psysh/src/Psy/ConfigPaths.phpt$zZ�Bֲ�*vendor/psy/psysh/src/Psy/Configuration.php�t$zZ�7c��0vendor/psy/psysh/src/Psy/ConsoleColorFactory.php?	t$zZ?	R`�Ķ$vendor/psy/psysh/src/Psy/Context.phpTt$zZTK����)vendor/psy/psysh/src/Psy/ContextAware.php7t$zZ7�NqC�5vendor/psy/psysh/src/Psy/Exception/BreakException.phpZt$zZZ��ݳ�:vendor/psy/psysh/src/Psy/Exception/DeprecatedException.php~t$zZ~Y�)O�5vendor/psy/psysh/src/Psy/Exception/ErrorException.php�t$zZ���U��0vendor/psy/psysh/src/Psy/Exception/Exception.php�t$zZ��71�:vendor/psy/psysh/src/Psy/Exception/FatalErrorException.php�t$zZ���ζ:vendor/psy/psysh/src/Psy/Exception/ParseErrorException.php�t$zZ�C�}�7vendor/psy/psysh/src/Psy/Exception/RuntimeException.php�t$zZ���+��7vendor/psy/psysh/src/Psy/Exception/ThrowUpException.php{t$zZ{J{�h�9vendor/psy/psysh/src/Psy/Exception/TypeErrorException.php�t$zZ�g" ��6vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php�t$zZ��r�/vendor/psy/psysh/src/Psy/ExecutionLoop/Loop.phpt$zZsn�4vendor/psy/psysh/src/Psy/Formatter/CodeFormatter.phpst$zZs����8vendor/psy/psysh/src/Psy/Formatter/DocblockFormatter.php�t$zZ�Δ;�0vendor/psy/psysh/src/Psy/Formatter/Formatter.php�t$zZ���I��9vendor/psy/psysh/src/Psy/Formatter/SignatureFormatter.php�t$zZ�Z(4��/vendor/psy/psysh/src/Psy/Input/CodeArgument.php�t$zZ��ܻF�0vendor/psy/psysh/src/Psy/Input/FilterOptions.php�t$zZ�/�zQ�-vendor/psy/psysh/src/Psy/Input/ShellInput.phpi+t$zZi+z���.vendor/psy/psysh/src/Psy/Input/SilentInput.php�t$zZ��C]ȶ/vendor/psy/psysh/src/Psy/Output/OutputPager.php6t$zZ6a��1vendor/psy/psysh/src/Psy/Output/PassthruPager.php)t$zZ)7T���3vendor/psy/psysh/src/Psy/Output/ProcOutputPager.php�
t$zZ�
�$˶/vendor/psy/psysh/src/Psy/Output/ShellOutput.php"t$zZ"/T�Ͷ*vendor/psy/psysh/src/Psy/ParserFactory.php	t$zZ	G9p�1vendor/psy/psysh/src/Psy/Readline/GNUReadline.php�t$zZ����0vendor/psy/psysh/src/Psy/Readline/HoaConsole.php!t$zZ!D�ж-vendor/psy/psysh/src/Psy/Readline/Libedit.php	t$zZ	;+lP�.vendor/psy/psysh/src/Psy/Readline/Readline.php�t$zZ���6̶/vendor/psy/psysh/src/Psy/Readline/Transient.php�
t$zZ�
��?�:vendor/psy/psysh/src/Psy/Reflection/ReflectionConstant.php	
t$zZ	
	�JD�Cvendor/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstruct.php�t$zZ�g��:�Lvendor/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstructParameter.phpgt$zZg9�eȶ"vendor/psy/psysh/src/Psy/Shell.php;st$zZ;s��I�!vendor/psy/psysh/src/Psy/Sudo.php#t$zZ#3鲈�-vendor/psy/psysh/src/Psy/Sudo/SudoVisitor.phpt$zZ�*9�8vendor/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php
t$zZ
�`�Nvendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php%t$zZ%�h@�Svendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php3t$zZ3=�H�Bvendor/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php~t$zZ~����Ivendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.phpat$zZa�!��Vvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.phpit$zZiƎ�ضFvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php�t$zZ�����Dvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php^	t$zZ^	�ɞ=�Bvendor/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php�	t$zZ�	+���Cvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.phpEt$zZEx�۶Svendor/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php�t$zZ�Kf�Cvendor/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php|t$zZ|���Bvendor/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php<t$zZ<��T�Evendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.phpt$zZ%9r�Gvendor/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php�t$zZ��Dž#�Jvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php�t$zZ�����Wvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php�t$zZ��\��Gvendor/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.phpt$zZA��Cvendor/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php�t$zZ�!m�c�*vendor/psy/psysh/src/Psy/Util/Docblock.php�t$zZ�T��&vendor/psy/psysh/src/Psy/Util/Json.php�t$zZ����=�(vendor/psy/psysh/src/Psy/Util/Mirror.php�t$zZ���gM�%vendor/psy/psysh/src/Psy/Util/Str.php�t$zZ��h@�-vendor/psy/psysh/src/Psy/VarDumper/Cloner.phprt$zZrL�O�-vendor/psy/psysh/src/Psy/VarDumper/Dumper.php�t$zZ�n�	4�0vendor/psy/psysh/src/Psy/VarDumper/Presenter.php�t$zZ�|�y��5vendor/psy/psysh/src/Psy/VarDumper/PresenterAware.phpt$zZ{��3vendor/psy/psysh/src/Psy/VersionUpdater/Checker.php(t$zZ(N
��9vendor/psy/psysh/src/Psy/VersionUpdater/GitHubChecker.php�t$zZ�D��Ķ;vendor/psy/psysh/src/Psy/VersionUpdater/IntervalChecker.php�t$zZ�-Y6�7vendor/psy/psysh/src/Psy/VersionUpdater/NoopChecker.php]t$zZ]���F�&vendor/psy/psysh/src/Psy/functions.php�,t$zZ�,䯫��&vendor/webmozart/assert/src/Assert.php�~t$zZ�~O��,�'vendor/webmozart/path-util/src/Path.phpGzt$zZGz@��!�&vendor/webmozart/path-util/src/Url.phpt$zZ�)�vendor/autoload.php�t$zZ�-�@Ƕcommands/core/archive.drush.inc�Qt$zZ�Q�Њ�commands/core/cache.drush.inc�+t$zZ�+��Ub�commands/core/cli.drush.inct$zZ��E�commands/core/config.drush.inc�t$zZ�VK�:�commands/core/core.drush.inc�t$zZ�L�0.�commands/core/docs.drush.inc�/t$zZ�/�5�commands/core/drupal/batch.inc3%t$zZ3%t��b� commands/core/drupal/batch_6.incEt$zZE,b���commands/core/drupal/cache.incQ	t$zZQ	W��P� commands/core/drupal/cache_8.inc%t$zZ%�5
��$commands/core/drupal/environment.inc�/t$zZ�/�K�&�&commands/core/drupal/environment_6.inc�)t$zZ�)@�8ڶ&commands/core/drupal/environment_7.inc�'t$zZ�'.�8̶commands/core/drupal/image.inc�t$zZ���Ͷ commands/core/drupal/image_7.inc�t$zZ�v�≶commands/core/drupal/pm.inc�t$zZ��m�commands/core/drupal/pm_8.inc�
t$zZ�
�t�X�%commands/core/drupal/site_install.incat$zZaq9�e�'commands/core/drupal/site_install_6.inc�t$zZ�>hy�'commands/core/drupal/site_install_7.inc�t$zZ�����commands/core/drupal/update.incQ<t$zZQ<���!commands/core/drupal/update_6.inclLt$zZlL�ab�!commands/core/drupal/update_7.inc�-t$zZ�-�;�commands/core/field.drush.incW1t$zZW1�C��commands/core/help.drush.incit$zZi�&�"commands/core/helpsingle.drush.inc:)t$zZ:)0Bt@�commands/core/image.drush.inc�t$zZ��܍U�commands/core/init.drush.inc=t$zZ=j%�%�!commands/core/locale.d8.drush.inc�t$zZ�%�9�commands/core/notify.drush.inc�t$zZ���⭶$commands/core/outputformat.drush.inc)Et$zZ)E���i�,commands/core/outputformat/csv_or_string.inc=t$zZ=D��Z�#commands/core/outputformat/html.inc�t$zZ��XQ��'commands/core/outputformat/html.tpl.phpjt$zZj3�ζ#commands/core/outputformat/json.inc�t$zZ��R�_�(commands/core/outputformat/key_value.inc
t$zZ
sJJ�#commands/core/outputformat/list.inc
t$zZ
��^&�&commands/core/outputformat/message.inc�t$zZ��Xa޶"commands/core/outputformat/php.inc$t$zZ$�uMt�&commands/core/outputformat/print_r.inct$zZ!�7Ӷ%commands/core/outputformat/string.inc�t$zZ�?����$commands/core/outputformat/table.inc�t$zZ�����,commands/core/outputformat/topics/table.html�t$zZ�Xv �)commands/core/outputformat/var_export.inc{t$zZ{-�+��(commands/core/outputformat/variables.inc�t$zZ�I�>�#commands/core/outputformat/yaml.inc�t$zZ���$��commands/core/queue.drush.inc_
t$zZ_
�D��commands/core/role.drush.inc�'t$zZ�'ɳ��commands/core/rsync.core.inc90t$zZ90�v"ζcommands/core/scratch.php�t$zZ��8X��commands/core/search.drush.inct$zZ�*��"commands/core/shellalias.drush.inc�t$zZ�#�zb�$commands/core/site_install.drush.inc�.t$zZ�.�yc��!commands/core/sitealias.drush.inc 7t$zZ 7Zͤ�commands/core/ssh.drush.incp
t$zZp
��?�commands/core/state.drush.inc�t$zZ��9�r�commands/core/topic.drush.inc@t$zZ@a�Ӷcommands/core/usage.drush.inc8t$zZ8H���� commands/core/variable.drush.inc�#t$zZ�#�}��� commands/core/views.d8.drush.inc6t$zZ6���	� commands/core/watchdog.drush.inc:t$zZ:.>*ܶ(commands/make/generate.contents.make.inc�/t$zZ�/av��commands/make/generate.make.inc'/t$zZ'/�}�commands/make/lock.make.inc�t$zZ�ͥp8�commands/make/make.download.inc�Ht$zZ�Hⷜ�commands/make/make.drush.inc��t$zZ��+>&׶commands/make/make.project.inc�`t$zZ�`��2ö commands/make/make.utilities.incRWt$zZRWs�Ɇ�commands/make/update.make.incy	t$zZy	���}�commands/pm/download.pm.incYBt$zZYB�ݳ׶commands/pm/info.pm.inc|t$zZ|�D�-commands/pm/package_handler/git_drupalorg.incq,t$zZq,����$commands/pm/package_handler/wget.incmt$zZm�]8G�commands/pm/pm.drush.inc�At$zZ�A���̶commands/pm/projectinfo.pm.inc	t$zZ	d�~�commands/pm/updatecode.pm.inc-At$zZ-A�8BE�commands/pm/updatestatus.pm.inc�#t$zZ�#g�f�&commands/pm/version_control/backup.incZ
t$zZZ
x*�#commands/pm/version_control/bzr.incat$zZa>�W��#commands/pm/version_control/svn.inc�t$zZ�?�qz�#commands/runserver/d7-rs-router.php(t$zZ(�aV�#commands/runserver/d8-rs-router.php:t$zZ:�%
�(commands/runserver/runserver-prepend.php�
t$zZ�
!"�
�&commands/runserver/runserver.drush.incz#t$zZz#�Uu�commands/sql/sql.drush.inc�`t$zZ�`�9S��commands/sql/sqlsync.drush.inc
5t$zZ
5�b�`�commands/user/user.drush.inc�<t$zZ�<���e�commands/xh.drush.inc�t$zZ�p��$�docs/bastion.mdt$zZ�`��docs/bootstrap.md�t$zZ��l,e�docs/commands.mdu/t$zZu/-��docs/config-exporting.mdt$zZ5�P�docs/context.md�t$zZ�����docs/cron.md�
t$zZ�
;�S�docs/examples.md�
t$zZ�
�AĶ
docs/index.md�
t$zZ�
Nd��docs/install-alternative.md|
t$zZ|
�M�4�docs/install.md�t$zZ����docs/make.md�It$zZ�I��qr�docs/output-formats.md�t$zZ��%ⶶdocs/repl.mdst$zZs��A;�docs/shellaliases.md�t$zZ�aM~K�docs/shellscripts.md�t$zZ�,O�h�docs/strict-options.mdat$zZa^
�#�
docs/usage.md2t$zZ2gH[�examples/drush.wrapper�t$zZ��|�5�$examples/example.aliases.drushrc.php8t$zZ8Y1���examples/example.bashrc=t$zZ=�
��examples/example.drush.ini�
t$zZ�
˜�examples/example.drushrc.phpQ5t$zZQ5�a_�examples/example.maket$zZ���d�examples/example.make.yml�
t$zZ�
����examples/example.prompt.sh�
t$zZ�
W��Ӷexamples/git-bisect.example.shtt$zZt��`o�examples/helloworld.scriptDt$zZD�Fו�examples/pm_update.drush.incKt$zZKؔ]�examples/policy.drush.inc4t$zZ4�}\�examples/sandwich-nocolor.txt�
t$zZ�
s�x�examples/sandwich-topic.txtt$zZ����examples/sandwich.drush.inc�*t$zZ�*�Z�U�examples/sandwich.txt�t$zZ��E���examples/sync_enable.drush.inc�t$zZ��]ȼ� examples/sync_via_http.drush.inc�t$zZ�}��examples/xkcd.drush.incVt$zZV�ow�&includes/annotationcommand_adapter.incbqt$zZbqb'��includes/array_column.inc�t$zZ��([�includes/backend.incE�t$zZE��'��includes/batch.inc�
t$zZ�
�m�[�includes/bootstrap.inc*Mt$zZ*M=�=�includes/cache.inc
t$zZ
Y�nD�includes/command.inc�=t$zZ�=�1�S�includes/complete.inc�[t$zZ�[8�includes/context.inc�Yt$zZ�Y^|��includes/dbtng.inc�t$zZ��X��includes/drupal.incxt$zZx�YΟ�includes/drush.inc t$zZ ���`�includes/engines.inc�Qt$zZ�Q���ֶincludes/environment.incgt$zZg��ٶincludes/exec.inc�3t$zZ�3�q�o�includes/filesystem.incF^t$zZF^��includes/output.inc�ht$zZ�h� ��includes/preflight.inc�t$zZ�>��includes/sitealias.inc�Tt$zZ�T�����includes/startup.inc�<t$zZ�<���lib/Drush/Boot/BaseBoot.phpPt$zZP�H�߶lib/Drush/Boot/Boot.php^t$zZ^J��
�lib/Drush/Boot/DrupalBoot.php�Ut$zZ�U�h�|�lib/Drush/Boot/DrupalBoot6.php�
t$zZ�
wʒ�lib/Drush/Boot/DrupalBoot7.php6t$zZ6�V��lib/Drush/Boot/DrupalBoot8.phpNt$zZNd-Ӷlib/Drush/Boot/EmptyBoot.php�t$zZ�;�"lib/Drush/Cache/CacheInterface.php�t$zZ�&���lib/Drush/Cache/FileCache.phpt$zZ�O�G�lib/Drush/Cache/JSONCache.php�t$zZ�/��ƶ"lib/Drush/Command/Commandfiles.phpGt$zZG��7m�+lib/Drush/Command/CommandfilesInterface.php�t$zZ����'lib/Drush/Command/DrushInputAdapter.phpt$zZ�mᑶ(lib/Drush/Command/DrushOutputAdapter.php6t$zZ6f�Z�(lib/Drush/Command/ServiceCommandlist.php�t$zZ�j�@�-lib/Drush/CommandFiles/ExampleCommandFile.php�t$zZ��&?�1lib/Drush/CommandFiles/core/DrupliconCommands.php�t$zZ��19�.lib/Drush/CommandFiles/core/browseCommands.phpH	t$zZH	���,lib/Drush/Commands/core/SanitizeCommands.php� t$zZ� �x1�*lib/Drush/Commands/core/StatusCommands.phplt$zZl\��v�!lib/Drush/Drupal/DrupalKernel.php�t$zZ���)lib/Drush/Drupal/DrushServiceModifier.phpst$zZs�����'lib/Drush/Drupal/ExtensionDiscovery.php�t$zZ�àќ�-lib/Drush/Drupal/FindCommandsCompilerPass.php�
t$zZ�
{���lib/Drush/Log/DrushLog.php
t$zZ
�F�\�lib/Drush/Log/LogLevel.php�t$zZ������lib/Drush/Log/Logger.php{t$zZ{�p��#lib/Drush/Make/Parser/ParserIni.php�
t$zZ�
I*��)lib/Drush/Make/Parser/ParserInterface.php�t$zZ������$lib/Drush/Make/Parser/ParserYaml.php�t$zZ�v�#�lib/Drush/Psysh/Caster.php	t$zZ	��� lib/Drush/Psysh/DrushCommand.php�t$zZ�ت��$lib/Drush/Psysh/DrushHelpCommand.php�t$zZ�k�}��lib/Drush/Psysh/Shell.php`t$zZ`tŹ��lib/Drush/Queue/Queue6.phpmt$zZmx�`�lib/Drush/Queue/Queue7.php�t$zZ�ࢹU�lib/Drush/Queue/Queue8.php�t$zZ�ֹ*�lib/Drush/Queue/QueueBase.php1t$zZ1�%R�"lib/Drush/Queue/QueueException.phpJt$zZJ&��g�"lib/Drush/Queue/QueueInterface.phpxt$zZx�K\5�lib/Drush/Role/Role6.php�t$zZ���Y,�lib/Drush/Role/Role7.phpFt$zZF�d�u�lib/Drush/Role/Role8.phpmt$zZm���lib/Drush/Role/RoleBase.php$t$zZ$�H�� lib/Drush/Role/RoleException.phpHt$zZHpRɶlib/Drush/Sql/Sql6.phpt$zZv˦�lib/Drush/Sql/Sql7.php�t$zZ��?*�lib/Drush/Sql/Sql8.phpt$zZ�fm4�lib/Drush/Sql/SqlBase.php�/t$zZ�/4�Hɶlib/Drush/Sql/SqlException.phpFt$zZF,񝙶lib/Drush/Sql/SqlVersion.php�t$zZ����w�lib/Drush/Sql/Sqlmysql.php�t$zZ��	��lib/Drush/Sql/Sqloracle.php�t$zZ�,�j�lib/Drush/Sql/Sqlpgsql.php�t$zZ�F�`}�lib/Drush/Sql/Sqlsqlite.phpAt$zZAh����lib/Drush/Sql/Sqlsqlsrv.php	t$zZ	i��{�#lib/Drush/UpdateService/Project.php�St$zZ�S�
d�'lib/Drush/UpdateService/ReleaseInfo.php�t$zZ�����-lib/Drush/UpdateService/StatusInfoDrupal6.phpt$zZ�PyP�-lib/Drush/UpdateService/StatusInfoDrupal7.php?
t$zZ?
�w�=�-lib/Drush/UpdateService/StatusInfoDrupal8.php�t$zZ�}�sE�+lib/Drush/UpdateService/StatusInfoDrush.phpL>t$zZL>_�>��/lib/Drush/UpdateService/StatusInfoInterface.php�t$zZ�U�(z�lib/Drush/User/User6.php�t$zZ�Џ�r�lib/Drush/User/User7.php�t$zZ����R�lib/Drush/User/User8.php�t$zZ�a���lib/Drush/User/UserList.php�t$zZ��,Z�$lib/Drush/User/UserListException.phpLt$zZLl]��lib/Drush/User/UserSingle6.php�t$zZ����lib/Drush/User/UserSingle7.phpYt$zZY���i�lib/Drush/User/UserSingle8.phpLt$zZL�n2O�!lib/Drush/User/UserSingleBase.phpGt$zZG�(�lib/Drush/User/UserVersion.php|t$zZ|X{��misc/druplicon-color.txt��t$zZ��W�J�misc/druplicon-no_color.txtbt$zZb5���misc/windrush_build/README.mdQt$zZQ�=+X�'misc/windrush_build/assets/composer.batjt$zZj%�Ŷ$misc/windrush_build/assets/drush.bat�t$zZ��g@�0misc/windrush_build/assets/notify_env_change.exe�t$zZ���Ŷ%misc/windrush_build/assets/setenv.bat(t$zZ(x?]��$misc/windrush_build/assets/setenv.js{t$zZ{F����"misc/windrush_build/windrush_buildB
t$zZB
*;� �
drush.api.php�6t$zZ�6˅�0�drush.complete.sh�t$zZ����
drush.infot$zZJ�j��drush.launcherZt$zZZR�Q�	drush.php�t$zZ��D1�drush_logo-black.png�Zt$zZ�Z�!KҶ	README.mdgt$zZg�{	=�drush�t$zZ��yd�<?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\Adapter;

@trigger_error('The '.__NAMESPACE__.'\AbstractAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

/**
 * Interface for finder engine implementations.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
abstract class AbstractAdapter implements AdapterInterface
{
    protected $followLinks = false;
    protected $mode = 0;
    protected $minDepth = 0;
    protected $maxDepth = PHP_INT_MAX;
    protected $exclude = array();
    protected $names = array();
    protected $notNames = array();
    protected $contains = array();
    protected $notContains = array();
    protected $sizes = array();
    protected $dates = array();
    protected $filters = array();
    protected $sort = false;
    protected $paths = array();
    protected $notPaths = array();
    protected $ignoreUnreadableDirs = false;

    private static $areSupported = array();

    /**
     * {@inheritdoc}
     */
    public function isSupported()
    {
        $name = $this->getName();

        if (!array_key_exists($name, self::$areSupported)) {
            self::$areSupported[$name] = $this->canBeUsed();
        }

        return self::$areSupported[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function setFollowLinks($followLinks)
    {
        $this->followLinks = $followLinks;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setMode($mode)
    {
        $this->mode = $mode;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDepths(array $depths)
    {
        $this->minDepth = 0;
        $this->maxDepth = PHP_INT_MAX;

        foreach ($depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $this->minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $this->minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $this->maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $this->maxDepth = $comparator->getTarget();
                    break;
                default:
                    $this->minDepth = $this->maxDepth = $comparator->getTarget();
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setExclude(array $exclude)
    {
        $this->exclude = $exclude;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNames(array $names)
    {
        $this->names = $names;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotNames(array $notNames)
    {
        $this->notNames = $notNames;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setContains(array $contains)
    {
        $this->contains = $contains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotContains(array $notContains)
    {
        $this->notContains = $notContains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSizes(array $sizes)
    {
        $this->sizes = $sizes;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDates(array $dates)
    {
        $this->dates = $dates;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setFilters(array $filters)
    {
        $this->filters = $filters;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSort($sort)
    {
        $this->sort = $sort;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setPath(array $paths)
    {
        $this->paths = $paths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotPath(array $notPaths)
    {
        $this->notPaths = $notPaths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function ignoreUnreadableDirs($ignore = true)
    {
        $this->ignoreUnreadableDirs = (bool) $ignore;

        return $this;
    }

    /**
     * Returns whether the adapter is supported in the current environment.
     *
     * This method should be implemented in all adapters. Do not implement
     * isSupported in the adapters as the generic implementation provides a cache
     * layer.
     *
     * @see isSupported()
     *
     * @return bool Whether the adapter is supported
     */
    abstract protected function canBeUsed();
}
<?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\Adapter;

@trigger_error('The '.__NAMESPACE__.'\AbstractFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Expression\Expression;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Comparator\DateComparator;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
abstract class AbstractFindAdapter extends AbstractAdapter
{
    /**
     * @var Shell
     */
    protected $shell;

    public function __construct()
    {
        $this->shell = new Shell();
    }

    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        // having "/../" in path make find fail
        $dir = realpath($dir);

        // searching directories containing or not containing strings leads to no result
        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
            return new Iterator\FilePathsIterator(array(), $dir);
        }

        $command = Command::create();
        $find = $this->buildFindCommand($command, $dir);

        if ($this->followLinks) {
            $find->add('-follow');
        }

        $find->add('-mindepth')->add($this->minDepth + 1);

        if (PHP_INT_MAX !== $this->maxDepth) {
            $find->add('-maxdepth')->add($this->maxDepth + 1);
        }

        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
            $find->add('-type d');
        } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
            $find->add('-type f');
        }

        $this->buildNamesFiltering($find, $this->names);
        $this->buildNamesFiltering($find, $this->notNames, true);
        $this->buildPathsFiltering($find, $dir, $this->paths);
        $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
        $this->buildSizesFiltering($find, $this->sizes);
        $this->buildDatesFiltering($find, $this->dates);

        $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
        $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');

        if ($useGrep && ($this->contains || $this->notContains)) {
            $grep = $command->ins('grep');
            $this->buildContentFiltering($grep, $this->contains);
            $this->buildContentFiltering($grep, $this->notContains, true);
        }

        if ($useSort) {
            $this->buildSorting($command, $this->sort);
        }

        $command->setErrorHandler(
            $this->ignoreUnreadableDirs
                // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
                ? function ($stderr) { }
                : function ($stderr) { throw new AccessDeniedException($stderr); }
        );

        $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
        $iterator = new Iterator\FilePathsIterator($paths, $dir);

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        if (!$useGrep && ($this->contains || $this->notContains)) {
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->filters) {
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
        }

        if (!$useSort && $this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return $this->shell->testCommand('find');
    }

    /**
     * @param Command $command
     * @param string  $dir
     *
     * @return Command
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        return $command
            ->ins('find')
            ->add('find ')
            ->arg($dir)
            ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
    }

    /**
     * @param Command  $command
     * @param string[] $names
     * @param bool     $not
     */
    private function buildNamesFiltering(Command $command, array $names, $not = false)
    {
        if (0 === count($names)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($names as $i => $name) {
            $expr = Expression::create($name);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' and 'full path matching' regex problems.
            // - Jokers '.' are replaced by [^/].
            // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
                    ->setStartFlag(false)
                    ->setStartJoker(true)
                    ->replaceJokers('[^/]');
                if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
                    $regex->setEndJoker(false)->append('[^/]*');
                }
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-name' : '-iname')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command  $command
     * @param string   $dir
     * @param string[] $paths
     * @param bool     $not
     */
    private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
    {
        if (0 === count($paths)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($paths as $i => $path) {
            $expr = Expression::create($path);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' regex problems.
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
            } else {
                $expr->prepend('*')->append('*');
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-path' : '-ipath')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command            $command
     * @param NumberComparator[] $sizes
     */
    private function buildSizesFiltering(Command $command, array $sizes)
    {
        foreach ($sizes as $i => $size) {
            $command->add($i > 0 ? '-and' : null);

            switch ($size->getOperator()) {
                case '<=':
                    $command->add('-size -'.($size->getTarget() + 1).'c');
                    break;
                case '>=':
                    $command->add('-size +'.($size->getTarget() - 1).'c');
                    break;
                case '>':
                    $command->add('-size +'.$size->getTarget().'c');
                    break;
                case '!=':
                    $command->add('-size -'.$size->getTarget().'c');
                    $command->add('-size +'.$size->getTarget().'c');
                    break;
                case '<':
                default:
                    $command->add('-size -'.$size->getTarget().'c');
            }
        }
    }

    /**
     * @param Command          $command
     * @param DateComparator[] $dates
     */
    private function buildDatesFiltering(Command $command, array $dates)
    {
        foreach ($dates as $i => $date) {
            $command->add($i > 0 ? '-and' : null);

            $mins = (int) round((time() - $date->getTarget()) / 60);

            if (0 > $mins) {
                // mtime is in the future
                $command->add(' -mmin -0');
                // we will have no result so we don't need to continue
                return;
            }

            switch ($date->getOperator()) {
                case '<=':
                    $command->add('-mmin +'.($mins - 1));
                    break;
                case '>=':
                    $command->add('-mmin -'.($mins + 1));
                    break;
                case '>':
                    $command->add('-mmin -'.$mins);
                    break;
                case '!=':
                    $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
                    break;
                case '<':
                default:
                    $command->add('-mmin +'.$mins);
            }
        }
    }

    /**
     * @param Command $command
     * @param string  $sort
     *
     * @throws \InvalidArgumentException
     */
    private function buildSorting(Command $command, $sort)
    {
        $this->buildFormatSorting($command, $sort);
    }

    /**
     * @param Command $command
     * @param string  $sort
     */
    abstract protected function buildFormatSorting(Command $command, $sort);

    /**
     * @param Command $command
     * @param array   $contains
     * @param bool    $not
     */
    abstract protected function buildContentFiltering(Command $command, array $contains, $not = 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\Adapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
interface AdapterInterface
{
    /**
     * @param bool $followLinks
     *
     * @return $this
     */
    public function setFollowLinks($followLinks);

    /**
     * @param int $mode
     *
     * @return $this
     */
    public function setMode($mode);

    /**
     * @param array $exclude
     *
     * @return $this
     */
    public function setExclude(array $exclude);

    /**
     * @param array $depths
     *
     * @return $this
     */
    public function setDepths(array $depths);

    /**
     * @param array $names
     *
     * @return $this
     */
    public function setNames(array $names);

    /**
     * @param array $notNames
     *
     * @return $this
     */
    public function setNotNames(array $notNames);

    /**
     * @param array $contains
     *
     * @return $this
     */
    public function setContains(array $contains);

    /**
     * @param array $notContains
     *
     * @return $this
     */
    public function setNotContains(array $notContains);

    /**
     * @param array $sizes
     *
     * @return $this
     */
    public function setSizes(array $sizes);

    /**
     * @param array $dates
     *
     * @return $this
     */
    public function setDates(array $dates);

    /**
     * @param array $filters
     *
     * @return $this
     */
    public function setFilters(array $filters);

    /**
     * @param \Closure|int $sort
     *
     * @return $this
     */
    public function setSort($sort);

    /**
     * @param array $paths
     *
     * @return $this
     */
    public function setPath(array $paths);

    /**
     * @param array $notPaths
     *
     * @return $this
     */
    public function setNotPath(array $notPaths);

    /**
     * @param bool $ignore
     *
     * @return $this
     */
    public function ignoreUnreadableDirs($ignore = true);

    /**
     * @param string $dir
     *
     * @return \Iterator Result iterator
     */
    public function searchInDirectory($dir);

    /**
     * Tests adapter support for current platform.
     *
     * @return bool
     */
    public function isSupported();

    /**
     * Returns adapter name.
     *
     * @return string
     */
    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\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\BsdFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Expression\Expression;

/**
 * Shell engine implementation using BSD find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class BsdFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'bsd_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%HT';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%a';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%c';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%m';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->add('-print0 | xargs -0 stat -f')
            ->arg($format.'%t%N')
            ->add('| sort | cut -f 2');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1);

        return $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| grep -v \'^$\'')
                ->add('| xargs -I{} grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?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\Adapter;

@trigger_error('The '.__NAMESPACE__.'\GnuFindAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Expression\Expression;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class GnuFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'gnu_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%y';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%A@';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%C@';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%T@';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->get('find')
            ->add('-printf')
            ->arg($format.' %h/%f\\n')
            ->add('| sort | cut')
            ->arg('-d ')
            ->arg('-f2-')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return Shell::TYPE_UNIX === $this->shell->getType() && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| xargs -I{} -r grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?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\Adapter;

@trigger_error('The '.__NAMESPACE__.'\PhpAdapter class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Iterator;

/**
 * PHP finder engine implementation.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class PhpAdapter extends AbstractAdapter
{
    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) {
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->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 || $this->notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
        }

        if ($this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'php';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        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\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, array('>', '<', '>=', '<=', '==', '!='))) {
            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($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\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($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\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;

@trigger_error('The '.__NAMESPACE__.'\AdapterFailureException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Adapter\AdapterInterface;

/**
 * Base exception for all adapter failures.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class AdapterFailureException extends \RuntimeException implements ExceptionInterface
{
    /**
     * @var \Symfony\Component\Finder\Adapter\AdapterInterface
     */
    private $adapter;

    /**
     * @param AdapterInterface $adapter
     * @param string|null      $message
     * @param \Exception|null  $previous
     */
    public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null)
    {
        $this->adapter = $adapter;
        parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous);
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }
}
<?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 <contact@jfsimon.fr>
 */
interface ExceptionInterface
{
    /**
     * @return \Symfony\Component\Finder\Adapter\AdapterInterface
     */
    public function getAdapter();
}
<?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;

@trigger_error('The '.__NAMESPACE__.'\OperationNotPermitedException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class OperationNotPermitedException extends AdapterFailureException
{
}
<?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;

@trigger_error('The '.__NAMESPACE__.'\ShellCommandFailureException class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Adapter\AdapterInterface;
use Symfony\Component\Finder\Shell\Command;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class ShellCommandFailureException extends AdapterFailureException
{
    /**
     * @var Command
     */
    private $command;

    /**
     * @param AdapterInterface $adapter
     * @param Command          $command
     * @param \Exception|null  $previous
     */
    public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null)
    {
        $this->command = $command;
        parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous);
    }

    /**
     * @return Command
     */
    public function getCommand()
    {
        return $this->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\Finder\Expression;

@trigger_error('The '.__NAMESPACE__.'\Expression class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Expression implements ValueInterface
{
    const TYPE_REGEX = 1;
    const TYPE_GLOB = 2;

    /**
     * @var ValueInterface
     */
    private $value;

    /**
     * @param string $expr
     *
     * @return self
     */
    public static function create($expr)
    {
        return new self($expr);
    }

    /**
     * @param string $expr
     */
    public function __construct($expr)
    {
        try {
            $this->value = Regex::create($expr);
        } catch (\InvalidArgumentException $e) {
            $this->value = new Glob($expr);
        }
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->value->render();
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->value->renderPattern();
    }

    /**
     * @return bool
     */
    public function isCaseSensitive()
    {
        return $this->value->isCaseSensitive();
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->value->getType();
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->value->prepend($expr);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->value->append($expr);

        return $this;
    }

    /**
     * @return bool
     */
    public function isRegex()
    {
        return self::TYPE_REGEX === $this->value->getType();
    }

    /**
     * @return bool
     */
    public function isGlob()
    {
        return self::TYPE_GLOB === $this->value->getType();
    }

    /**
     * @return Glob
     *
     * @throws \LogicException
     */
    public function getGlob()
    {
        if (self::TYPE_GLOB !== $this->value->getType()) {
            throw new \LogicException('Regex can\'t be transformed to glob.');
        }

        return $this->value;
    }

    /**
     * @return Regex
     */
    public function getRegex()
    {
        return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex();
    }
}
<?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\Expression;

@trigger_error('The '.__NAMESPACE__.'\Glob class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Glob as FinderGlob;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Glob implements ValueInterface
{
    /**
     * @var string
     */
    private $pattern;

    /**
     * @param string $pattern
     */
    public function __construct($pattern)
    {
        $this->pattern = $pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_GLOB;
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * Tests if glob is expandable ("*.{a,b}" syntax).
     *
     * @return bool
     */
    public function isExpandable()
    {
        return false !== strpos($this->pattern, '{')
            && false !== strpos($this->pattern, '}');
    }

    /**
     * @param bool $strictLeadingDot
     * @param bool $strictWildcardSlash
     *
     * @return Regex
     */
    public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true)
    {
        $regex = FinderGlob::toRegex($this->pattern, $strictLeadingDot, $strictWildcardSlash, '');

        return new Regex($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\Expression;

@trigger_error('The '.__NAMESPACE__.'\Regex class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Regex implements ValueInterface
{
    const START_FLAG = '^';
    const END_FLAG = '$';
    const BOUNDARY = '~';
    const JOKER = '.*';
    const ESCAPING = '\\';

    /**
     * @var string
     */
    private $pattern;

    /**
     * @var array
     */
    private $options;

    /**
     * @var bool
     */
    private $startFlag;

    /**
     * @var bool
     */
    private $endFlag;

    /**
     * @var bool
     */
    private $startJoker;

    /**
     * @var bool
     */
    private $endJoker;

    /**
     * @param string $expr
     *
     * @return self
     *
     * @throws \InvalidArgumentException
     */
    public static function create($expr)
    {
        if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) {
            $start = substr($m[1], 0, 1);
            $end = substr($m[1], -1);

            if (
                ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start))
                || ('{' === $start && '}' === $end)
                || ('(' === $start && ')' === $end)
            ) {
                return new self(substr($m[1], 1, -1), $m[2], $end);
            }
        }

        throw new \InvalidArgumentException('Given expression is not a regex.');
    }

    /**
     * @param string $pattern
     * @param string $options
     * @param string $delimiter
     */
    public function __construct($pattern, $options = '', $delimiter = null)
    {
        if (null !== $delimiter) {
            // removes delimiter escaping
            $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern);
        }

        $this->parsePattern($pattern);
        $this->options = $options;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return self::BOUNDARY
            .$this->renderPattern()
            .self::BOUNDARY
            .$this->options;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return ($this->startFlag ? self::START_FLAG : '')
            .($this->startJoker ? self::JOKER : '')
            .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern)
            .($this->endJoker ? self::JOKER : '')
            .($this->endFlag ? self::END_FLAG : '');
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return !$this->hasOption('i');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_REGEX;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * @param string $option
     *
     * @return bool
     */
    public function hasOption($option)
    {
        return false !== strpos($this->options, $option);
    }

    /**
     * @param string $option
     *
     * @return $this
     */
    public function addOption($option)
    {
        if (!$this->hasOption($option)) {
            $this->options .= $option;
        }

        return $this;
    }

    /**
     * @param string $option
     *
     * @return $this
     */
    public function removeOption($option)
    {
        $this->options = str_replace($option, '', $this->options);

        return $this;
    }

    /**
     * @param bool $startFlag
     *
     * @return $this
     */
    public function setStartFlag($startFlag)
    {
        $this->startFlag = $startFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartFlag()
    {
        return $this->startFlag;
    }

    /**
     * @param bool $endFlag
     *
     * @return $this
     */
    public function setEndFlag($endFlag)
    {
        $this->endFlag = (bool) $endFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndFlag()
    {
        return $this->endFlag;
    }

    /**
     * @param bool $startJoker
     *
     * @return $this
     */
    public function setStartJoker($startJoker)
    {
        $this->startJoker = $startJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartJoker()
    {
        return $this->startJoker;
    }

    /**
     * @param bool $endJoker
     *
     * @return $this
     */
    public function setEndJoker($endJoker)
    {
        $this->endJoker = (bool) $endJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndJoker()
    {
        return $this->endJoker;
    }

    /**
     * @param array $replacement
     *
     * @return $this
     */
    public function replaceJokers($replacement)
    {
        $replace = function ($subject) use ($replacement) {
            $subject = $subject[0];
            $replace = 0 === substr_count($subject, '\\') % 2;

            return $replace ? str_replace('.', $replacement, $subject) : $subject;
        };

        $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern);

        return $this;
    }

    /**
     * @param string $pattern
     */
    private function parsePattern($pattern)
    {
        if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) {
            $pattern = substr($pattern, 1);
        }

        if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) {
            $pattern = substr($pattern, 2);
        }

        if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) {
            $pattern = substr($pattern, 0, -1);
        }

        if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) {
            $pattern = substr($pattern, 0, -2);
        }

        $this->pattern = $pattern;
    }
}
<?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\Expression;

@trigger_error('The '.__NAMESPACE__.'\ValueInterface interface is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface ValueInterface
{
    /**
     * Renders string representation of expression.
     *
     * @return string
     */
    public function render();

    /**
     * Renders string representation of pattern.
     *
     * @return string
     */
    public function renderPattern();

    /**
     * Returns value case sensitivity.
     *
     * @return bool
     */
    public function isCaseSensitive();

    /**
     * Returns expression type.
     *
     * @return int
     */
    public function getType();

    /**
     * @param string $expr
     *
     * @return $this
     */
    public function prepend($expr);

    /**
     * @param string $expr
     *
     * @return $this
     */
    public function append($expr);
}
<?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\Adapter\AdapterInterface;
use Symfony\Component\Finder\Adapter\GnuFindAdapter;
use Symfony\Component\Finder\Adapter\BsdFindAdapter;
use Symfony\Component\Finder\Adapter\PhpAdapter;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\ExceptionInterface;
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 easy 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;

    private $mode = 0;
    private $names = array();
    private $notNames = array();
    private $exclude = array();
    private $filters = array();
    private $depths = array();
    private $sizes = array();
    private $followLinks = false;
    private $sort = false;
    private $ignore = 0;
    private $dirs = array();
    private $dates = array();
    private $iterators = array();
    private $contains = array();
    private $notContains = array();
    private $adapters = null;
    private $paths = array();
    private $notPaths = array();
    private $ignoreUnreadableDirs = false;

    private static $vcsPatterns = array('.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();
    }

    /**
     * Registers a finder engine implementation.
     *
     * @param AdapterInterface $adapter  An adapter instance
     * @param int              $priority Highest is selected first
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function addAdapter(AdapterInterface $adapter, $priority = 0)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        $this->adapters[$adapter->getName()] = array(
            'adapter' => $adapter,
            'priority' => $priority,
            'selected' => false,
        );

        return $this->sortAdapters();
    }

    /**
     * Sets the selected adapter to the best one according to the current platform the code is run on.
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function useBestAdapter()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        $this->resetAdapterSelection();

        return $this->sortAdapters();
    }

    /**
     * Selects the adapter to use.
     *
     * @param string $name
     *
     * @return $this
     *
     * @throws \InvalidArgumentException
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function setAdapter($name)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        if (!isset($this->adapters[$name])) {
            throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name));
        }

        $this->resetAdapterSelection();
        $this->adapters[$name]['selected'] = true;

        return $this->sortAdapters();
    }

    /**
     * Removes all adapters registered in the finder.
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function removeAdapters()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->adapters = array();

        return $this;
    }

    /**
     * Returns registered adapters ordered by priority without extra information.
     *
     * @return AdapterInterface[]
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function getAdapters()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        return array_values(array_map(function (array $adapter) {
            return $adapter['adapter'];
        }, $this->adapters));
    }

    /**
     * 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.
     *
     * @param string|int $level The depth level expression
     *
     * @return $this
     *
     * @see DepthRangeFilterIterator
     * @see NumberComparator
     */
    public function depth($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');
     *
     * @param string $date A date range string
     *
     * @return $this
     *
     * @see strtotime
     * @see DateRangeFilterIterator
     * @see DateComparator
     */
    public function date($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')
     *
     * @param string $pattern A pattern (a regexp, a glob, or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function name($pattern)
    {
        $this->names[] = $pattern;

        return $this;
    }

    /**
     * Adds rules that files must not match.
     *
     * @param string $pattern A pattern (a regexp, a glob, or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notName($pattern)
    {
        $this->notNames[] = $pattern;

        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')
     *
     * @param string $pattern A pattern (string or regexp)
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function contains($pattern)
    {
        $this->contains[] = $pattern;

        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')
     *
     * @param string $pattern A pattern (string or regexp)
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function notContains($pattern)
    {
        $this->notContains[] = $pattern;

        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
     *
     * Use only / as dirname separator.
     *
     * @param string $pattern A pattern (a regexp or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function path($pattern)
    {
        $this->paths[] = $pattern;

        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
     *
     * Use only / as dirname separator.
     *
     * @param string $pattern A pattern (a regexp or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notPath($pattern)
    {
        $this->notPaths[] = $pattern;

        return $this;
    }

    /**
     * Adds tests for file sizes.
     *
     * $finder->size('> 10K');
     * $finder->size('<= 1Ki');
     * $finder->size(4);
     *
     * @param string|int $size A size range string or an integer
     *
     * @return $this
     *
     * @see SizeRangeFilterIterator
     * @see NumberComparator
     */
    public function size($size)
    {
        $this->sizes[] = new Comparator\NumberComparator($size);

        return $this;
    }

    /**
     * Excludes directories.
     *
     * @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).
     *
     * @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.
     *
     * @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;
    }

    /**
     * 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.
     *
     * @param \Closure $closure An anonymous function
     *
     * @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.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByName()
    {
        $this->sort = 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;
    }

    /**
     * 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.
     *
     * @param \Closure $closure An anonymous function
     *
     * @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|array $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @throws \InvalidArgumentException if one of the directories does not exist
     */
    public function in($dirs)
    {
        $resolvedDirs = array();

        foreach ((array) $dirs as $dir) {
            if (is_dir($dir)) {
                $resolvedDirs[] = $dir;
            } elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) {
                $resolvedDirs = array_merge($resolvedDirs, $glob);
            } else {
                throw new \InvalidArgumentException(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 mixed $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;
    }

    /**
     * Counts all the results collected by the iterators.
     *
     * @return int
     */
    public function count()
    {
        return iterator_count($this->getIterator());
    }

    /**
     * @return $this
     */
    private function sortAdapters()
    {
        uasort($this->adapters, function (array $a, array $b) {
            if ($a['selected'] || $b['selected']) {
                return $a['selected'] ? -1 : 1;
            }

            return $a['priority'] > $b['priority'] ? -1 : 1;
        });

        return $this;
    }

    /**
     * @param $dir
     *
     * @return \Iterator
     */
    private function searchInDirectory($dir)
    {
        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
            $this->exclude = array_merge($this->exclude, self::$vcsPatterns);
        }

        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
            $this->notPaths[] = '#(^|/)\..+(/|$)#';
        }

        if ($this->adapters) {
            foreach ($this->adapters as $adapter) {
                if ($adapter['adapter']->isSupported()) {
                    try {
                        return $this
                            ->buildAdapter($adapter['adapter'])
                            ->searchInDirectory($dir);
                    } catch (ExceptionInterface $e) {
                    }
                }
            }
        }

        $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 ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->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 || $this->notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
        }

        if ($this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * @param AdapterInterface $adapter
     *
     * @return AdapterInterface
     */
    private function buildAdapter(AdapterInterface $adapter)
    {
        return $adapter
            ->setFollowLinks($this->followLinks)
            ->setDepths($this->depths)
            ->setMode($this->mode)
            ->setExclude($this->exclude)
            ->setNames($this->names)
            ->setNotNames($this->notNames)
            ->setContains($this->contains)
            ->setNotContains($this->notContains)
            ->setSizes($this->sizes)
            ->setDates($this->dates)
            ->setFilters($this->filters)
            ->setSort($this->sort)
            ->setPath($this->paths)
            ->setNotPath($this->notPaths)
            ->ignoreUnreadableDirs($this->ignoreUnreadableDirs);
    }

    /**
     * Unselects all adapters.
     */
    private function resetAdapterSelection()
    {
        $this->adapters = array_map(function (array $properties) {
            $properties['selected'] = false;

            return $properties;
        }, $this->adapters);
    }

    private function initDefaultAdapters()
    {
        if (null === $this->adapters) {
            $this->adapters = array();
            $this
                ->addAdapter(new GnuFindAdapter())
                ->addAdapter(new BsdFindAdapter())
                ->addAdapter(new PhpAdapter(), -50)
                ->setAdapter('php')
            ;
        }
    }
}
<?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 (array('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) {
                if ($strictLeadingDot && '.' !== $car) {
                    $regex .= '(?=[^\.])';
                }

                $firstByte = false;
            }

            if ('/' === $car) {
                $firstByte = true;
            }

            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;

/**
 * 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 = array();

    /**
     * @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 === call_user_func($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\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 = array();

    /**
     * @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;

/**
 * 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, $minDepth = 0, $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;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator
{
    private $iterator;
    private $isRecursive;
    private $excludedDirs = array();
    private $excludedPattern;

    /**
     * @param \Iterator $iterator    The Iterator to filter
     * @param array     $directories An array of directories to exclude
     */
    public function __construct(\Iterator $iterator, array $directories)
    {
        $this->iterator = $iterator;
        $this->isRecursive = $iterator instanceof \RecursiveIterator;
        $patterns = array();
        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;
    }

    public function hasChildren()
    {
        return $this->isRecursive && $this->iterator->hasChildren();
    }

    public function getChildren()
    {
        $children = new self($this->iterator->getChildren(), array());
        $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;

@trigger_error('The '.__NAMESPACE__.'\FilePathsIterator class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\SplFileInfo;

/**
 * Iterate over shell command result.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class FilePathsIterator extends \ArrayIterator
{
    /**
     * @var string
     */
    private $baseDir;

    /**
     * @var int
     */
    private $baseDirLength;

    /**
     * @var string
     */
    private $subPath;

    /**
     * @var string
     */
    private $subPathname;

    /**
     * @var SplFileInfo
     */
    private $current;

    /**
     * @param array  $paths   List of paths returned by shell command
     * @param string $baseDir Base dir for relative path building
     */
    public function __construct(array $paths, $baseDir)
    {
        $this->baseDir = $baseDir;
        $this->baseDirLength = strlen($baseDir);

        parent::__construct($paths);
    }

    /**
     * @param string $name
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($name, array $arguments)
    {
        return call_user_func_array(array($this->current(), $name), $arguments);
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     *
     * @return SplFileInfo File information
     */
    public function current()
    {
        return $this->current;
    }

    /**
     * @return string
     */
    public function key()
    {
        return $this->current->getPathname();
    }

    public function next()
    {
        parent::next();
        $this->buildProperties();
    }

    public function rewind()
    {
        parent::rewind();
        $this->buildProperties();
    }

    /**
     * @return string
     */
    public function getSubPath()
    {
        return $this->subPath;
    }

    /**
     * @return string
     */
    public function getSubPathname()
    {
        return $this->subPathname;
    }

    private function buildProperties()
    {
        $absolutePath = parent::current();

        if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) {
            $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\');
            $dir = dirname($this->subPathname);
            $this->subPath = '.' === $dir ? '' : $dir;
        } else {
            $this->subPath = $this->subPathname = '';
        }

        $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname);
    }
}
<?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, $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;

/**
 * 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;

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;

/**
 * This iterator just overrides the rewind method in order to correct a PHP bug,
 * which existed before version 5.5.23/5.6.7.
 *
 * @see https://bugs.php.net/68557
 *
 * @author Alex Bogomazov
 */
abstract class FilterIterator extends \FilterIterator
{
    /**
     * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after
     * rewind in some cases.
     *
     * @see FilterIterator::rewind()
     */
    public function rewind()
    {
        if (\PHP_VERSION_ID > 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) {
            parent::rewind();

            return;
        }

        $iterator = $this;
        while ($iterator instanceof \OuterIterator) {
            $innerIterator = $iterator->getInnerIterator();

            if ($innerIterator instanceof RecursiveDirectoryIterator) {
                // this condition is necessary for iterators to work properly with non-local filesystems like ftp
                if ($innerIterator->isRewindable()) {
                    $innerIterator->next();
                    $innerIterator->rewind();
                }
            } elseif ($innerIterator instanceof \FilesystemIterator) {
                $innerIterator->next();
                $innerIterator->rewind();
            }

            $iterator = $innerIterator;
        }

        parent::rewind();
    }
}
<?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 = array();
    protected $noMatchRegexps = array();

    /**
     * @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 (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) 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;

/**
 * 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\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 = '/';

    /**
     * @param string $path
     * @param int    $flags
     * @param bool   $ignoreUnreadableDirs
     *
     * @throws \RuntimeException
     */
    public function __construct($path, $flags, $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 = (string) $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(array());
            } else {
                throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
            }
        }
    }

    /**
     * Do nothing for non rewindable stream.
     */
    public function rewind()
    {
        if (false === $this->isRewindable()) {
            return;
        }

        // @see https://bugs.php.net/68557
        if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) {
            parent::next();
        }

        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;
        }

        // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed
        if ('' === $this->getPath()) {
            return $this->rewindable = false;
        }

        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;

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 = array();

    /**
     * @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;

/**
 * SortableIterator applies a sort on a given Iterator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SortableIterator implements \IteratorAggregate
{
    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;

    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)
    {
        $this->iterator = $iterator;

        if (self::SORT_BY_NAME === $sort) {
            $this->sort = function ($a, $b) {
                return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_TYPE === $sort) {
            $this->sort = function ($a, $b) {
                if ($a->isDir() && $b->isFile()) {
                    return -1;
                } elseif ($a->isFile() && $b->isDir()) {
                    return 1;
                }

                return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
            $this->sort = function ($a, $b) {
                return $a->getATime() - $b->getATime();
            };
        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
            $this->sort = function ($a, $b) {
                return $a->getCTime() - $b->getCTime();
            };
        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
            $this->sort = function ($a, $b) {
                return $a->getMTime() - $b->getMTime();
            };
        } elseif (is_callable($sort)) {
            $this->sort = $sort;
        } else {
            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
        }
    }

    public function getIterator()
    {
        $array = iterator_to_array($this->iterator, true);
        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\Shell;

@trigger_error('The '.__NAMESPACE__.'\Command class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class Command
{
    /**
     * @var Command|null
     */
    private $parent;

    /**
     * @var array
     */
    private $bits = array();

    /**
     * @var array
     */
    private $labels = array();

    /**
     * @var \Closure|null
     */
    private $errorHandler;

    /**
     * @param Command|null $parent Parent command
     */
    public function __construct(Command $parent = null)
    {
        $this->parent = $parent;
    }

    /**
     * Returns command as string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->join();
    }

    /**
     * Creates a new Command instance.
     *
     * @param Command|null $parent Parent command
     *
     * @return self
     */
    public static function create(Command $parent = null)
    {
        return new self($parent);
    }

    /**
     * Escapes special chars from input.
     *
     * @param string $input A string to escape
     *
     * @return string The escaped string
     */
    public static function escape($input)
    {
        return escapeshellcmd($input);
    }

    /**
     * Quotes input.
     *
     * @param string $input An argument string
     *
     * @return string The quoted string
     */
    public static function quote($input)
    {
        return escapeshellarg($input);
    }

    /**
     * Appends a string or a Command instance.
     *
     * @param string|Command $bit
     *
     * @return $this
     */
    public function add($bit)
    {
        $this->bits[] = $bit;

        return $this;
    }

    /**
     * Prepends a string or a command instance.
     *
     * @param string|Command $bit
     *
     * @return $this
     */
    public function top($bit)
    {
        array_unshift($this->bits, $bit);

        foreach ($this->labels as $label => $index) {
            $this->labels[$label] += 1;
        }

        return $this;
    }

    /**
     * Appends an argument, will be quoted.
     *
     * @param string $arg
     *
     * @return $this
     */
    public function arg($arg)
    {
        $this->bits[] = self::quote($arg);

        return $this;
    }

    /**
     * Appends escaped special command chars.
     *
     * @param string $esc
     *
     * @return $this
     */
    public function cmd($esc)
    {
        $this->bits[] = self::escape($esc);

        return $this;
    }

    /**
     * Inserts a labeled command to feed later.
     *
     * @param string $label The unique label
     *
     * @return self|string
     *
     * @throws \RuntimeException If label already exists
     */
    public function ins($label)
    {
        if (isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" already exists.', $label));
        }

        $this->bits[] = self::create($this);
        $this->labels[$label] = count($this->bits) - 1;

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Retrieves a previously labeled command.
     *
     * @param string $label
     *
     * @return self|string
     *
     * @throws \RuntimeException
     */
    public function get($label)
    {
        if (!isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label));
        }

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Returns parent command (if any).
     *
     * @return self
     *
     * @throws \RuntimeException If command has no parent
     */
    public function end()
    {
        if (null === $this->parent) {
            throw new \RuntimeException('Calling end on root command doesn\'t make sense.');
        }

        return $this->parent;
    }

    /**
     * Counts bits stored in command.
     *
     * @return int The bits count
     */
    public function length()
    {
        return count($this->bits);
    }

    /**
     * @param \Closure $errorHandler
     *
     * @return $this
     */
    public function setErrorHandler(\Closure $errorHandler)
    {
        $this->errorHandler = $errorHandler;

        return $this;
    }

    /**
     * @return \Closure|null
     */
    public function getErrorHandler()
    {
        return $this->errorHandler;
    }

    /**
     * Executes current command.
     *
     * @return array The command result
     *
     * @throws \RuntimeException
     */
    public function execute()
    {
        if (null === $errorHandler = $this->errorHandler) {
            exec($this->join(), $output);
        } else {
            $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes);
            $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY);

            if ($error = stream_get_contents($pipes[2])) {
                $errorHandler($error);
            }

            proc_close($process);
        }

        return $output ?: array();
    }

    /**
     * Joins bits.
     *
     * @return string
     */
    public function join()
    {
        return implode(' ', array_filter(
            array_map(function ($bit) {
                return $bit instanceof Command ? $bit->join() : ($bit ?: null);
            }, $this->bits),
            function ($bit) { return null !== $bit; }
        ));
    }

    /**
     * Insert a string or a Command instance before the bit at given position $index (index starts from 0).
     *
     * @param string|Command $bit
     * @param int            $index
     *
     * @return $this
     */
    public function addAtIndex($bit, $index)
    {
        array_splice($this->bits, $index, 0, $bit instanceof self ? array($bit) : $bit);

        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\Finder\Shell;

@trigger_error('The '.__NAMESPACE__.'\Shell class is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class Shell
{
    const TYPE_UNIX = 1;
    const TYPE_DARWIN = 2;
    const TYPE_CYGWIN = 3;
    const TYPE_WINDOWS = 4;
    const TYPE_BSD = 5;

    /**
     * @var string|null
     */
    private $type;

    /**
     * Returns guessed OS type.
     *
     * @return int
     */
    public function getType()
    {
        if (null === $this->type) {
            $this->type = $this->guessType();
        }

        return $this->type;
    }

    /**
     * Tests if a command is available.
     *
     * @param string $command
     *
     * @return bool
     */
    public function testCommand($command)
    {
        if (!function_exists('exec')) {
            return false;
        }

        // todo: find a better way (command could not be available)
        $testCommand = 'which ';
        if (self::TYPE_WINDOWS === $this->type) {
            $testCommand = 'where ';
        }

        $command = escapeshellcmd($command);

        exec($testCommand.$command, $output, $code);

        return 0 === $code && count($output) > 0;
    }

    /**
     * Guesses OS type.
     *
     * @return int
     */
    private function guessType()
    {
        $os = strtolower(PHP_OS);

        if (false !== strpos($os, 'cygwin')) {
            return self::TYPE_CYGWIN;
        }

        if (false !== strpos($os, 'darwin')) {
            return self::TYPE_DARWIN;
        }

        if (false !== strpos($os, 'bsd')) {
            return self::TYPE_BSD;
        }

        if (0 === strpos($os, 'win')) {
            return self::TYPE_WINDOWS;
        }

        return self::TYPE_UNIX;
    }
}
<?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($file, $relativePath, $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;
    }

    /**
     * Returns the contents of the file.
     *
     * @return string the contents of the file
     *
     * @throws \RuntimeException
     */
    public function getContents()
    {
        $level = error_reporting(0);
        $content = file_get_contents($this->getPathname());
        error_reporting($level);
        if (false === $content) {
            $error = error_get_last();
            throw new \RuntimeException($error['message']);
        }

        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\EventDispatcher;

use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Lazily loads listeners and subscribers from the dependency injection
 * container.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 */
class ContainerAwareEventDispatcher extends EventDispatcher
{
    /**
     * The container from where services are loaded.
     *
     * @var ContainerInterface
     */
    private $container;

    /**
     * The service IDs of the event listeners and subscribers.
     *
     * @var array
     */
    private $listenerIds = array();

    /**
     * The services registered as listeners.
     *
     * @var array
     */
    private $listeners = array();

    /**
     * @param ContainerInterface $container A ContainerInterface instance
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Adds a service as event listener.
     *
     * @param string $eventName Event for which the listener is added
     * @param array  $callback  The service ID of the listener service & the method
     *                          name that has to be called
     * @param int    $priority  The higher this value, the earlier an event listener
     *                          will be triggered in the chain.
     *                          Defaults to 0.
     *
     * @throws \InvalidArgumentException
     */
    public function addListenerService($eventName, $callback, $priority = 0)
    {
        if (!is_array($callback) || 2 !== count($callback)) {
            throw new \InvalidArgumentException('Expected an array("service", "method") argument');
        }

        $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
    }

    public function removeListener($eventName, $listener)
    {
        $this->lazyLoad($eventName);

        if (isset($this->listenerIds[$eventName])) {
            foreach ($this->listenerIds[$eventName] as $i => $args) {
                list($serviceId, $method, $priority) = $args;
                $key = $serviceId.'.'.$method;
                if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
                    unset($this->listeners[$eventName][$key]);
                    if (empty($this->listeners[$eventName])) {
                        unset($this->listeners[$eventName]);
                    }
                    unset($this->listenerIds[$eventName][$i]);
                    if (empty($this->listenerIds[$eventName])) {
                        unset($this->listenerIds[$eventName]);
                    }
                }
            }
        }

        parent::removeListener($eventName, $listener);
    }

    /**
     * {@inheritdoc}
     */
    public function hasListeners($eventName = null)
    {
        if (null === $eventName) {
            return $this->listenerIds || $this->listeners || parent::hasListeners();
        }

        if (isset($this->listenerIds[$eventName])) {
            return true;
        }

        return parent::hasListeners($eventName);
    }

    /**
     * {@inheritdoc}
     */
    public function getListeners($eventName = null)
    {
        if (null === $eventName) {
            foreach ($this->listenerIds as $serviceEventName => $args) {
                $this->lazyLoad($serviceEventName);
            }
        } else {
            $this->lazyLoad($eventName);
        }

        return parent::getListeners($eventName);
    }

    /**
     * {@inheritdoc}
     */
    public function getListenerPriority($eventName, $listener)
    {
        $this->lazyLoad($eventName);

        return parent::getListenerPriority($eventName, $listener);
    }

    /**
     * Adds a service as event subscriber.
     *
     * @param string $serviceId The service ID of the subscriber service
     * @param string $class     The service's class name (which must implement EventSubscriberInterface)
     */
    public function addSubscriberService($serviceId, $class)
    {
        foreach ($class::getSubscribedEvents() as $eventName => $params) {
            if (is_string($params)) {
                $this->listenerIds[$eventName][] = array($serviceId, $params, 0);
            } elseif (is_string($params[0])) {
                $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
            } else {
                foreach ($params as $listener) {
                    $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
                }
            }
        }
    }

    public function getContainer()
    {
        return $this->container;
    }

    /**
     * Lazily loads listeners for this event from the dependency injection
     * container.
     *
     * @param string $eventName The name of the event to dispatch. The name of
     *                          the event is the name of the method that is
     *                          invoked on listeners.
     */
    protected function lazyLoad($eventName)
    {
        if (isset($this->listenerIds[$eventName])) {
            foreach ($this->listenerIds[$eventName] as $args) {
                list($serviceId, $method, $priority) = $args;
                $listener = $this->container->get($serviceId);

                $key = $serviceId.'.'.$method;
                if (!isset($this->listeners[$eventName][$key])) {
                    $this->addListener($eventName, array($listener, $method), $priority);
                } elseif ($this->listeners[$eventName][$key] !== $listener) {
                    parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
                    $this->addListener($eventName, array($listener, $method), $priority);
                }

                $this->listeners[$eventName][$key] = $listener;
            }
        }
    }
}
<?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\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Stopwatch\Stopwatch;
use Psr\Log\LoggerInterface;

/**
 * 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 $called;
    private $dispatcher;
    private $wrappedListeners;

    /**
     * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
     * @param Stopwatch                $stopwatch  A Stopwatch instance
     * @param LoggerInterface          $logger     A LoggerInterface instance
     */
    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
    {
        $this->dispatcher = $dispatcher;
        $this->stopwatch = $stopwatch;
        $this->logger = $logger;
        $this->called = array();
        $this->wrappedListeners = array();
    }

    /**
     * {@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)
    {
        if (!method_exists($this->dispatcher, 'getListenerPriority')) {
            return 0;
        }

        return $this->dispatcher->getListenerPriority($eventName, $listener);
    }

    /**
     * {@inheritdoc}
     */
    public function hasListeners($eventName = null)
    {
        return $this->dispatcher->hasListeners($eventName);
    }

    /**
     * {@inheritdoc}
     */
    public function dispatch($eventName, Event $event = null)
    {
        if (null === $event) {
            $event = new Event();
        }

        if (null !== $this->logger && $event->isPropagationStopped()) {
            $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
        }

        $this->preProcess($eventName);
        $this->preDispatch($eventName, $event);

        $e = $this->stopwatch->start($eventName, 'section');

        $this->dispatcher->dispatch($eventName, $event);

        if ($e->isStarted()) {
            $e->stop();
        }

        $this->postDispatch($eventName, $event);
        $this->postProcess($eventName);

        return $event;
    }

    /**
     * {@inheritdoc}
     */
    public function getCalledListeners()
    {
        $called = array();
        foreach ($this->called as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
                $called[$eventName.'.'.$info['pretty']] = $info;
            }
        }

        return $called;
    }

    /**
     * {@inheritdoc}
     */
    public function getNotCalledListeners()
    {
        try {
            $allListeners = $this->getListeners();
        } catch (\Exception $e) {
            if (null !== $this->logger) {
                $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
            }

            // unable to retrieve the uncalled listeners
            return array();
        }

        $notCalled = array();
        foreach ($allListeners as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $called = false;
                if (isset($this->called[$eventName])) {
                    foreach ($this->called[$eventName] as $l) {
                        if ($l->getWrappedListener() === $listener) {
                            $called = true;

                            break;
                        }
                    }
                }

                if (!$called) {
                    $info = $this->getListenerInfo($listener, $eventName);
                    $notCalled[$eventName.'.'.$info['pretty']] = $info;
                }
            }
        }

        uasort($notCalled, array($this, 'sortListenersByPriority'));

        return $notCalled;
    }

    /**
     * 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 call_user_func_array(array($this->dispatcher, $method), $arguments);
    }

    /**
     * Called before dispatching the event.
     *
     * @param string $eventName The event name
     * @param Event  $event     The event
     */
    protected function preDispatch($eventName, Event $event)
    {
    }

    /**
     * Called after dispatching the event.
     *
     * @param string $eventName The event name
     * @param Event  $event     The event
     */
    protected function postDispatch($eventName, Event $event)
    {
    }

    private function preProcess($eventName)
    {
        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
            $info = $this->getListenerInfo($listener, $eventName);
            $name = isset($info['class']) ? $info['class'] : $info['type'];
            $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
            $this->wrappedListeners[$eventName][] = $wrappedListener;
            $this->dispatcher->removeListener($eventName, $listener);
            $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']);
        }
    }

    private function postProcess($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);

            $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
            if ($listener->wasCalled()) {
                if (null !== $this->logger) {
                    $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
                }

                if (!isset($this->called[$eventName])) {
                    $this->called[$eventName] = new \SplObjectStorage();
                }

                $this->called[$eventName]->attach($listener);
            }

            if (null !== $this->logger && $skipped) {
                $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
            }

            if ($listener->stoppedPropagation()) {
                if (null !== $this->logger) {
                    $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
                }

                $skipped = true;
            }
        }
    }

    /**
     * Returns information about the listener.
     *
     * @param object $listener  The listener
     * @param string $eventName The event name
     *
     * @return array Information about the listener
     */
    private function getListenerInfo($listener, $eventName)
    {
        $info = array(
            'event' => $eventName,
            'priority' => $this->getListenerPriority($eventName, $listener),
        );

        // unwrap for correct listener info
        if ($listener instanceof WrappedListener) {
            $listener = $listener->getWrappedListener();
        }

        if ($listener instanceof \Closure) {
            $info += array(
                'type' => 'Closure',
                'pretty' => 'closure',
            );
        } elseif (is_string($listener)) {
            try {
                $r = new \ReflectionFunction($listener);
                $file = $r->getFileName();
                $line = $r->getStartLine();
            } catch (\ReflectionException $e) {
                $file = null;
                $line = null;
            }
            $info += array(
                'type' => 'Function',
                'function' => $listener,
                'file' => $file,
                'line' => $line,
                'pretty' => $listener,
            );
        } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
            if (!is_array($listener)) {
                $listener = array($listener, '__invoke');
            }
            $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
            try {
                $r = new \ReflectionMethod($class, $listener[1]);
                $file = $r->getFileName();
                $line = $r->getStartLine();
            } catch (\ReflectionException $e) {
                $file = null;
                $line = null;
            }
            $info += array(
                'type' => 'Method',
                'class' => $class,
                'method' => $listener[1],
                'file' => $file,
                'line' => $line,
                'pretty' => $class.'::'.$listener[1],
            );
        }

        return $info;
    }

    private function sortListenersByPriority($a, $b)
    {
        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 Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface TraceableEventDispatcherInterface extends EventDispatcherInterface
{
    /**
     * Gets the called listeners.
     *
     * @return array An array of called listeners
     */
    public function getCalledListeners();

    /**
     * Gets the not called listeners.
     *
     * @return array An array of not called listeners
     */
    public function getNotCalledListeners();
}
<?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\Stopwatch\Stopwatch;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
class WrappedListener
{
    private $listener;
    private $name;
    private $called;
    private $stoppedPropagation;
    private $stopwatch;
    private $dispatcher;

    public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
    {
        $this->listener = $listener;
        $this->name = $name;
        $this->stopwatch = $stopwatch;
        $this->dispatcher = $dispatcher;
        $this->called = false;
        $this->stoppedPropagation = false;
    }

    public function getWrappedListener()
    {
        return $this->listener;
    }

    public function wasCalled()
    {
        return $this->called;
    }

    public function stoppedPropagation()
    {
        return $this->stoppedPropagation;
    }

    public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
    {
        $this->called = true;

        $e = $this->stopwatch->start($this->name, 'event_listener');

        call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);

        if ($e->isStarted()) {
            $e->stop();
        }

        if ($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\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

/**
 * Compiler pass to register tagged services for an event dispatcher.
 */
class RegisterListenersPass implements CompilerPassInterface
{
    /**
     * @var string
     */
    protected $dispatcherService;

    /**
     * @var string
     */
    protected $listenerTag;

    /**
     * @var string
     */
    protected $subscriberTag;

    /**
     * @param string $dispatcherService Service name of the event dispatcher in processed container
     * @param string $listenerTag       Tag name used for listener
     * @param string $subscriberTag     Tag name used for subscribers
     */
    public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
    {
        $this->dispatcherService = $dispatcherService;
        $this->listenerTag = $listenerTag;
        $this->subscriberTag = $subscriberTag;
    }

    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
            return;
        }

        $definition = $container->findDefinition($this->dispatcherService);

        foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
            $def = $container->getDefinition($id);
            if (!$def->isPublic()) {
                throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
            }

            if ($def->isAbstract()) {
                throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
            }

            foreach ($events as $event) {
                $priority = isset($event['priority']) ? $event['priority'] : 0;

                if (!isset($event['event'])) {
                    throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
                }

                if (!isset($event['method'])) {
                    $event['method'] = 'on'.preg_replace_callback(array(
                        '/(?<=\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']);
                }

                $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
            }
        }

        foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
            $def = $container->getDefinition($id);
            if (!$def->isPublic()) {
                throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
            }

            if ($def->isAbstract()) {
                throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
            }

            // We must assume that the class value has been correctly filled, even if the service is created by a factory
            $class = $container->getParameterBag()->resolveValue($def->getClass());
            $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';

            if (!is_subclass_of($class, $interface)) {
                if (!class_exists($class, false)) {
                    throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }

                throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
            }

            $definition->addMethodCall('addSubscriberService', array($id, $class));
        }
    }
}
<?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 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>
 */
class Event
{
    /**
     * @var bool Whether no further event listeners should be triggered
     */
    private $propagationStopped = false;

    /**
     * @var EventDispatcherInterface Dispatcher that dispatched this event
     */
    private $dispatcher;

    /**
     * @var string This event's name
     */
    private $name;

    /**
     * Returns whether further event listeners should be triggered.
     *
     * @see Event::stopPropagation()
     *
     * @return bool Whether propagation was already stopped for this event
     */
    public function isPropagationStopped()
    {
        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()
    {
        $this->propagationStopped = true;
    }

    /**
     * Stores the EventDispatcher that dispatches this Event.
     *
     * @param EventDispatcherInterface $dispatcher
     *
     * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * Returns the EventDispatcher that dispatches this Event.
     *
     * @return EventDispatcherInterface
     *
     * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
     */
    public function getDispatcher()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED);

        return $this->dispatcher;
    }

    /**
     * Gets the event's name.
     *
     * @return string
     *
     * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
     */
    public function getName()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED);

        return $this->name;
    }

    /**
     * Sets the event's name property.
     *
     * @param string $name The event name
     *
     * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
     */
    public function setName($name)
    {
        $this->name = $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\EventDispatcher;

/**
 * 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>
 */
class EventDispatcher implements EventDispatcherInterface
{
    private $listeners = array();
    private $sorted = array();

    /**
     * {@inheritdoc}
     */
    public function dispatch($eventName, Event $event = null)
    {
        if (null === $event) {
            $event = new Event();
        }

        $event->setDispatcher($this);
        $event->setName($eventName);

        if ($listeners = $this->getListeners($eventName)) {
            $this->doDispatch($listeners, $eventName, $event);
        }

        return $event;
    }

    /**
     * {@inheritdoc}
     */
    public function getListeners($eventName = null)
    {
        if (null !== $eventName) {
            if (!isset($this->listeners[$eventName])) {
                return array();
            }

            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);
    }

    /**
     * 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)
    {
        if (!isset($this->listeners[$eventName])) {
            return;
        }

        foreach ($this->listeners[$eventName] as $priority => $listeners) {
            if (false !== in_array($listener, $listeners, true)) {
                return $priority;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasListeners($eventName = null)
    {
        return (bool) $this->getListeners($eventName);
    }

    /**
     * {@inheritdoc}
     */
    public function addListener($eventName, $listener, $priority = 0)
    {
        $this->listeners[$eventName][$priority][] = $listener;
        unset($this->sorted[$eventName]);
    }

    /**
     * {@inheritdoc}
     */
    public function removeListener($eventName, $listener)
    {
        if (!isset($this->listeners[$eventName])) {
            return;
        }

        foreach ($this->listeners[$eventName] as $priority => $listeners) {
            if (false !== ($key = array_search($listener, $listeners, true))) {
                unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (is_string($params)) {
                $this->addListener($eventName, array($subscriber, $params));
            } elseif (is_string($params[0])) {
                $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, array($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, array($subscriber, $listener[0]));
                }
            } else {
                $this->removeListener($eventName, array($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 Event      $event     The event object to pass to the event handlers/listeners
     */
    protected function doDispatch($listeners, $eventName, Event $event)
    {
        foreach ($listeners as $listener) {
            if ($event->isPropagationStopped()) {
                break;
            }
            call_user_func($listener, $event, $eventName, $this);
        }
    }

    /**
     * Sorts the internal list of listeners for the given event by priority.
     *
     * @param string $eventName The name of the event
     */
    private function sortListeners($eventName)
    {
        krsort($this->listeners[$eventName]);
        $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$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;

/**
 * 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
{
    /**
     * Dispatches an event to all registered listeners.
     *
     * @param string $eventName The name of the event to dispatch. The name of
     *                          the event is the name of the method that is
     *                          invoked on listeners.
     * @param Event  $event     the event to pass to the event handlers/listeners
     *                          If not supplied, an empty Event instance is created
     *
     * @return Event
     */
    public function dispatch($eventName, Event $event = null);

    /**
     * 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 he is
     * interested in and added as a listener for these events.
     *
     * @param EventSubscriberInterface $subscriber The subscriber
     */
    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);

    /**
     * Removes an event subscriber.
     *
     * @param EventSubscriberInterface $subscriber The subscriber
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber);

    /**
     * Gets the listeners of a specific event or all listeners sorted by descending priority.
     *
     * @param string $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);

    /**
     * Checks whether an event has any registered listeners.
     *
     * @param string $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;

/**
 * An EventSubscriber knows himself what events he 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:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('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;

/**
 * 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
{
    /**
     * Event subject.
     *
     * @var mixed usually object or callable
     */
    protected $subject;

    /**
     * Array of arguments.
     *
     * @var array
     */
    protected $arguments;

    /**
     * Encapsulate an event with $subject and $args.
     *
     * @param mixed $subject   The subject of the event, usually an object
     * @param array $arguments Arguments to store in the event
     */
    public function __construct($subject = null, array $arguments = array())
    {
        $this->subject = $subject;
        $this->arguments = $arguments;
    }

    /**
     * Getter for subject property.
     *
     * @return mixed $subject 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 = array())
    {
        $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;

/**
 * A read-only proxy for an event dispatcher.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class ImmutableEventDispatcher implements EventDispatcherInterface
{
    /**
     * The proxied dispatcher.
     *
     * @var EventDispatcherInterface
     */
    private $dispatcher;

    /**
     * Creates an unmodifiable proxy for an event dispatcher.
     *
     * @param EventDispatcherInterface $dispatcher The proxied event dispatcher
     */
    public function __construct(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * {@inheritdoc}
     */
    public function dispatch($eventName, Event $event = null)
    {
        return $this->dispatcher->dispatch($eventName, $event);
    }

    /**
     * {@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\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_convert_case         - Perform case folding on a string
 * - 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_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 anothers
 * - 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_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - 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_convert_case($s, $mode, $encoding = null)
    {
        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) {
            $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s);
            $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $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;
                    }
                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);
        }

        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) {
                $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_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 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 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);
        }

        $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($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_lower($s)
    {
        return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8');
    }

    private static function title_case_upper($s)
    {
        return self::mb_convert_case($s[0], MB_CASE_UPPER, '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;
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }
        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }
}
<?php

static $data = 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',
  'Ǵ' => 'ǵ',
  'Ƕ' => 'ƕ',
  'Ƿ' => 'ƿ',
  'Ǹ' => 'ǹ',
  'Ǻ' => 'ǻ',
  'Ǽ' => 'ǽ',
  'Ǿ' => 'ǿ',
  'Ȁ' => 'ȁ',
  'Ȃ' => 'ȃ',
  'Ȅ' => 'ȅ',
  'Ȇ' => 'ȇ',
  'Ȉ' => 'ȉ',
  'Ȋ' => 'ȋ',
  'Ȍ' => 'ȍ',
  'Ȏ' => 'ȏ',
  'Ȑ' => 'ȑ',
  'Ȓ' => 'ȓ',
  'Ȕ' => 'ȕ',
  'Ȗ' => 'ȗ',
  'Ș' => 'ș',
  'Ț' => 'ț',
  'Ȝ' => 'ȝ',
  'Ȟ' => 'ȟ',
  'Ƞ' => 'ƞ',
  'Ȣ' => 'ȣ',
  'Ȥ' => 'ȥ',
  'Ȧ' => 'ȧ',
  'Ȩ' => 'ȩ',
  'Ȫ' => 'ȫ',
  'Ȭ' => 'ȭ',
  'Ȯ' => 'ȯ',
  'Ȱ' => 'ȱ',
  'Ȳ' => 'ȳ',
  'Ⱥ' => 'ⱥ',
  'Ȼ' => 'ȼ',
  'Ƚ' => 'ƚ',
  'Ⱦ' => 'ⱦ',
  'Ɂ' => 'ɂ',
  'Ƀ' => 'ƀ',
  'Ʉ' => 'ʉ',
  'Ʌ' => 'ʌ',
  'Ɇ' => 'ɇ',
  'Ɉ' => 'ɉ',
  'Ɋ' => 'ɋ',
  'Ɍ' => 'ɍ',
  'Ɏ' => 'ɏ',
  'Ͱ' => 'ͱ',
  'Ͳ' => 'ͳ',
  'Ͷ' => 'ͷ',
  'Ϳ' => 'ϳ',
  'Ά' => 'ά',
  'Έ' => 'έ',
  'Ή' => 'ή',
  'Ί' => 'ί',
  'Ό' => 'ό',
  'Ύ' => 'ύ',
  'Ώ' => 'ώ',
  'Α' => 'α',
  'Β' => 'β',
  'Γ' => 'γ',
  'Δ' => 'δ',
  'Ε' => 'ε',
  'Ζ' => 'ζ',
  'Η' => 'η',
  'Θ' => 'θ',
  'Ι' => 'ι',
  'Κ' => 'κ',
  'Λ' => 'λ',
  'Μ' => 'μ',
  'Ν' => 'ν',
  'Ξ' => 'ξ',
  'Ο' => 'ο',
  'Π' => 'π',
  'Ρ' => 'ρ',
  'Σ' => 'σ',
  'Τ' => 'τ',
  'Υ' => 'υ',
  'Φ' => 'φ',
  'Χ' => 'χ',
  'Ψ' => 'ψ',
  'Ω' => 'ω',
  'Ϊ' => 'ϊ',
  'Ϋ' => 'ϋ',
  'Ϗ' => 'ϗ',
  'Ϙ' => 'ϙ',
  'Ϛ' => 'ϛ',
  'Ϝ' => 'ϝ',
  'Ϟ' => 'ϟ',
  'Ϡ' => 'ϡ',
  'Ϣ' => 'ϣ',
  'Ϥ' => 'ϥ',
  'Ϧ' => 'ϧ',
  'Ϩ' => 'ϩ',
  'Ϫ' => 'ϫ',
  'Ϭ' => 'ϭ',
  'Ϯ' => 'ϯ',
  'ϴ' => 'θ',
  'Ϸ' => 'ϸ',
  'Ϲ' => 'ϲ',
  'Ϻ' => 'ϻ',
  'Ͻ' => 'ͻ',
  'Ͼ' => 'ͼ',
  'Ͽ' => 'ͽ',
  'Ѐ' => 'ѐ',
  'Ё' => 'ё',
  'Ђ' => 'ђ',
  'Ѓ' => 'ѓ',
  'Є' => 'є',
  'Ѕ' => 'ѕ',
  'І' => 'і',
  'Ї' => 'ї',
  'Ј' => 'ј',
  'Љ' => 'љ',
  'Њ' => 'њ',
  'Ћ' => 'ћ',
  'Ќ' => 'ќ',
  'Ѝ' => 'ѝ',
  'Ў' => 'ў',
  'Џ' => 'џ',
  'А' => 'а',
  'Б' => 'б',
  'В' => 'в',
  'Г' => 'г',
  'Д' => 'д',
  'Е' => 'е',
  'Ж' => 'ж',
  'З' => 'з',
  'И' => 'и',
  'Й' => 'й',
  'К' => 'к',
  'Л' => 'л',
  'М' => 'м',
  'Н' => 'н',
  'О' => 'о',
  'П' => 'п',
  'Р' => 'р',
  'С' => 'с',
  'Т' => 'т',
  'У' => 'у',
  'Ф' => 'ф',
  'Х' => 'х',
  'Ц' => 'ц',
  'Ч' => 'ч',
  'Ш' => 'ш',
  'Щ' => 'щ',
  'Ъ' => 'ъ',
  'Ы' => 'ы',
  'Ь' => 'ь',
  'Э' => 'э',
  'Ю' => 'ю',
  'Я' => 'я',
  'Ѡ' => 'ѡ',
  'Ѣ' => 'ѣ',
  'Ѥ' => 'ѥ',
  'Ѧ' => 'ѧ',
  'Ѩ' => 'ѩ',
  'Ѫ' => 'ѫ',
  'Ѭ' => 'ѭ',
  'Ѯ' => 'ѯ',
  'Ѱ' => 'ѱ',
  'Ѳ' => 'ѳ',
  'Ѵ' => 'ѵ',
  'Ѷ' => 'ѷ',
  'Ѹ' => 'ѹ',
  'Ѻ' => 'ѻ',
  'Ѽ' => 'ѽ',
  'Ѿ' => 'ѿ',
  'Ҁ' => 'ҁ',
  'Ҋ' => 'ҋ',
  'Ҍ' => 'ҍ',
  'Ҏ' => 'ҏ',
  'Ґ' => 'ґ',
  'Ғ' => 'ғ',
  'Ҕ' => 'ҕ',
  'Җ' => 'җ',
  'Ҙ' => 'ҙ',
  'Қ' => 'қ',
  'Ҝ' => 'ҝ',
  'Ҟ' => 'ҟ',
  'Ҡ' => 'ҡ',
  'Ң' => 'ң',
  'Ҥ' => 'ҥ',
  'Ҧ' => 'ҧ',
  'Ҩ' => 'ҩ',
  'Ҫ' => 'ҫ',
  'Ҭ' => 'ҭ',
  'Ү' => 'ү',
  'Ұ' => 'ұ',
  'Ҳ' => 'ҳ',
  'Ҵ' => 'ҵ',
  'Ҷ' => 'ҷ',
  'Ҹ' => 'ҹ',
  'Һ' => 'һ',
  'Ҽ' => 'ҽ',
  'Ҿ' => 'ҿ',
  'Ӏ' => 'ӏ',
  'Ӂ' => 'ӂ',
  'Ӄ' => 'ӄ',
  'Ӆ' => 'ӆ',
  'Ӈ' => 'ӈ',
  'Ӊ' => 'ӊ',
  'Ӌ' => 'ӌ',
  'Ӎ' => 'ӎ',
  'Ӑ' => 'ӑ',
  'Ӓ' => 'ӓ',
  'Ӕ' => 'ӕ',
  'Ӗ' => 'ӗ',
  'Ә' => 'ә',
  'Ӛ' => 'ӛ',
  'Ӝ' => 'ӝ',
  'Ӟ' => 'ӟ',
  'Ӡ' => 'ӡ',
  'Ӣ' => 'ӣ',
  'Ӥ' => 'ӥ',
  'Ӧ' => 'ӧ',
  'Ө' => 'ө',
  'Ӫ' => 'ӫ',
  'Ӭ' => 'ӭ',
  'Ӯ' => 'ӯ',
  'Ӱ' => 'ӱ',
  'Ӳ' => 'ӳ',
  'Ӵ' => 'ӵ',
  'Ӷ' => 'ӷ',
  'Ӹ' => 'ӹ',
  'Ӻ' => 'ӻ',
  'Ӽ' => 'ӽ',
  'Ӿ' => 'ӿ',
  'Ԁ' => 'ԁ',
  'Ԃ' => 'ԃ',
  'Ԅ' => 'ԅ',
  'Ԇ' => 'ԇ',
  'Ԉ' => 'ԉ',
  'Ԋ' => 'ԋ',
  'Ԍ' => 'ԍ',
  'Ԏ' => 'ԏ',
  'Ԑ' => 'ԑ',
  'Ԓ' => 'ԓ',
  'Ԕ' => 'ԕ',
  'Ԗ' => 'ԗ',
  'Ԙ' => 'ԙ',
  'Ԛ' => 'ԛ',
  'Ԝ' => 'ԝ',
  'Ԟ' => 'ԟ',
  'Ԡ' => 'ԡ',
  'Ԣ' => 'ԣ',
  'Ԥ' => 'ԥ',
  'Ԧ' => 'ԧ',
  'Ԩ' => 'ԩ',
  'Ԫ' => 'ԫ',
  'Ԭ' => 'ԭ',
  'Ԯ' => 'ԯ',
  'Ա' => 'ա',
  'Բ' => 'բ',
  'Գ' => 'գ',
  'Դ' => 'դ',
  'Ե' => 'ե',
  'Զ' => 'զ',
  'Է' => 'է',
  'Ը' => 'ը',
  'Թ' => 'թ',
  'Ժ' => 'ժ',
  'Ի' => 'ի',
  'Լ' => 'լ',
  'Խ' => 'խ',
  'Ծ' => 'ծ',
  'Կ' => 'կ',
  'Հ' => 'հ',
  'Ձ' => 'ձ',
  'Ղ' => 'ղ',
  'Ճ' => 'ճ',
  'Մ' => 'մ',
  'Յ' => 'յ',
  'Ն' => 'ն',
  'Շ' => 'շ',
  'Ո' => 'ո',
  'Չ' => 'չ',
  'Պ' => 'պ',
  'Ջ' => 'ջ',
  'Ռ' => 'ռ',
  'Ս' => 'ս',
  'Վ' => 'վ',
  'Տ' => 'տ',
  'Ր' => 'ր',
  'Ց' => 'ց',
  'Ւ' => 'ւ',
  'Փ' => 'փ',
  'Ք' => 'ք',
  'Օ' => 'օ',
  'Ֆ' => 'ֆ',
  'Ⴀ' => 'ⴀ',
  'Ⴁ' => 'ⴁ',
  'Ⴂ' => 'ⴂ',
  'Ⴃ' => 'ⴃ',
  'Ⴄ' => 'ⴄ',
  'Ⴅ' => 'ⴅ',
  'Ⴆ' => 'ⴆ',
  'Ⴇ' => 'ⴇ',
  'Ⴈ' => 'ⴈ',
  'Ⴉ' => 'ⴉ',
  'Ⴊ' => 'ⴊ',
  'Ⴋ' => 'ⴋ',
  'Ⴌ' => 'ⴌ',
  'Ⴍ' => 'ⴍ',
  'Ⴎ' => 'ⴎ',
  'Ⴏ' => 'ⴏ',
  'Ⴐ' => 'ⴐ',
  'Ⴑ' => 'ⴑ',
  'Ⴒ' => 'ⴒ',
  'Ⴓ' => 'ⴓ',
  'Ⴔ' => 'ⴔ',
  'Ⴕ' => 'ⴕ',
  'Ⴖ' => 'ⴖ',
  'Ⴗ' => 'ⴗ',
  'Ⴘ' => 'ⴘ',
  'Ⴙ' => 'ⴙ',
  'Ⴚ' => 'ⴚ',
  'Ⴛ' => 'ⴛ',
  'Ⴜ' => 'ⴜ',
  'Ⴝ' => 'ⴝ',
  'Ⴞ' => 'ⴞ',
  'Ⴟ' => 'ⴟ',
  'Ⴠ' => 'ⴠ',
  'Ⴡ' => 'ⴡ',
  'Ⴢ' => 'ⴢ',
  'Ⴣ' => 'ⴣ',
  'Ⴤ' => 'ⴤ',
  'Ⴥ' => 'ⴥ',
  'Ⴧ' => 'ⴧ',
  'Ⴭ' => 'ⴭ',
  'Ḁ' => 'ḁ',
  'Ḃ' => 'ḃ',
  'Ḅ' => 'ḅ',
  'Ḇ' => 'ḇ',
  'Ḉ' => 'ḉ',
  'Ḋ' => 'ḋ',
  'Ḍ' => 'ḍ',
  'Ḏ' => 'ḏ',
  'Ḑ' => 'ḑ',
  'Ḓ' => 'ḓ',
  'Ḕ' => 'ḕ',
  'Ḗ' => 'ḗ',
  'Ḙ' => 'ḙ',
  'Ḛ' => 'ḛ',
  'Ḝ' => 'ḝ',
  'Ḟ' => 'ḟ',
  'Ḡ' => 'ḡ',
  'Ḣ' => 'ḣ',
  'Ḥ' => 'ḥ',
  'Ḧ' => 'ḧ',
  'Ḩ' => 'ḩ',
  'Ḫ' => 'ḫ',
  'Ḭ' => 'ḭ',
  'Ḯ' => 'ḯ',
  'Ḱ' => 'ḱ',
  'Ḳ' => 'ḳ',
  'Ḵ' => 'ḵ',
  'Ḷ' => 'ḷ',
  'Ḹ' => 'ḹ',
  'Ḻ' => 'ḻ',
  'Ḽ' => 'ḽ',
  'Ḿ' => 'ḿ',
  'Ṁ' => 'ṁ',
  'Ṃ' => 'ṃ',
  'Ṅ' => 'ṅ',
  'Ṇ' => 'ṇ',
  'Ṉ' => 'ṉ',
  'Ṋ' => 'ṋ',
  'Ṍ' => 'ṍ',
  'Ṏ' => 'ṏ',
  'Ṑ' => 'ṑ',
  'Ṓ' => 'ṓ',
  'Ṕ' => 'ṕ',
  'Ṗ' => 'ṗ',
  'Ṙ' => 'ṙ',
  'Ṛ' => 'ṛ',
  'Ṝ' => 'ṝ',
  'Ṟ' => 'ṟ',
  'Ṡ' => 'ṡ',
  'Ṣ' => 'ṣ',
  'Ṥ' => 'ṥ',
  'Ṧ' => 'ṧ',
  'Ṩ' => 'ṩ',
  'Ṫ' => 'ṫ',
  'Ṭ' => 'ṭ',
  'Ṯ' => 'ṯ',
  'Ṱ' => 'ṱ',
  'Ṳ' => 'ṳ',
  'Ṵ' => 'ṵ',
  'Ṷ' => 'ṷ',
  'Ṹ' => 'ṹ',
  'Ṻ' => 'ṻ',
  'Ṽ' => 'ṽ',
  'Ṿ' => 'ṿ',
  'Ẁ' => 'ẁ',
  'Ẃ' => 'ẃ',
  'Ẅ' => 'ẅ',
  'Ẇ' => 'ẇ',
  'Ẉ' => 'ẉ',
  'Ẋ' => 'ẋ',
  'Ẍ' => 'ẍ',
  'Ẏ' => 'ẏ',
  'Ẑ' => 'ẑ',
  'Ẓ' => 'ẓ',
  'Ẕ' => 'ẕ',
  'ẞ' => 'ß',
  'Ạ' => 'ạ',
  'Ả' => 'ả',
  'Ấ' => 'ấ',
  'Ầ' => 'ầ',
  'Ẩ' => 'ẩ',
  'Ẫ' => 'ẫ',
  'Ậ' => 'ậ',
  'Ắ' => 'ắ',
  'Ằ' => 'ằ',
  'Ẳ' => 'ẳ',
  'Ẵ' => 'ẵ',
  'Ặ' => 'ặ',
  'Ẹ' => 'ẹ',
  'Ẻ' => 'ẻ',
  'Ẽ' => 'ẽ',
  'Ế' => 'ế',
  'Ề' => 'ề',
  'Ể' => 'ể',
  'Ễ' => 'ễ',
  'Ệ' => 'ệ',
  'Ỉ' => 'ỉ',
  'Ị' => 'ị',
  'Ọ' => 'ọ',
  'Ỏ' => 'ỏ',
  'Ố' => 'ố',
  'Ồ' => 'ồ',
  'Ổ' => 'ổ',
  'Ỗ' => 'ỗ',
  'Ộ' => 'ộ',
  'Ớ' => 'ớ',
  'Ờ' => 'ờ',
  'Ở' => 'ở',
  'Ỡ' => 'ỡ',
  'Ợ' => 'ợ',
  'Ụ' => 'ụ',
  'Ủ' => 'ủ',
  'Ứ' => 'ứ',
  'Ừ' => 'ừ',
  'Ử' => 'ử',
  'Ữ' => 'ữ',
  'Ự' => 'ự',
  'Ỳ' => 'ỳ',
  'Ỵ' => 'ỵ',
  'Ỷ' => 'ỷ',
  'Ỹ' => 'ỹ',
  'Ỻ' => 'ỻ',
  'Ỽ' => 'ỽ',
  'Ỿ' => 'ỿ',
  'Ἀ' => 'ἀ',
  'Ἁ' => 'ἁ',
  'Ἂ' => 'ἂ',
  'Ἃ' => 'ἃ',
  'Ἄ' => 'ἄ',
  'Ἅ' => 'ἅ',
  'Ἆ' => 'ἆ',
  'Ἇ' => 'ἇ',
  'Ἐ' => 'ἐ',
  'Ἑ' => 'ἑ',
  'Ἒ' => 'ἒ',
  'Ἓ' => 'ἓ',
  'Ἔ' => 'ἔ',
  'Ἕ' => 'ἕ',
  'Ἠ' => 'ἠ',
  'Ἡ' => 'ἡ',
  'Ἢ' => 'ἢ',
  'Ἣ' => 'ἣ',
  'Ἤ' => 'ἤ',
  'Ἥ' => 'ἥ',
  'Ἦ' => 'ἦ',
  'Ἧ' => 'ἧ',
  'Ἰ' => 'ἰ',
  'Ἱ' => 'ἱ',
  'Ἲ' => 'ἲ',
  'Ἳ' => 'ἳ',
  'Ἴ' => 'ἴ',
  'Ἵ' => 'ἵ',
  'Ἶ' => 'ἶ',
  'Ἷ' => 'ἷ',
  'Ὀ' => 'ὀ',
  'Ὁ' => 'ὁ',
  'Ὂ' => 'ὂ',
  'Ὃ' => 'ὃ',
  'Ὄ' => 'ὄ',
  'Ὅ' => 'ὅ',
  'Ὑ' => 'ὑ',
  'Ὓ' => 'ὓ',
  'Ὕ' => 'ὕ',
  'Ὗ' => 'ὗ',
  'Ὠ' => 'ὠ',
  'Ὡ' => 'ὡ',
  'Ὢ' => 'ὢ',
  'Ὣ' => 'ὣ',
  'Ὤ' => 'ὤ',
  'Ὥ' => 'ὥ',
  'Ὦ' => 'ὦ',
  'Ὧ' => 'ὧ',
  'ᾈ' => 'ᾀ',
  'ᾉ' => 'ᾁ',
  'ᾊ' => 'ᾂ',
  'ᾋ' => 'ᾃ',
  'ᾌ' => 'ᾄ',
  'ᾍ' => 'ᾅ',
  'ᾎ' => 'ᾆ',
  'ᾏ' => 'ᾇ',
  'ᾘ' => 'ᾐ',
  'ᾙ' => 'ᾑ',
  'ᾚ' => 'ᾒ',
  'ᾛ' => 'ᾓ',
  'ᾜ' => 'ᾔ',
  'ᾝ' => 'ᾕ',
  'ᾞ' => 'ᾖ',
  'ᾟ' => 'ᾗ',
  'ᾨ' => 'ᾠ',
  'ᾩ' => 'ᾡ',
  'ᾪ' => 'ᾢ',
  'ᾫ' => 'ᾣ',
  'ᾬ' => 'ᾤ',
  'ᾭ' => 'ᾥ',
  'ᾮ' => 'ᾦ',
  'ᾯ' => 'ᾧ',
  'Ᾰ' => 'ᾰ',
  'Ᾱ' => 'ᾱ',
  'Ὰ' => 'ὰ',
  'Ά' => 'ά',
  'ᾼ' => 'ᾳ',
  'Ὲ' => 'ὲ',
  'Έ' => 'έ',
  'Ὴ' => 'ὴ',
  'Ή' => 'ή',
  'ῌ' => 'ῃ',
  'Ῐ' => 'ῐ',
  'Ῑ' => 'ῑ',
  'Ὶ' => 'ὶ',
  'Ί' => 'ί',
  'Ῠ' => 'ῠ',
  'Ῡ' => 'ῡ',
  'Ὺ' => 'ὺ',
  'Ύ' => 'ύ',
  'Ῥ' => 'ῥ',
  'Ὸ' => 'ὸ',
  'Ό' => 'ό',
  'Ὼ' => 'ὼ',
  'Ώ' => 'ώ',
  'ῼ' => 'ῳ',
  'Ω' => 'ω',
  'K' => 'k',
  'Å' => 'å',
  'Ⅎ' => 'ⅎ',
  'Ⅰ' => 'ⅰ',
  'Ⅱ' => 'ⅱ',
  'Ⅲ' => 'ⅲ',
  'Ⅳ' => 'ⅳ',
  'Ⅴ' => 'ⅴ',
  'Ⅵ' => 'ⅵ',
  'Ⅶ' => 'ⅶ',
  'Ⅷ' => 'ⅷ',
  'Ⅸ' => 'ⅸ',
  'Ⅹ' => 'ⅹ',
  'Ⅺ' => 'ⅺ',
  'Ⅻ' => 'ⅻ',
  'Ⅼ' => 'ⅼ',
  'Ⅽ' => 'ⅽ',
  'Ⅾ' => 'ⅾ',
  'Ⅿ' => 'ⅿ',
  'Ↄ' => 'ↄ',
  'Ⓐ' => 'ⓐ',
  'Ⓑ' => 'ⓑ',
  'Ⓒ' => 'ⓒ',
  'Ⓓ' => 'ⓓ',
  'Ⓔ' => 'ⓔ',
  'Ⓕ' => 'ⓕ',
  'Ⓖ' => 'ⓖ',
  'Ⓗ' => 'ⓗ',
  'Ⓘ' => 'ⓘ',
  'Ⓙ' => 'ⓙ',
  'Ⓚ' => 'ⓚ',
  'Ⓛ' => 'ⓛ',
  'Ⓜ' => 'ⓜ',
  'Ⓝ' => 'ⓝ',
  'Ⓞ' => 'ⓞ',
  'Ⓟ' => 'ⓟ',
  'Ⓠ' => 'ⓠ',
  'Ⓡ' => 'ⓡ',
  'Ⓢ' => 'ⓢ',
  'Ⓣ' => 'ⓣ',
  'Ⓤ' => 'ⓤ',
  'Ⓥ' => 'ⓥ',
  'Ⓦ' => 'ⓦ',
  'Ⓧ' => 'ⓧ',
  'Ⓨ' => 'ⓨ',
  'Ⓩ' => 'ⓩ',
  'Ⰰ' => 'ⰰ',
  'Ⰱ' => 'ⰱ',
  'Ⰲ' => 'ⰲ',
  'Ⰳ' => 'ⰳ',
  'Ⰴ' => 'ⰴ',
  'Ⰵ' => 'ⰵ',
  'Ⰶ' => 'ⰶ',
  'Ⰷ' => 'ⰷ',
  'Ⰸ' => 'ⰸ',
  'Ⰹ' => 'ⰹ',
  'Ⰺ' => 'ⰺ',
  'Ⰻ' => 'ⰻ',
  'Ⰼ' => 'ⰼ',
  'Ⰽ' => 'ⰽ',
  'Ⰾ' => 'ⰾ',
  'Ⰿ' => 'ⰿ',
  'Ⱀ' => 'ⱀ',
  'Ⱁ' => 'ⱁ',
  'Ⱂ' => 'ⱂ',
  'Ⱃ' => 'ⱃ',
  'Ⱄ' => 'ⱄ',
  'Ⱅ' => 'ⱅ',
  'Ⱆ' => 'ⱆ',
  'Ⱇ' => 'ⱇ',
  'Ⱈ' => 'ⱈ',
  'Ⱉ' => 'ⱉ',
  'Ⱊ' => 'ⱊ',
  'Ⱋ' => 'ⱋ',
  'Ⱌ' => 'ⱌ',
  'Ⱍ' => 'ⱍ',
  'Ⱎ' => 'ⱎ',
  'Ⱏ' => 'ⱏ',
  'Ⱐ' => 'ⱐ',
  'Ⱑ' => 'ⱑ',
  'Ⱒ' => 'ⱒ',
  'Ⱓ' => 'ⱓ',
  'Ⱔ' => 'ⱔ',
  'Ⱕ' => 'ⱕ',
  'Ⱖ' => 'ⱖ',
  'Ⱗ' => 'ⱗ',
  'Ⱘ' => 'ⱘ',
  'Ⱙ' => 'ⱙ',
  'Ⱚ' => 'ⱚ',
  'Ⱛ' => 'ⱛ',
  'Ⱜ' => 'ⱜ',
  'Ⱝ' => 'ⱝ',
  'Ⱞ' => 'ⱞ',
  'Ⱡ' => 'ⱡ',
  'Ɫ' => 'ɫ',
  'Ᵽ' => 'ᵽ',
  'Ɽ' => 'ɽ',
  'Ⱨ' => 'ⱨ',
  'Ⱪ' => 'ⱪ',
  'Ⱬ' => 'ⱬ',
  'Ɑ' => 'ɑ',
  'Ɱ' => 'ɱ',
  'Ɐ' => 'ɐ',
  'Ɒ' => 'ɒ',
  'Ⱳ' => 'ⱳ',
  'Ⱶ' => 'ⱶ',
  'Ȿ' => 'ȿ',
  'Ɀ' => 'ɀ',
  'Ⲁ' => 'ⲁ',
  'Ⲃ' => 'ⲃ',
  'Ⲅ' => 'ⲅ',
  'Ⲇ' => 'ⲇ',
  'Ⲉ' => 'ⲉ',
  'Ⲋ' => 'ⲋ',
  'Ⲍ' => 'ⲍ',
  'Ⲏ' => 'ⲏ',
  'Ⲑ' => 'ⲑ',
  'Ⲓ' => 'ⲓ',
  'Ⲕ' => 'ⲕ',
  'Ⲗ' => 'ⲗ',
  'Ⲙ' => 'ⲙ',
  'Ⲛ' => 'ⲛ',
  'Ⲝ' => 'ⲝ',
  'Ⲟ' => 'ⲟ',
  'Ⲡ' => 'ⲡ',
  'Ⲣ' => 'ⲣ',
  'Ⲥ' => 'ⲥ',
  'Ⲧ' => 'ⲧ',
  'Ⲩ' => 'ⲩ',
  'Ⲫ' => 'ⲫ',
  'Ⲭ' => 'ⲭ',
  'Ⲯ' => 'ⲯ',
  'Ⲱ' => 'ⲱ',
  'Ⲳ' => 'ⲳ',
  'Ⲵ' => 'ⲵ',
  'Ⲷ' => 'ⲷ',
  'Ⲹ' => 'ⲹ',
  'Ⲻ' => 'ⲻ',
  'Ⲽ' => 'ⲽ',
  'Ⲿ' => 'ⲿ',
  'Ⳁ' => 'ⳁ',
  'Ⳃ' => 'ⳃ',
  'Ⳅ' => 'ⳅ',
  'Ⳇ' => 'ⳇ',
  'Ⳉ' => 'ⳉ',
  'Ⳋ' => 'ⳋ',
  'Ⳍ' => 'ⳍ',
  'Ⳏ' => 'ⳏ',
  'Ⳑ' => 'ⳑ',
  'Ⳓ' => 'ⳓ',
  'Ⳕ' => 'ⳕ',
  'Ⳗ' => 'ⳗ',
  'Ⳙ' => 'ⳙ',
  'Ⳛ' => 'ⳛ',
  'Ⳝ' => 'ⳝ',
  'Ⳟ' => 'ⳟ',
  'Ⳡ' => 'ⳡ',
  'Ⳣ' => 'ⳣ',
  'Ⳬ' => 'ⳬ',
  'Ⳮ' => 'ⳮ',
  'Ⳳ' => 'ⳳ',
  'Ꙁ' => 'ꙁ',
  'Ꙃ' => 'ꙃ',
  'Ꙅ' => 'ꙅ',
  'Ꙇ' => 'ꙇ',
  'Ꙉ' => 'ꙉ',
  'Ꙋ' => 'ꙋ',
  'Ꙍ' => 'ꙍ',
  'Ꙏ' => 'ꙏ',
  'Ꙑ' => 'ꙑ',
  'Ꙓ' => 'ꙓ',
  'Ꙕ' => 'ꙕ',
  'Ꙗ' => 'ꙗ',
  'Ꙙ' => 'ꙙ',
  'Ꙛ' => 'ꙛ',
  'Ꙝ' => 'ꙝ',
  'Ꙟ' => 'ꙟ',
  'Ꙡ' => 'ꙡ',
  'Ꙣ' => 'ꙣ',
  'Ꙥ' => 'ꙥ',
  'Ꙧ' => 'ꙧ',
  'Ꙩ' => 'ꙩ',
  'Ꙫ' => 'ꙫ',
  'Ꙭ' => 'ꙭ',
  'Ꚁ' => 'ꚁ',
  'Ꚃ' => 'ꚃ',
  'Ꚅ' => 'ꚅ',
  'Ꚇ' => 'ꚇ',
  'Ꚉ' => 'ꚉ',
  'Ꚋ' => 'ꚋ',
  'Ꚍ' => 'ꚍ',
  'Ꚏ' => 'ꚏ',
  'Ꚑ' => 'ꚑ',
  'Ꚓ' => 'ꚓ',
  'Ꚕ' => 'ꚕ',
  'Ꚗ' => 'ꚗ',
  'Ꚙ' => 'ꚙ',
  'Ꚛ' => 'ꚛ',
  'Ꜣ' => 'ꜣ',
  'Ꜥ' => 'ꜥ',
  'Ꜧ' => 'ꜧ',
  'Ꜩ' => 'ꜩ',
  'Ꜫ' => 'ꜫ',
  'Ꜭ' => 'ꜭ',
  'Ꜯ' => 'ꜯ',
  'Ꜳ' => 'ꜳ',
  'Ꜵ' => 'ꜵ',
  'Ꜷ' => 'ꜷ',
  'Ꜹ' => 'ꜹ',
  'Ꜻ' => 'ꜻ',
  'Ꜽ' => 'ꜽ',
  'Ꜿ' => 'ꜿ',
  'Ꝁ' => 'ꝁ',
  'Ꝃ' => 'ꝃ',
  'Ꝅ' => 'ꝅ',
  'Ꝇ' => 'ꝇ',
  'Ꝉ' => 'ꝉ',
  'Ꝋ' => 'ꝋ',
  'Ꝍ' => 'ꝍ',
  'Ꝏ' => 'ꝏ',
  'Ꝑ' => 'ꝑ',
  'Ꝓ' => 'ꝓ',
  'Ꝕ' => 'ꝕ',
  'Ꝗ' => 'ꝗ',
  'Ꝙ' => 'ꝙ',
  'Ꝛ' => 'ꝛ',
  'Ꝝ' => 'ꝝ',
  'Ꝟ' => 'ꝟ',
  'Ꝡ' => 'ꝡ',
  'Ꝣ' => 'ꝣ',
  'Ꝥ' => 'ꝥ',
  'Ꝧ' => 'ꝧ',
  'Ꝩ' => 'ꝩ',
  'Ꝫ' => 'ꝫ',
  'Ꝭ' => 'ꝭ',
  'Ꝯ' => 'ꝯ',
  'Ꝺ' => 'ꝺ',
  'Ꝼ' => 'ꝼ',
  'Ᵹ' => 'ᵹ',
  'Ꝿ' => 'ꝿ',
  'Ꞁ' => 'ꞁ',
  'Ꞃ' => 'ꞃ',
  'Ꞅ' => 'ꞅ',
  'Ꞇ' => 'ꞇ',
  'Ꞌ' => 'ꞌ',
  'Ɥ' => 'ɥ',
  'Ꞑ' => 'ꞑ',
  'Ꞓ' => 'ꞓ',
  'Ꞗ' => 'ꞗ',
  'Ꞙ' => 'ꞙ',
  'Ꞛ' => 'ꞛ',
  'Ꞝ' => 'ꞝ',
  'Ꞟ' => 'ꞟ',
  'Ꞡ' => 'ꞡ',
  'Ꞣ' => 'ꞣ',
  'Ꞥ' => 'ꞥ',
  'Ꞧ' => 'ꞧ',
  'Ꞩ' => 'ꞩ',
  'Ɦ' => 'ɦ',
  'Ɜ' => 'ɜ',
  'Ɡ' => 'ɡ',
  'Ɬ' => 'ɬ',
  'Ʞ' => 'ʞ',
  'Ʇ' => 'ʇ',
  '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',
  '𐐀' => '𐐨',
  '𐐁' => '𐐩',
  '𐐂' => '𐐪',
  '𐐃' => '𐐫',
  '𐐄' => '𐐬',
  '𐐅' => '𐐭',
  '𐐆' => '𐐮',
  '𐐇' => '𐐯',
  '𐐈' => '𐐰',
  '𐐉' => '𐐱',
  '𐐊' => '𐐲',
  '𐐋' => '𐐳',
  '𐐌' => '𐐴',
  '𐐍' => '𐐵',
  '𐐎' => '𐐶',
  '𐐏' => '𐐷',
  '𐐐' => '𐐸',
  '𐐑' => '𐐹',
  '𐐒' => '𐐺',
  '𐐓' => '𐐻',
  '𐐔' => '𐐼',
  '𐐕' => '𐐽',
  '𐐖' => '𐐾',
  '𐐗' => '𐐿',
  '𐐘' => '𐑀',
  '𐐙' => '𐑁',
  '𐐚' => '𐑂',
  '𐐛' => '𐑃',
  '𐐜' => '𐑄',
  '𐐝' => '𐑅',
  '𐐞' => '𐑆',
  '𐐟' => '𐑇',
  '𐐠' => '𐑈',
  '𐐡' => '𐑉',
  '𐐢' => '𐑊',
  '𐐣' => '𐑋',
  '𐐤' => '𐑌',
  '𐐥' => '𐑍',
  '𐐦' => '𐑎',
  '𐐧' => '𐑏',
  '𑢠' => '𑣀',
  '𑢡' => '𑣁',
  '𑢢' => '𑣂',
  '𑢣' => '𑣃',
  '𑢤' => '𑣄',
  '𑢥' => '𑣅',
  '𑢦' => '𑣆',
  '𑢧' => '𑣇',
  '𑢨' => '𑣈',
  '𑢩' => '𑣉',
  '𑢪' => '𑣊',
  '𑢫' => '𑣋',
  '𑢬' => '𑣌',
  '𑢭' => '𑣍',
  '𑢮' => '𑣎',
  '𑢯' => '𑣏',
  '𑢰' => '𑣐',
  '𑢱' => '𑣑',
  '𑢲' => '𑣒',
  '𑢳' => '𑣓',
  '𑢴' => '𑣔',
  '𑢵' => '𑣕',
  '𑢶' => '𑣖',
  '𑢷' => '𑣗',
  '𑢸' => '𑣘',
  '𑢹' => '𑣙',
  '𑢺' => '𑣚',
  '𑢻' => '𑣛',
  '𑢼' => '𑣜',
  '𑢽' => '𑣝',
  '𑢾' => '𑣞',
  '𑢿' => '𑣟',
);

$result =& $data;
unset($data);

return $result;
<?php

static $data = 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Ž',
  'dž' => 'DŽ',
  'Lj' => 'LJ',
  'lj' => 'LJ',
  'Nj' => 'NJ',
  'nj' => 'NJ',
  'ǎ' => 'Ǎ',
  'ǐ' => 'Ǐ',
  'ǒ' => 'Ǒ',
  'ǔ' => 'Ǔ',
  'ǖ' => 'Ǖ',
  'ǘ' => 'Ǘ',
  'ǚ' => 'Ǚ',
  'ǜ' => 'Ǜ',
  'ǝ' => 'Ǝ',
  'ǟ' => 'Ǟ',
  'ǡ' => 'Ǡ',
  'ǣ' => 'Ǣ',
  'ǥ' => 'Ǥ',
  'ǧ' => 'Ǧ',
  'ǩ' => 'Ǩ',
  'ǫ' => 'Ǫ',
  'ǭ' => 'Ǭ',
  'ǯ' => 'Ǯ',
  'Dz' => 'DZ',
  'dz' => 'DZ',
  'ǵ' => 'Ǵ',
  'ǹ' => 'Ǹ',
  'ǻ' => 'Ǻ',
  'ǽ' => 'Ǽ',
  'ǿ' => 'Ǿ',
  'ȁ' => 'Ȁ',
  'ȃ' => 'Ȃ',
  'ȅ' => 'Ȅ',
  'ȇ' => 'Ȇ',
  'ȉ' => 'Ȉ',
  'ȋ' => 'Ȋ',
  'ȍ' => 'Ȍ',
  'ȏ' => 'Ȏ',
  'ȑ' => 'Ȑ',
  'ȓ' => 'Ȓ',
  'ȕ' => 'Ȕ',
  'ȗ' => 'Ȗ',
  'ș' => 'Ș',
  'ț' => 'Ț',
  'ȝ' => 'Ȝ',
  'ȟ' => 'Ȟ',
  'ȣ' => 'Ȣ',
  'ȥ' => 'Ȥ',
  'ȧ' => 'Ȧ',
  'ȩ' => 'Ȩ',
  'ȫ' => 'Ȫ',
  'ȭ' => 'Ȭ',
  'ȯ' => 'Ȯ',
  'ȱ' => 'Ȱ',
  'ȳ' => 'Ȳ',
  'ȼ' => 'Ȼ',
  'ȿ' => 'Ȿ',
  'ɀ' => 'Ɀ',
  'ɂ' => 'Ɂ',
  'ɇ' => 'Ɇ',
  'ɉ' => 'Ɉ',
  'ɋ' => 'Ɋ',
  'ɍ' => 'Ɍ',
  'ɏ' => 'Ɏ',
  'ɐ' => 'Ɐ',
  'ɑ' => 'Ɑ',
  'ɒ' => 'Ɒ',
  'ɓ' => 'Ɓ',
  'ɔ' => 'Ɔ',
  'ɖ' => 'Ɖ',
  'ɗ' => 'Ɗ',
  'ə' => 'Ə',
  'ɛ' => 'Ɛ',
  'ɜ' => 'Ɜ',
  'ɠ' => 'Ɠ',
  'ɡ' => 'Ɡ',
  'ɣ' => 'Ɣ',
  'ɥ' => 'Ɥ',
  'ɦ' => 'Ɦ',
  'ɨ' => 'Ɨ',
  'ɩ' => 'Ɩ',
  'ɫ' => 'Ɫ',
  'ɬ' => 'Ɬ',
  'ɯ' => 'Ɯ',
  'ɱ' => 'Ɱ',
  'ɲ' => 'Ɲ',
  'ɵ' => 'Ɵ',
  'ɽ' => 'Ɽ',
  'ʀ' => 'Ʀ',
  'ʃ' => 'Ʃ',
  'ʇ' => 'Ʇ',
  'ʈ' => 'Ʈ',
  'ʉ' => 'Ʉ',
  'ʊ' => 'Ʊ',
  'ʋ' => 'Ʋ',
  'ʌ' => 'Ʌ',
  'ʒ' => 'Ʒ',
  'ʞ' => 'Ʞ',
  'ͅ' => 'Ι',
  'ͱ' => 'Ͱ',
  'ͳ' => 'Ͳ',
  'ͷ' => 'Ͷ',
  'ͻ' => 'Ͻ',
  'ͼ' => 'Ͼ',
  'ͽ' => 'Ͽ',
  'ά' => 'Ά',
  'έ' => 'Έ',
  'ή' => 'Ή',
  'ί' => 'Ί',
  'α' => 'Α',
  'β' => 'Β',
  'γ' => 'Γ',
  'δ' => 'Δ',
  'ε' => 'Ε',
  'ζ' => 'Ζ',
  'η' => 'Η',
  'θ' => 'Θ',
  'ι' => 'Ι',
  'κ' => 'Κ',
  'λ' => 'Λ',
  'μ' => 'Μ',
  'ν' => 'Ν',
  'ξ' => 'Ξ',
  'ο' => 'Ο',
  'π' => 'Π',
  'ρ' => 'Ρ',
  'ς' => 'Σ',
  'σ' => 'Σ',
  'τ' => 'Τ',
  'υ' => 'Υ',
  'φ' => 'Φ',
  'χ' => 'Χ',
  'ψ' => 'Ψ',
  'ω' => 'Ω',
  'ϊ' => 'Ϊ',
  'ϋ' => 'Ϋ',
  'ό' => 'Ό',
  'ύ' => 'Ύ',
  'ώ' => 'Ώ',
  'ϐ' => 'Β',
  'ϑ' => 'Θ',
  'ϕ' => 'Φ',
  'ϖ' => 'Π',
  'ϗ' => 'Ϗ',
  'ϙ' => 'Ϙ',
  'ϛ' => 'Ϛ',
  'ϝ' => 'Ϝ',
  'ϟ' => 'Ϟ',
  'ϡ' => 'Ϡ',
  'ϣ' => 'Ϣ',
  'ϥ' => 'Ϥ',
  'ϧ' => 'Ϧ',
  'ϩ' => 'Ϩ',
  'ϫ' => 'Ϫ',
  'ϭ' => 'Ϭ',
  'ϯ' => 'Ϯ',
  'ϰ' => 'Κ',
  'ϱ' => 'Ρ',
  'ϲ' => 'Ϲ',
  'ϳ' => 'Ϳ',
  'ϵ' => 'Ε',
  'ϸ' => 'Ϸ',
  'ϻ' => 'Ϻ',
  'а' => 'А',
  'б' => 'Б',
  'в' => 'В',
  'г' => 'Г',
  'д' => 'Д',
  'е' => 'Е',
  'ж' => 'Ж',
  'з' => 'З',
  'и' => 'И',
  'й' => 'Й',
  'к' => 'К',
  'л' => 'Л',
  'м' => 'М',
  'н' => 'Н',
  'о' => 'О',
  'п' => 'П',
  'р' => 'Р',
  'с' => 'С',
  'т' => 'Т',
  'у' => 'У',
  'ф' => 'Ф',
  'х' => 'Х',
  'ц' => 'Ц',
  'ч' => 'Ч',
  'ш' => 'Ш',
  'щ' => 'Щ',
  'ъ' => 'Ъ',
  'ы' => 'Ы',
  'ь' => 'Ь',
  'э' => 'Э',
  'ю' => 'Ю',
  'я' => 'Я',
  'ѐ' => 'Ѐ',
  'ё' => 'Ё',
  'ђ' => 'Ђ',
  'ѓ' => 'Ѓ',
  'є' => 'Є',
  'ѕ' => 'Ѕ',
  'і' => 'І',
  'ї' => 'Ї',
  'ј' => 'Ј',
  'љ' => 'Љ',
  'њ' => 'Њ',
  'ћ' => 'Ћ',
  'ќ' => 'Ќ',
  'ѝ' => 'Ѝ',
  'ў' => 'Ў',
  'џ' => 'Џ',
  'ѡ' => 'Ѡ',
  'ѣ' => 'Ѣ',
  'ѥ' => 'Ѥ',
  'ѧ' => 'Ѧ',
  'ѩ' => 'Ѩ',
  'ѫ' => 'Ѫ',
  'ѭ' => 'Ѭ',
  'ѯ' => 'Ѯ',
  'ѱ' => 'Ѱ',
  'ѳ' => 'Ѳ',
  'ѵ' => 'Ѵ',
  'ѷ' => 'Ѷ',
  'ѹ' => 'Ѹ',
  'ѻ' => 'Ѻ',
  'ѽ' => 'Ѽ',
  'ѿ' => 'Ѿ',
  'ҁ' => 'Ҁ',
  'ҋ' => 'Ҋ',
  'ҍ' => 'Ҍ',
  'ҏ' => 'Ҏ',
  'ґ' => 'Ґ',
  'ғ' => 'Ғ',
  'ҕ' => 'Ҕ',
  'җ' => 'Җ',
  'ҙ' => 'Ҙ',
  'қ' => 'Қ',
  'ҝ' => 'Ҝ',
  'ҟ' => 'Ҟ',
  'ҡ' => 'Ҡ',
  'ң' => 'Ң',
  'ҥ' => 'Ҥ',
  'ҧ' => 'Ҧ',
  'ҩ' => 'Ҩ',
  'ҫ' => 'Ҫ',
  'ҭ' => 'Ҭ',
  'ү' => 'Ү',
  'ұ' => 'Ұ',
  'ҳ' => 'Ҳ',
  'ҵ' => 'Ҵ',
  'ҷ' => 'Ҷ',
  'ҹ' => 'Ҹ',
  'һ' => 'Һ',
  'ҽ' => 'Ҽ',
  'ҿ' => 'Ҿ',
  'ӂ' => 'Ӂ',
  'ӄ' => 'Ӄ',
  'ӆ' => 'Ӆ',
  'ӈ' => 'Ӈ',
  'ӊ' => 'Ӊ',
  'ӌ' => 'Ӌ',
  'ӎ' => 'Ӎ',
  'ӏ' => 'Ӏ',
  'ӑ' => 'Ӑ',
  'ӓ' => 'Ӓ',
  'ӕ' => 'Ӕ',
  'ӗ' => 'Ӗ',
  'ә' => 'Ә',
  'ӛ' => 'Ӛ',
  'ӝ' => 'Ӝ',
  'ӟ' => 'Ӟ',
  'ӡ' => 'Ӡ',
  'ӣ' => 'Ӣ',
  'ӥ' => 'Ӥ',
  'ӧ' => 'Ӧ',
  'ө' => 'Ө',
  'ӫ' => 'Ӫ',
  'ӭ' => 'Ӭ',
  'ӯ' => 'Ӯ',
  'ӱ' => 'Ӱ',
  'ӳ' => 'Ӳ',
  'ӵ' => 'Ӵ',
  'ӷ' => 'Ӷ',
  'ӹ' => 'Ӹ',
  'ӻ' => 'Ӻ',
  'ӽ' => 'Ӽ',
  'ӿ' => 'Ӿ',
  'ԁ' => 'Ԁ',
  'ԃ' => 'Ԃ',
  'ԅ' => 'Ԅ',
  'ԇ' => 'Ԇ',
  'ԉ' => 'Ԉ',
  'ԋ' => 'Ԋ',
  'ԍ' => 'Ԍ',
  'ԏ' => 'Ԏ',
  'ԑ' => 'Ԑ',
  'ԓ' => 'Ԓ',
  'ԕ' => 'Ԕ',
  'ԗ' => 'Ԗ',
  'ԙ' => 'Ԙ',
  'ԛ' => 'Ԛ',
  'ԝ' => 'Ԝ',
  'ԟ' => 'Ԟ',
  'ԡ' => 'Ԡ',
  'ԣ' => 'Ԣ',
  'ԥ' => 'Ԥ',
  'ԧ' => 'Ԧ',
  'ԩ' => 'Ԩ',
  'ԫ' => 'Ԫ',
  'ԭ' => 'Ԭ',
  'ԯ' => 'Ԯ',
  'ա' => 'Ա',
  'բ' => 'Բ',
  'գ' => 'Գ',
  'դ' => 'Դ',
  'ե' => 'Ե',
  'զ' => 'Զ',
  'է' => 'Է',
  'ը' => 'Ը',
  'թ' => 'Թ',
  'ժ' => 'Ժ',
  'ի' => 'Ի',
  'լ' => 'Լ',
  'խ' => 'Խ',
  'ծ' => 'Ծ',
  'կ' => 'Կ',
  'հ' => 'Հ',
  'ձ' => 'Ձ',
  'ղ' => 'Ղ',
  'ճ' => 'Ճ',
  'մ' => 'Մ',
  'յ' => 'Յ',
  'ն' => 'Ն',
  'շ' => 'Շ',
  'ո' => 'Ո',
  'չ' => 'Չ',
  'պ' => 'Պ',
  'ջ' => 'Ջ',
  'ռ' => 'Ռ',
  'ս' => 'Ս',
  'վ' => 'Վ',
  'տ' => 'Տ',
  'ր' => 'Ր',
  'ց' => 'Ց',
  'ւ' => 'Ւ',
  'փ' => 'Փ',
  'ք' => 'Ք',
  'օ' => 'Օ',
  'ֆ' => 'Ֆ',
  'ᵹ' => 'Ᵹ',
  'ᵽ' => 'Ᵽ',
  'ḁ' => 'Ḁ',
  'ḃ' => 'Ḃ',
  'ḅ' => 'Ḅ',
  'ḇ' => 'Ḇ',
  'ḉ' => 'Ḉ',
  'ḋ' => 'Ḋ',
  'ḍ' => 'Ḍ',
  'ḏ' => 'Ḏ',
  'ḑ' => 'Ḑ',
  'ḓ' => 'Ḓ',
  'ḕ' => 'Ḕ',
  'ḗ' => 'Ḗ',
  'ḙ' => 'Ḙ',
  'ḛ' => 'Ḛ',
  'ḝ' => 'Ḝ',
  'ḟ' => 'Ḟ',
  'ḡ' => 'Ḡ',
  'ḣ' => 'Ḣ',
  'ḥ' => 'Ḥ',
  'ḧ' => 'Ḧ',
  'ḩ' => 'Ḩ',
  'ḫ' => 'Ḫ',
  'ḭ' => 'Ḭ',
  'ḯ' => 'Ḯ',
  'ḱ' => 'Ḱ',
  'ḳ' => 'Ḳ',
  'ḵ' => 'Ḵ',
  'ḷ' => 'Ḷ',
  'ḹ' => 'Ḹ',
  'ḻ' => 'Ḻ',
  'ḽ' => 'Ḽ',
  'ḿ' => 'Ḿ',
  'ṁ' => 'Ṁ',
  'ṃ' => 'Ṃ',
  'ṅ' => 'Ṅ',
  'ṇ' => 'Ṇ',
  'ṉ' => 'Ṉ',
  'ṋ' => 'Ṋ',
  'ṍ' => 'Ṍ',
  'ṏ' => 'Ṏ',
  'ṑ' => 'Ṑ',
  'ṓ' => 'Ṓ',
  'ṕ' => 'Ṕ',
  'ṗ' => 'Ṗ',
  'ṙ' => 'Ṙ',
  'ṛ' => 'Ṛ',
  'ṝ' => 'Ṝ',
  'ṟ' => 'Ṟ',
  'ṡ' => 'Ṡ',
  'ṣ' => 'Ṣ',
  'ṥ' => 'Ṥ',
  'ṧ' => 'Ṧ',
  'ṩ' => 'Ṩ',
  'ṫ' => 'Ṫ',
  'ṭ' => 'Ṭ',
  'ṯ' => 'Ṯ',
  'ṱ' => 'Ṱ',
  'ṳ' => 'Ṳ',
  'ṵ' => 'Ṵ',
  'ṷ' => 'Ṷ',
  'ṹ' => 'Ṹ',
  'ṻ' => 'Ṻ',
  'ṽ' => 'Ṽ',
  'ṿ' => 'Ṿ',
  'ẁ' => 'Ẁ',
  'ẃ' => 'Ẃ',
  'ẅ' => 'Ẅ',
  'ẇ' => 'Ẇ',
  'ẉ' => 'Ẉ',
  'ẋ' => 'Ẋ',
  'ẍ' => 'Ẍ',
  'ẏ' => 'Ẏ',
  'ẑ' => 'Ẑ',
  'ẓ' => 'Ẓ',
  'ẕ' => 'Ẕ',
  'ẛ' => 'Ṡ',
  'ạ' => 'Ạ',
  'ả' => 'Ả',
  'ấ' => 'Ấ',
  'ầ' => 'Ầ',
  'ẩ' => 'Ẩ',
  'ẫ' => 'Ẫ',
  'ậ' => 'Ậ',
  'ắ' => 'Ắ',
  'ằ' => 'Ằ',
  'ẳ' => 'Ẳ',
  'ẵ' => 'Ẵ',
  'ặ' => 'Ặ',
  'ẹ' => 'Ẹ',
  'ẻ' => 'Ẻ',
  'ẽ' => 'Ẽ',
  'ế' => 'Ế',
  'ề' => 'Ề',
  'ể' => 'Ể',
  'ễ' => 'Ễ',
  'ệ' => 'Ệ',
  'ỉ' => 'Ỉ',
  'ị' => 'Ị',
  'ọ' => 'Ọ',
  'ỏ' => 'Ỏ',
  'ố' => 'Ố',
  'ồ' => 'Ồ',
  'ổ' => 'Ổ',
  'ỗ' => 'Ỗ',
  'ộ' => 'Ộ',
  'ớ' => 'Ớ',
  'ờ' => 'Ờ',
  'ở' => 'Ở',
  'ỡ' => 'Ỡ',
  'ợ' => 'Ợ',
  'ụ' => 'Ụ',
  'ủ' => 'Ủ',
  'ứ' => 'Ứ',
  'ừ' => 'Ừ',
  'ử' => 'Ử',
  'ữ' => 'Ữ',
  'ự' => 'Ự',
  'ỳ' => 'Ỳ',
  'ỵ' => 'Ỵ',
  'ỷ' => 'Ỷ',
  'ỹ' => 'Ỹ',
  'ỻ' => 'Ỻ',
  'ỽ' => 'Ỽ',
  'ỿ' => 'Ỿ',
  'ἀ' => 'Ἀ',
  'ἁ' => 'Ἁ',
  'ἂ' => 'Ἂ',
  'ἃ' => 'Ἃ',
  'ἄ' => 'Ἄ',
  'ἅ' => 'Ἅ',
  'ἆ' => 'Ἆ',
  'ἇ' => 'Ἇ',
  'ἐ' => 'Ἐ',
  'ἑ' => 'Ἑ',
  'ἒ' => 'Ἒ',
  'ἓ' => 'Ἓ',
  'ἔ' => 'Ἔ',
  'ἕ' => 'Ἕ',
  'ἠ' => 'Ἠ',
  'ἡ' => 'Ἡ',
  'ἢ' => 'Ἢ',
  'ἣ' => 'Ἣ',
  'ἤ' => 'Ἤ',
  'ἥ' => 'Ἥ',
  'ἦ' => 'Ἦ',
  'ἧ' => 'Ἧ',
  'ἰ' => 'Ἰ',
  'ἱ' => 'Ἱ',
  'ἲ' => 'Ἲ',
  'ἳ' => 'Ἳ',
  'ἴ' => 'Ἴ',
  'ἵ' => 'Ἵ',
  'ἶ' => 'Ἶ',
  'ἷ' => 'Ἷ',
  'ὀ' => 'Ὀ',
  'ὁ' => 'Ὁ',
  'ὂ' => 'Ὂ',
  'ὃ' => 'Ὃ',
  'ὄ' => 'Ὄ',
  'ὅ' => 'Ὅ',
  'ὑ' => 'Ὑ',
  'ὓ' => 'Ὓ',
  'ὕ' => 'Ὕ',
  'ὗ' => 'Ὗ',
  'ὠ' => 'Ὠ',
  'ὡ' => 'Ὡ',
  'ὢ' => 'Ὢ',
  'ὣ' => 'Ὣ',
  'ὤ' => 'Ὤ',
  'ὥ' => 'Ὥ',
  'ὦ' => 'Ὦ',
  'ὧ' => 'Ὧ',
  'ὰ' => 'Ὰ',
  'ά' => 'Ά',
  'ὲ' => 'Ὲ',
  'έ' => 'Έ',
  'ὴ' => 'Ὴ',
  'ή' => 'Ή',
  'ὶ' => 'Ὶ',
  'ί' => 'Ί',
  'ὸ' => 'Ὸ',
  'ό' => 'Ό',
  'ὺ' => 'Ὺ',
  'ύ' => 'Ύ',
  'ὼ' => 'Ὼ',
  'ώ' => 'Ώ',
  'ᾀ' => 'ᾈ',
  'ᾁ' => 'ᾉ',
  'ᾂ' => 'ᾊ',
  'ᾃ' => 'ᾋ',
  'ᾄ' => 'ᾌ',
  'ᾅ' => 'ᾍ',
  'ᾆ' => 'ᾎ',
  'ᾇ' => 'ᾏ',
  'ᾐ' => 'ᾘ',
  'ᾑ' => 'ᾙ',
  'ᾒ' => 'ᾚ',
  'ᾓ' => 'ᾛ',
  'ᾔ' => 'ᾜ',
  'ᾕ' => 'ᾝ',
  'ᾖ' => 'ᾞ',
  'ᾗ' => 'ᾟ',
  'ᾠ' => 'ᾨ',
  'ᾡ' => 'ᾩ',
  'ᾢ' => 'ᾪ',
  'ᾣ' => 'ᾫ',
  'ᾤ' => 'ᾬ',
  'ᾥ' => 'ᾭ',
  'ᾦ' => 'ᾮ',
  'ᾧ' => 'ᾯ',
  'ᾰ' => 'Ᾰ',
  'ᾱ' => 'Ᾱ',
  'ᾳ' => 'ᾼ',
  'ι' => 'Ι',
  'ῃ' => 'ῌ',
  'ῐ' => 'Ῐ',
  'ῑ' => 'Ῑ',
  'ῠ' => 'Ῠ',
  'ῡ' => 'Ῡ',
  'ῥ' => 'Ῥ',
  'ῳ' => 'ῼ',
  'ⅎ' => 'Ⅎ',
  'ⅰ' => 'Ⅰ',
  'ⅱ' => 'Ⅱ',
  'ⅲ' => 'Ⅲ',
  'ⅳ' => 'Ⅳ',
  'ⅴ' => 'Ⅴ',
  'ⅵ' => 'Ⅵ',
  'ⅶ' => 'Ⅶ',
  'ⅷ' => 'Ⅷ',
  'ⅸ' => 'Ⅸ',
  'ⅹ' => 'Ⅹ',
  'ⅺ' => 'Ⅺ',
  'ⅻ' => 'Ⅻ',
  'ⅼ' => 'Ⅼ',
  'ⅽ' => 'Ⅽ',
  'ⅾ' => 'Ⅾ',
  'ⅿ' => 'Ⅿ',
  'ↄ' => 'Ↄ',
  'ⓐ' => 'Ⓐ',
  'ⓑ' => 'Ⓑ',
  'ⓒ' => 'Ⓒ',
  'ⓓ' => 'Ⓓ',
  'ⓔ' => 'Ⓔ',
  'ⓕ' => 'Ⓕ',
  'ⓖ' => 'Ⓖ',
  'ⓗ' => 'Ⓗ',
  'ⓘ' => 'Ⓘ',
  'ⓙ' => 'Ⓙ',
  'ⓚ' => 'Ⓚ',
  'ⓛ' => 'Ⓛ',
  'ⓜ' => 'Ⓜ',
  'ⓝ' => 'Ⓝ',
  'ⓞ' => 'Ⓞ',
  'ⓟ' => 'Ⓟ',
  'ⓠ' => 'Ⓠ',
  'ⓡ' => 'Ⓡ',
  'ⓢ' => 'Ⓢ',
  'ⓣ' => 'Ⓣ',
  'ⓤ' => 'Ⓤ',
  'ⓥ' => 'Ⓥ',
  'ⓦ' => 'Ⓦ',
  'ⓧ' => 'Ⓧ',
  'ⓨ' => 'Ⓨ',
  'ⓩ' => 'Ⓩ',
  'ⰰ' => 'Ⰰ',
  'ⰱ' => 'Ⰱ',
  'ⰲ' => 'Ⰲ',
  'ⰳ' => 'Ⰳ',
  'ⰴ' => 'Ⰴ',
  'ⰵ' => 'Ⰵ',
  'ⰶ' => 'Ⰶ',
  'ⰷ' => 'Ⰷ',
  'ⰸ' => 'Ⰸ',
  'ⰹ' => 'Ⰹ',
  'ⰺ' => 'Ⰺ',
  'ⰻ' => 'Ⰻ',
  'ⰼ' => 'Ⰼ',
  'ⰽ' => 'Ⰽ',
  'ⰾ' => 'Ⰾ',
  'ⰿ' => 'Ⰿ',
  'ⱀ' => 'Ⱀ',
  'ⱁ' => 'Ⱁ',
  'ⱂ' => 'Ⱂ',
  'ⱃ' => 'Ⱃ',
  'ⱄ' => 'Ⱄ',
  'ⱅ' => 'Ⱅ',
  'ⱆ' => 'Ⱆ',
  'ⱇ' => 'Ⱇ',
  'ⱈ' => 'Ⱈ',
  'ⱉ' => 'Ⱉ',
  'ⱊ' => 'Ⱊ',
  'ⱋ' => 'Ⱋ',
  'ⱌ' => 'Ⱌ',
  'ⱍ' => 'Ⱍ',
  'ⱎ' => 'Ⱎ',
  'ⱏ' => 'Ⱏ',
  'ⱐ' => 'Ⱐ',
  'ⱑ' => 'Ⱑ',
  'ⱒ' => 'Ⱒ',
  'ⱓ' => 'Ⱓ',
  'ⱔ' => 'Ⱔ',
  'ⱕ' => 'Ⱕ',
  'ⱖ' => 'Ⱖ',
  'ⱗ' => 'Ⱗ',
  'ⱘ' => 'Ⱘ',
  'ⱙ' => 'Ⱙ',
  'ⱚ' => 'Ⱚ',
  'ⱛ' => 'Ⱛ',
  'ⱜ' => 'Ⱜ',
  'ⱝ' => 'Ⱝ',
  'ⱞ' => 'Ⱞ',
  'ⱡ' => 'Ⱡ',
  'ⱥ' => 'Ⱥ',
  'ⱦ' => 'Ⱦ',
  'ⱨ' => 'Ⱨ',
  'ⱪ' => 'Ⱪ',
  'ⱬ' => 'Ⱬ',
  'ⱳ' => 'Ⱳ',
  'ⱶ' => 'Ⱶ',
  'ⲁ' => 'Ⲁ',
  'ⲃ' => 'Ⲃ',
  'ⲅ' => 'Ⲅ',
  'ⲇ' => 'Ⲇ',
  'ⲉ' => 'Ⲉ',
  'ⲋ' => 'Ⲋ',
  'ⲍ' => 'Ⲍ',
  'ⲏ' => 'Ⲏ',
  'ⲑ' => 'Ⲑ',
  'ⲓ' => 'Ⲓ',
  'ⲕ' => 'Ⲕ',
  'ⲗ' => 'Ⲗ',
  'ⲙ' => 'Ⲙ',
  'ⲛ' => 'Ⲛ',
  'ⲝ' => 'Ⲝ',
  'ⲟ' => 'Ⲟ',
  'ⲡ' => 'Ⲡ',
  'ⲣ' => 'Ⲣ',
  'ⲥ' => 'Ⲥ',
  'ⲧ' => 'Ⲧ',
  'ⲩ' => 'Ⲩ',
  'ⲫ' => 'Ⲫ',
  'ⲭ' => 'Ⲭ',
  'ⲯ' => 'Ⲯ',
  'ⲱ' => 'Ⲱ',
  'ⲳ' => 'Ⲳ',
  'ⲵ' => 'Ⲵ',
  'ⲷ' => 'Ⲷ',
  'ⲹ' => 'Ⲹ',
  'ⲻ' => 'Ⲻ',
  'ⲽ' => 'Ⲽ',
  'ⲿ' => 'Ⲿ',
  'ⳁ' => 'Ⳁ',
  'ⳃ' => 'Ⳃ',
  'ⳅ' => 'Ⳅ',
  'ⳇ' => 'Ⳇ',
  'ⳉ' => 'Ⳉ',
  'ⳋ' => 'Ⳋ',
  'ⳍ' => 'Ⳍ',
  'ⳏ' => 'Ⳏ',
  'ⳑ' => 'Ⳑ',
  'ⳓ' => 'Ⳓ',
  'ⳕ' => 'Ⳕ',
  'ⳗ' => 'Ⳗ',
  'ⳙ' => 'Ⳙ',
  'ⳛ' => 'Ⳛ',
  'ⳝ' => 'Ⳝ',
  'ⳟ' => 'Ⳟ',
  'ⳡ' => 'Ⳡ',
  'ⳣ' => 'Ⳣ',
  'ⳬ' => 'Ⳬ',
  'ⳮ' => 'Ⳮ',
  'ⳳ' => 'Ⳳ',
  'ⴀ' => 'Ⴀ',
  'ⴁ' => 'Ⴁ',
  'ⴂ' => 'Ⴂ',
  'ⴃ' => 'Ⴃ',
  'ⴄ' => 'Ⴄ',
  'ⴅ' => 'Ⴅ',
  'ⴆ' => 'Ⴆ',
  'ⴇ' => 'Ⴇ',
  'ⴈ' => 'Ⴈ',
  'ⴉ' => 'Ⴉ',
  'ⴊ' => 'Ⴊ',
  'ⴋ' => 'Ⴋ',
  'ⴌ' => 'Ⴌ',
  'ⴍ' => 'Ⴍ',
  'ⴎ' => 'Ⴎ',
  'ⴏ' => 'Ⴏ',
  'ⴐ' => 'Ⴐ',
  'ⴑ' => 'Ⴑ',
  'ⴒ' => 'Ⴒ',
  'ⴓ' => 'Ⴓ',
  'ⴔ' => 'Ⴔ',
  'ⴕ' => 'Ⴕ',
  'ⴖ' => 'Ⴖ',
  'ⴗ' => 'Ⴗ',
  'ⴘ' => 'Ⴘ',
  'ⴙ' => 'Ⴙ',
  'ⴚ' => 'Ⴚ',
  'ⴛ' => 'Ⴛ',
  'ⴜ' => 'Ⴜ',
  'ⴝ' => 'Ⴝ',
  'ⴞ' => 'Ⴞ',
  'ⴟ' => 'Ⴟ',
  'ⴠ' => 'Ⴠ',
  'ⴡ' => 'Ⴡ',
  'ⴢ' => 'Ⴢ',
  'ⴣ' => 'Ⴣ',
  'ⴤ' => 'Ⴤ',
  'ⴥ' => 'Ⴥ',
  'ⴧ' => 'Ⴧ',
  'ⴭ' => 'Ⴭ',
  'ꙁ' => 'Ꙁ',
  'ꙃ' => 'Ꙃ',
  'ꙅ' => 'Ꙅ',
  'ꙇ' => 'Ꙇ',
  'ꙉ' => 'Ꙉ',
  'ꙋ' => 'Ꙋ',
  'ꙍ' => 'Ꙍ',
  'ꙏ' => 'Ꙏ',
  'ꙑ' => 'Ꙑ',
  'ꙓ' => 'Ꙓ',
  'ꙕ' => 'Ꙕ',
  'ꙗ' => 'Ꙗ',
  'ꙙ' => 'Ꙙ',
  'ꙛ' => 'Ꙛ',
  'ꙝ' => 'Ꙝ',
  'ꙟ' => 'Ꙟ',
  'ꙡ' => 'Ꙡ',
  'ꙣ' => 'Ꙣ',
  'ꙥ' => 'Ꙥ',
  'ꙧ' => 'Ꙧ',
  'ꙩ' => 'Ꙩ',
  'ꙫ' => 'Ꙫ',
  'ꙭ' => 'Ꙭ',
  'ꚁ' => 'Ꚁ',
  'ꚃ' => 'Ꚃ',
  'ꚅ' => 'Ꚅ',
  'ꚇ' => 'Ꚇ',
  'ꚉ' => 'Ꚉ',
  'ꚋ' => 'Ꚋ',
  'ꚍ' => 'Ꚍ',
  'ꚏ' => 'Ꚏ',
  'ꚑ' => 'Ꚑ',
  'ꚓ' => 'Ꚓ',
  'ꚕ' => 'Ꚕ',
  'ꚗ' => 'Ꚗ',
  'ꚙ' => 'Ꚙ',
  'ꚛ' => 'Ꚛ',
  'ꜣ' => 'Ꜣ',
  'ꜥ' => 'Ꜥ',
  'ꜧ' => 'Ꜧ',
  'ꜩ' => 'Ꜩ',
  'ꜫ' => 'Ꜫ',
  'ꜭ' => 'Ꜭ',
  'ꜯ' => 'Ꜯ',
  'ꜳ' => 'Ꜳ',
  'ꜵ' => 'Ꜵ',
  'ꜷ' => 'Ꜷ',
  'ꜹ' => 'Ꜹ',
  'ꜻ' => 'Ꜻ',
  'ꜽ' => 'Ꜽ',
  'ꜿ' => 'Ꜿ',
  'ꝁ' => 'Ꝁ',
  'ꝃ' => 'Ꝃ',
  'ꝅ' => 'Ꝅ',
  'ꝇ' => 'Ꝇ',
  'ꝉ' => 'Ꝉ',
  'ꝋ' => 'Ꝋ',
  'ꝍ' => 'Ꝍ',
  'ꝏ' => 'Ꝏ',
  'ꝑ' => 'Ꝑ',
  'ꝓ' => 'Ꝓ',
  'ꝕ' => 'Ꝕ',
  'ꝗ' => 'Ꝗ',
  'ꝙ' => 'Ꝙ',
  'ꝛ' => 'Ꝛ',
  'ꝝ' => 'Ꝝ',
  'ꝟ' => 'Ꝟ',
  'ꝡ' => 'Ꝡ',
  'ꝣ' => 'Ꝣ',
  'ꝥ' => 'Ꝥ',
  'ꝧ' => 'Ꝧ',
  'ꝩ' => 'Ꝩ',
  'ꝫ' => 'Ꝫ',
  'ꝭ' => 'Ꝭ',
  'ꝯ' => 'Ꝯ',
  'ꝺ' => 'Ꝺ',
  'ꝼ' => 'Ꝼ',
  'ꝿ' => 'Ꝿ',
  'ꞁ' => 'Ꞁ',
  'ꞃ' => 'Ꞃ',
  'ꞅ' => 'Ꞅ',
  'ꞇ' => 'Ꞇ',
  'ꞌ' => 'Ꞌ',
  'ꞑ' => 'Ꞑ',
  'ꞓ' => 'Ꞓ',
  'ꞗ' => 'Ꞗ',
  'ꞙ' => 'Ꞙ',
  'ꞛ' => 'Ꞛ',
  'ꞝ' => 'Ꞝ',
  'ꞟ' => 'Ꞟ',
  'ꞡ' => 'Ꞡ',
  'ꞣ' => 'Ꞣ',
  'ꞥ' => 'Ꞥ',
  'ꞧ' => 'Ꞧ',
  'ꞩ' => 'Ꞩ',
  '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',
  '𐐨' => '𐐀',
  '𐐩' => '𐐁',
  '𐐪' => '𐐂',
  '𐐫' => '𐐃',
  '𐐬' => '𐐄',
  '𐐭' => '𐐅',
  '𐐮' => '𐐆',
  '𐐯' => '𐐇',
  '𐐰' => '𐐈',
  '𐐱' => '𐐉',
  '𐐲' => '𐐊',
  '𐐳' => '𐐋',
  '𐐴' => '𐐌',
  '𐐵' => '𐐍',
  '𐐶' => '𐐎',
  '𐐷' => '𐐏',
  '𐐸' => '𐐐',
  '𐐹' => '𐐑',
  '𐐺' => '𐐒',
  '𐐻' => '𐐓',
  '𐐼' => '𐐔',
  '𐐽' => '𐐕',
  '𐐾' => '𐐖',
  '𐐿' => '𐐗',
  '𐑀' => '𐐘',
  '𐑁' => '𐐙',
  '𐑂' => '𐐚',
  '𐑃' => '𐐛',
  '𐑄' => '𐐜',
  '𐑅' => '𐐝',
  '𐑆' => '𐐞',
  '𐑇' => '𐐟',
  '𐑈' => '𐐠',
  '𐑉' => '𐐡',
  '𐑊' => '𐐢',
  '𐑋' => '𐐣',
  '𐑌' => '𐐤',
  '𐑍' => '𐐥',
  '𐑎' => '𐐦',
  '𐑏' => '𐐧',
  '𑣀' => '𑢠',
  '𑣁' => '𑢡',
  '𑣂' => '𑢢',
  '𑣃' => '𑢣',
  '𑣄' => '𑢤',
  '𑣅' => '𑢥',
  '𑣆' => '𑢦',
  '𑣇' => '𑢧',
  '𑣈' => '𑢨',
  '𑣉' => '𑢩',
  '𑣊' => '𑢪',
  '𑣋' => '𑢫',
  '𑣌' => '𑢬',
  '𑣍' => '𑢭',
  '𑣎' => '𑢮',
  '𑣏' => '𑢯',
  '𑣐' => '𑢰',
  '𑣑' => '𑢱',
  '𑣒' => '𑢲',
  '𑣓' => '𑢳',
  '𑣔' => '𑢴',
  '𑣕' => '𑢵',
  '𑣖' => '𑢶',
  '𑣗' => '𑢷',
  '𑣘' => '𑢸',
  '𑣙' => '𑢹',
  '𑣚' => '𑢺',
  '𑣛' => '𑢻',
  '𑣜' => '𑢼',
  '𑣝' => '𑢽',
  '𑣞' => '𑢾',
  '𑣟' => '𑢿',
);

$result =& $data;
unset($data);

return $result;
<?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_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); }
}
<?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\Debug;

use Psr\Log\AbstractLogger;

/**
 * A buffering logger that stacks logs for later.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class BufferingLogger extends AbstractLogger
{
    private $logs = array();

    public function log($level, $message, array $context = array())
    {
        $this->logs[] = array($level, $message, $context);
    }

    public function cleanLogs()
    {
        $logs = $this->logs;
        $this->logs = array();

        return $logs;
    }
}
<?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\Debug;

/**
 * Registers all the debug tools.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Debug
{
    private static $enabled = false;

    /**
     * Enables the debug tools.
     *
     * This method registers an error handler and an exception handler.
     *
     * If the Symfony ClassLoader component is available, a special
     * class loader is also registered.
     *
     * @param int  $errorReportingLevel The level of error reporting you want
     * @param bool $displayErrors       Whether to display errors (for development) or just log them (for production)
     */
    public static function enable($errorReportingLevel = null, $displayErrors = true)
    {
        if (static::$enabled) {
            return;
        }

        static::$enabled = true;

        if (null !== $errorReportingLevel) {
            error_reporting($errorReportingLevel);
        } else {
            error_reporting(-1);
        }

        if ('cli' !== PHP_SAPI) {
            ini_set('display_errors', 0);
            ExceptionHandler::register();
        } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
            // CLI - display errors only if they're not already logged to STDERR
            ini_set('display_errors', 1);
        }
        if ($displayErrors) {
            ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
        } else {
            ErrorHandler::register()->throwAt(0, true);
        }

        DebugClassLoader::enable();
    }
}
<?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\Debug;

/**
 * Autoloader checking if the class is really defined in the file found.
 *
 * The ClassLoader will wrap all registered autoloaders
 * and will throw an exception if a file is found but does
 * not declare the class.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christophe Coevoet <stof@notk.org>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class DebugClassLoader
{
    private $classLoader;
    private $isFinder;
    private $loaded = array();
    private $wasFinder;
    private static $caseCheck;
    private static $deprecated = array();
    private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
    private static $darwinCache = array('/' => array('/', array()));

    /**
     * @param callable|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0
     */
    public function __construct($classLoader)
    {
        $this->wasFinder = is_object($classLoader) && method_exists($classLoader, 'findFile');

        if ($this->wasFinder) {
            @trigger_error('The '.__METHOD__.' method will no longer support receiving an object into its $classLoader argument in 3.0.', E_USER_DEPRECATED);
            $this->classLoader = array($classLoader, 'loadClass');
            $this->isFinder = true;
        } else {
            $this->classLoader = $classLoader;
            $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');
        }

        if (!isset(self::$caseCheck)) {
            $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
            $i = strrpos($file, DIRECTORY_SEPARATOR);
            $dir = substr($file, 0, 1 + $i);
            $file = substr($file, 1 + $i);
            $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
            $test = realpath($dir.$test);

            if (false === $test || false === $i) {
                // filesystem is case sensitive
                self::$caseCheck = 0;
            } elseif (substr($test, -strlen($file)) === $file) {
                // filesystem is case insensitive and realpath() normalizes the case of characters
                self::$caseCheck = 1;
            } elseif (false !== stripos(PHP_OS, 'darwin')) {
                // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
                self::$caseCheck = 2;
            } else {
                // filesystem case checks failed, fallback to disabling them
                self::$caseCheck = 0;
            }
        }
    }

    /**
     * Gets the wrapped class loader.
     *
     * @return callable|object A class loader. Since version 2.5, returning an object is @deprecated and support for it will be removed in 3.0
     */
    public function getClassLoader()
    {
        return $this->wasFinder ? $this->classLoader[0] : $this->classLoader;
    }

    /**
     * Wraps all autoloaders.
     */
    public static function enable()
    {
        // Ensures we don't hit https://bugs.php.net/42098
        class_exists('Symfony\Component\Debug\ErrorHandler');
        class_exists('Psr\Log\LogLevel');

        if (!is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (!is_array($function) || !$function[0] instanceof self) {
                $function = array(new static($function), 'loadClass');
            }

            spl_autoload_register($function);
        }
    }

    /**
     * Disables the wrapping.
     */
    public static function disable()
    {
        if (!is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (is_array($function) && $function[0] instanceof self) {
                $function = $function[0]->getClassLoader();
            }

            spl_autoload_register($function);
        }
    }

    /**
     * Finds a file by class name.
     *
     * @param string $class A class name to resolve to file
     *
     * @return string|null
     *
     * @deprecated since version 2.5, to be removed in 3.0.
     */
    public function findFile($class)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);

        if ($this->wasFinder) {
            return $this->classLoader[0]->findFile($class);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param string $class The name of the class
     *
     * @return bool|null True, if loaded
     *
     * @throws \RuntimeException
     */
    public function loadClass($class)
    {
        ErrorHandler::stackErrors();

        try {
            if ($this->isFinder && !isset($this->loaded[$class])) {
                $this->loaded[$class] = true;
                if ($file = $this->classLoader[0]->findFile($class)) {
                    require $file;
                }
            } else {
                call_user_func($this->classLoader, $class);
                $file = false;
            }
        } catch (\Exception $e) {
            ErrorHandler::unstackErrors();

            throw $e;
        } catch (\Throwable $e) {
            ErrorHandler::unstackErrors();

            throw $e;
        }

        ErrorHandler::unstackErrors();

        $exists = class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));

        if ($class && '\\' === $class[0]) {
            $class = substr($class, 1);
        }

        if ($exists) {
            $refl = new \ReflectionClass($class);
            $name = $refl->getName();

            if ($name !== $class && 0 === strcasecmp($name, $class)) {
                throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name));
            }

            if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
                @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
            } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
                self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
            } else {
                if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
                    $len = 0;
                    $ns = '';
                } else {
                    switch ($ns = substr($name, 0, $len)) {
                        case 'Symfony\Bridge\\':
                        case 'Symfony\Bundle\\':
                        case 'Symfony\Component\\':
                            $ns = 'Symfony\\';
                            $len = strlen($ns);
                            break;
                    }
                }
                $parent = get_parent_class($class);

                if (!$parent || strncmp($ns, $parent, $len)) {
                    if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
                        @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
                    }

                    $parentInterfaces = array();
                    $deprecatedInterfaces = array();
                    if ($parent) {
                        foreach (class_implements($parent) as $interface) {
                            $parentInterfaces[$interface] = 1;
                        }
                    }

                    foreach ($refl->getInterfaceNames() as $interface) {
                        if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
                            $deprecatedInterfaces[] = $interface;
                        }
                        foreach (class_implements($interface) as $interface) {
                            $parentInterfaces[$interface] = 1;
                        }
                    }

                    foreach ($deprecatedInterfaces as $interface) {
                        if (!isset($parentInterfaces[$interface])) {
                            @trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
                        }
                    }
                }
            }
        }

        if ($file) {
            if (!$exists) {
                if (false !== strpos($class, '/')) {
                    throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
                }

                throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
            }
            if (self::$caseCheck) {
                $real = explode('\\', $class.strrchr($file, '.'));
                $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));

                $i = count($tail) - 1;
                $j = count($real) - 1;

                while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
                    --$i;
                    --$j;
                }

                array_splice($tail, 0, $i + 1);
            }
            if (self::$caseCheck && $tail) {
                $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
                $tailLen = strlen($tail);
                $real = $refl->getFileName();

                if (2 === self::$caseCheck) {
                    // realpath() on MacOSX doesn't normalize the case of characters

                    $i = 1 + strrpos($real, '/');
                    $file = substr($real, $i);
                    $real = substr($real, 0, $i);

                    if (isset(self::$darwinCache[$real])) {
                        $kDir = $real;
                    } else {
                        $kDir = strtolower($real);

                        if (isset(self::$darwinCache[$kDir])) {
                            $real = self::$darwinCache[$kDir][0];
                        } else {
                            $dir = getcwd();
                            chdir($real);
                            $real = getcwd().'/';
                            chdir($dir);

                            $dir = $real;
                            $k = $kDir;
                            $i = strlen($dir) - 1;
                            while (!isset(self::$darwinCache[$k])) {
                                self::$darwinCache[$k] = array($dir, array());
                                self::$darwinCache[$dir] = &self::$darwinCache[$k];

                                while ('/' !== $dir[--$i]) {
                                }
                                $k = substr($k, 0, ++$i);
                                $dir = substr($dir, 0, $i--);
                            }
                        }
                    }

                    $dirFiles = self::$darwinCache[$kDir][1];

                    if (isset($dirFiles[$file])) {
                        $kFile = $file;
                    } else {
                        $kFile = strtolower($file);

                        if (!isset($dirFiles[$kFile])) {
                            foreach (scandir($real, 2) as $f) {
                                if ('.' !== $f[0]) {
                                    $dirFiles[$f] = $f;
                                    if ($f === $file) {
                                        $kFile = $k = $file;
                                    } elseif ($f !== $k = strtolower($f)) {
                                        $dirFiles[$k] = $f;
                                    }
                                }
                            }
                            self::$darwinCache[$kDir][1] = $dirFiles;
                        }
                    }

                    $real .= $dirFiles[$kFile];
                }

                if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
                  && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
                ) {
                    throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
                }
            }

            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\Debug;

use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;

/**
 * A generic ErrorHandler for the PHP engine.
 *
 * Provides five bit fields that control how errors are handled:
 * - thrownErrors: errors thrown as \ErrorException
 * - loggedErrors: logged errors, when not @-silenced
 * - scopedErrors: errors thrown or logged with their local context
 * - tracedErrors: errors logged with their stack trace, only once for repeated errors
 * - screamedErrors: never @-silenced errors
 *
 * Each error level can be logged by a dedicated PSR-3 logger object.
 * Screaming only applies to logging.
 * Throwing takes precedence over logging.
 * Uncaught exceptions are logged as E_ERROR.
 * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
 * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
 * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
 * As errors have a performance cost, repeated errors are all logged, so that the developer
 * can see them and weight them as more important to fix than others of the same level.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ErrorHandler
{
    /**
     * @deprecated since version 2.6, to be removed in 3.0.
     */
    const TYPE_DEPRECATION = -100;

    private $levels = array(
        E_DEPRECATED => 'Deprecated',
        E_USER_DEPRECATED => 'User Deprecated',
        E_NOTICE => 'Notice',
        E_USER_NOTICE => 'User Notice',
        E_STRICT => 'Runtime Notice',
        E_WARNING => 'Warning',
        E_USER_WARNING => 'User Warning',
        E_COMPILE_WARNING => 'Compile Warning',
        E_CORE_WARNING => 'Core Warning',
        E_USER_ERROR => 'User Error',
        E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
        E_COMPILE_ERROR => 'Compile Error',
        E_PARSE => 'Parse Error',
        E_ERROR => 'Error',
        E_CORE_ERROR => 'Core Error',
    );

    private $loggers = array(
        E_DEPRECATED => array(null, LogLevel::INFO),
        E_USER_DEPRECATED => array(null, LogLevel::INFO),
        E_NOTICE => array(null, LogLevel::WARNING),
        E_USER_NOTICE => array(null, LogLevel::WARNING),
        E_STRICT => array(null, LogLevel::WARNING),
        E_WARNING => array(null, LogLevel::WARNING),
        E_USER_WARNING => array(null, LogLevel::WARNING),
        E_COMPILE_WARNING => array(null, LogLevel::WARNING),
        E_CORE_WARNING => array(null, LogLevel::WARNING),
        E_USER_ERROR => array(null, LogLevel::CRITICAL),
        E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
        E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
        E_PARSE => array(null, LogLevel::CRITICAL),
        E_ERROR => array(null, LogLevel::CRITICAL),
        E_CORE_ERROR => array(null, LogLevel::CRITICAL),
    );

    private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
    private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
    private $loggedErrors = 0;

    private $loggedTraces = array();
    private $isRecursive = 0;
    private $isRoot = false;
    private $exceptionHandler;
    private $bootstrappingLogger;

    private static $reservedMemory;
    private static $stackedErrors = array();
    private static $stackedErrorLevels = array();
    private static $toStringException = null;
    private static $exitCode = 0;

    /**
     * Same init value as thrownErrors.
     *
     * @deprecated since version 2.6, to be removed in 3.0.
     */
    private $displayErrors = 0x1FFF;

    /**
     * Registers the error handler.
     *
     * @param self|null|int $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels
     * @param bool          $replace Whether to replace or not any existing handler
     *
     * @return self The registered error handler
     */
    public static function register($handler = null, $replace = true)
    {
        if (null === self::$reservedMemory) {
            self::$reservedMemory = str_repeat('x', 10240);
            register_shutdown_function(__CLASS__.'::handleFatalError');
        }

        $levels = -1;

        if ($handlerIsNew = !$handler instanceof self) {
            // @deprecated polymorphism, to be removed in 3.0
            if (null !== $handler) {
                $levels = $replace ? $handler : 0;
                $replace = true;
            }
            $handler = new static();
        }

        if (null === $prev = set_error_handler(array($handler, 'handleError'))) {
            restore_error_handler();
            // Specifying the error types earlier would expose us to https://bugs.php.net/63206
            set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
            $handler->isRoot = true;
        }

        if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
            $handler = $prev[0];
            $replace = false;
        }
        if ($replace || !$prev) {
            $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
        } else {
            restore_error_handler();
        }

        $handler->throwAt($levels & $handler->thrownErrors, true);

        return $handler;
    }

    public function __construct(BufferingLogger $bootstrappingLogger = null)
    {
        if ($bootstrappingLogger) {
            $this->bootstrappingLogger = $bootstrappingLogger;
            $this->setDefaultLogger($bootstrappingLogger);
        }
    }

    /**
     * Sets a logger to non assigned errors levels.
     *
     * @param LoggerInterface $logger  A PSR-3 logger to put as default for the given levels
     * @param array|int       $levels  An array map of E_* to LogLevel::* or an integer bit field of E_* constants
     * @param bool            $replace Whether to replace or not any existing logger
     */
    public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
    {
        $loggers = array();

        if (is_array($levels)) {
            foreach ($levels as $type => $logLevel) {
                if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
                    $loggers[$type] = array($logger, $logLevel);
                }
            }
        } else {
            if (null === $levels) {
                $levels = E_ALL | E_STRICT;
            }
            foreach ($this->loggers as $type => $log) {
                if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
                    $log[0] = $logger;
                    $loggers[$type] = $log;
                }
            }
        }

        $this->setLoggers($loggers);
    }

    /**
     * Sets a logger for each error level.
     *
     * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
     *
     * @return array The previous map
     *
     * @throws \InvalidArgumentException
     */
    public function setLoggers(array $loggers)
    {
        $prevLogged = $this->loggedErrors;
        $prev = $this->loggers;
        $flush = array();

        foreach ($loggers as $type => $log) {
            if (!isset($prev[$type])) {
                throw new \InvalidArgumentException('Unknown error type: '.$type);
            }
            if (!is_array($log)) {
                $log = array($log);
            } elseif (!array_key_exists(0, $log)) {
                throw new \InvalidArgumentException('No logger provided');
            }
            if (null === $log[0]) {
                $this->loggedErrors &= ~$type;
            } elseif ($log[0] instanceof LoggerInterface) {
                $this->loggedErrors |= $type;
            } else {
                throw new \InvalidArgumentException('Invalid logger provided');
            }
            $this->loggers[$type] = $log + $prev[$type];

            if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
                $flush[$type] = $type;
            }
        }
        $this->reRegister($prevLogged | $this->thrownErrors);

        if ($flush) {
            foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
                $type = $log[2]['type'];
                if (!isset($flush[$type])) {
                    $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
                } elseif ($this->loggers[$type][0]) {
                    $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
                }
            }
        }

        return $prev;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler A handler that will be called on Exception
     *
     * @return callable|null The previous exception handler
     *
     * @throws \InvalidArgumentException
     */
    public function setExceptionHandler($handler)
    {
        if (null !== $handler && !is_callable($handler)) {
            throw new \LogicException('The exception handler must be a valid PHP callable.');
        }
        $prev = $this->exceptionHandler;
        $this->exceptionHandler = $handler;

        return $prev;
    }

    /**
     * Sets the PHP error levels that throw an exception when a PHP error occurs.
     *
     * @param int  $levels  A bit field of E_* constants for thrown errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function throwAt($levels, $replace = false)
    {
        $prev = $this->thrownErrors;
        $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
        if (!$replace) {
            $this->thrownErrors |= $prev;
        }
        $this->reRegister($prev | $this->loggedErrors);

        // $this->displayErrors is @deprecated since version 2.6
        $this->displayErrors = $this->thrownErrors;

        return $prev;
    }

    /**
     * Sets the PHP error levels for which local variables are preserved.
     *
     * @param int  $levels  A bit field of E_* constants for scoped errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function scopeAt($levels, $replace = false)
    {
        $prev = $this->scopedErrors;
        $this->scopedErrors = (int) $levels;
        if (!$replace) {
            $this->scopedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the PHP error levels for which the stack trace is preserved.
     *
     * @param int  $levels  A bit field of E_* constants for traced errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function traceAt($levels, $replace = false)
    {
        $prev = $this->tracedErrors;
        $this->tracedErrors = (int) $levels;
        if (!$replace) {
            $this->tracedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the error levels where the @-operator is ignored.
     *
     * @param int  $levels  A bit field of E_* constants for screamed errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function screamAt($levels, $replace = false)
    {
        $prev = $this->screamedErrors;
        $this->screamedErrors = (int) $levels;
        if (!$replace) {
            $this->screamedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Re-registers as a PHP error handler if levels changed.
     */
    private function reRegister($prev)
    {
        if ($prev !== $this->thrownErrors | $this->loggedErrors) {
            $handler = set_error_handler('var_dump');
            $handler = is_array($handler) ? $handler[0] : null;
            restore_error_handler();
            if ($handler === $this) {
                restore_error_handler();
                if ($this->isRoot) {
                    set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
                } else {
                    set_error_handler(array($this, 'handleError'));
                }
            }
        }
    }

    /**
     * Handles errors by filtering then logging them according to the configured bit fields.
     *
     * @param int    $type    One of the E_* constants
     * @param string $message
     * @param string $file
     * @param int    $line
     *
     * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
     *
     * @throws \ErrorException When $this->thrownErrors requests so
     *
     * @internal
     */
    public function handleError($type, $message, $file, $line)
    {
        $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
        $log = $this->loggedErrors & $type;
        $throw = $this->thrownErrors & $type & $level;
        $type &= $level | $this->screamedErrors;

        if (!$type || (!$log && !$throw)) {
            return $type && $log;
        }
        $scope = $this->scopedErrors & $type;

        if (4 < $numArgs = func_num_args()) {
            $context = $scope ? (func_get_arg(4) ?: array()) : array();
            $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
        } else {
            $context = array();
            $backtrace = null;
        }

        if (isset($context['GLOBALS']) && $scope) {
            $e = $context;                  // Whatever the signature of the method,
            unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
            $context = $e;
        }

        if (null !== $backtrace && $type & E_ERROR) {
            // E_ERROR fatal errors are triggered on HHVM when
            // hhvm.error_handling.call_user_handler_on_fatals=1
            // which is the way to get their backtrace.
            $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));

            return true;
        }

        if ($throw) {
            if (null !== self::$toStringException) {
                $throw = self::$toStringException;
                self::$toStringException = null;
            } elseif ($scope && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
                // Checking for class existence is a work around for https://bugs.php.net/42098
                $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
            } else {
                $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
            }

            if (\PHP_VERSION_ID <= 50407 && (\PHP_VERSION_ID >= 50400 || \PHP_VERSION_ID <= 50317)) {
                // Exceptions thrown from error handlers are sometimes not caught by the exception
                // handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
                // We temporarily re-enable display_errors to prevent any blank page related to this bug.

                $throw->errorHandlerCanary = new ErrorHandlerCanary();
            }

            if (E_USER_ERROR & $type) {
                $backtrace = $backtrace ?: $throw->getTrace();

                for ($i = 1; isset($backtrace[$i]); ++$i) {
                    if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
                        && '__toString' === $backtrace[$i]['function']
                        && '->' === $backtrace[$i]['type']
                        && !isset($backtrace[$i - 1]['class'])
                        && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
                    ) {
                        // Here, we know trigger_error() has been called from __toString().
                        // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
                        // A small convention allows working around the limitation:
                        // given a caught $e exception in __toString(), quitting the method with
                        // `return trigger_error($e, E_USER_ERROR);` allows this error handler
                        // to make $e get through the __toString() barrier.

                        foreach ($context as $e) {
                            if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
                                if (1 === $i) {
                                    // On HHVM
                                    $throw = $e;
                                    break;
                                }
                                self::$toStringException = $e;

                                return true;
                            }
                        }

                        if (1 < $i) {
                            // On PHP (not on HHVM), display the original error message instead of the default one.
                            $this->handleException($throw);

                            // Stop the process by giving back the error to the native handler.
                            return false;
                        }
                    }
                }
            }

            throw $throw;
        }

        // For duplicated errors, log the trace only once
        $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
        $trace = true;

        if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
            $trace = false;
        } else {
            $this->loggedTraces[$e] = 1;
        }

        $e = compact('type', 'file', 'line', 'level');

        if ($type & $level) {
            if ($scope) {
                $e['scope_vars'] = $context;
                if ($trace) {
                    $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
                }
            } elseif ($trace) {
                if (null === $backtrace) {
                    $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
                } else {
                    foreach ($backtrace as &$frame) {
                        unset($frame['args'], $frame);
                    }
                    $e['stack'] = $backtrace;
                }
            }
        }

        if ($this->isRecursive) {
            $log = 0;
        } elseif (self::$stackedErrorLevels) {
            self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
        } else {
            try {
                $this->isRecursive = true;
                $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
                $this->isRecursive = false;
            } catch (\Exception $e) {
                $this->isRecursive = false;

                throw $e;
            } catch (\Throwable $e) {
                $this->isRecursive = false;

                throw $e;
            }
        }

        return $type && $log;
    }

    /**
     * Handles an exception by logging then forwarding it to another handler.
     *
     * @param \Exception|\Throwable $exception An exception to handle
     * @param array                 $error     An array as returned by error_get_last()
     *
     * @internal
     */
    public function handleException($exception, array $error = null)
    {
        if (null === $error) {
            self::$exitCode = 255;
        }
        if (!$exception instanceof \Exception) {
            $exception = new FatalThrowableError($exception);
        }
        $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;

        if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
            $e = array(
                'type' => $type,
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'level' => error_reporting(),
                'stack' => $exception->getTrace(),
            );
            if ($exception instanceof FatalErrorException) {
                if ($exception instanceof FatalThrowableError) {
                    $error = array(
                        'type' => $type,
                        'message' => $message = $exception->getMessage(),
                        'file' => $e['file'],
                        'line' => $e['line'],
                    );
                } else {
                    $message = 'Fatal '.$exception->getMessage();
                }
            } elseif ($exception instanceof \ErrorException) {
                $message = 'Uncaught '.$exception->getMessage();
                if ($exception instanceof ContextErrorException) {
                    $e['context'] = $exception->getContext();
                }
            } else {
                $message = 'Uncaught Exception: '.$exception->getMessage();
            }
        }
        if ($this->loggedErrors & $type) {
            try {
                $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
            } catch (\Exception $handlerException) {
            } catch (\Throwable $handlerException) {
            }
        }
        if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
            foreach ($this->getFatalErrorHandlers() as $handler) {
                if ($e = $handler->handleError($error, $exception)) {
                    $exception = $e;
                    break;
                }
            }
        }
        if (empty($this->exceptionHandler)) {
            throw $exception; // Give back $exception to the native handler
        }
        try {
            call_user_func($this->exceptionHandler, $exception);
        } catch (\Exception $handlerException) {
        } catch (\Throwable $handlerException) {
        }
        if (isset($handlerException)) {
            $this->exceptionHandler = null;
            $this->handleException($handlerException);
        }
    }

    /**
     * Shutdown registered function for handling PHP fatal errors.
     *
     * @param array $error An array as returned by error_get_last()
     *
     * @internal
     */
    public static function handleFatalError(array $error = null)
    {
        if (null === self::$reservedMemory) {
            return;
        }

        self::$reservedMemory = null;

        $handler = set_error_handler('var_dump');
        $handler = is_array($handler) ? $handler[0] : null;
        restore_error_handler();

        if (!$handler instanceof self) {
            return;
        }

        if ($exit = null === $error) {
            $error = error_get_last();
        }

        try {
            while (self::$stackedErrorLevels) {
                static::unstackErrors();
            }
        } catch (\Exception $exception) {
            // Handled below
        } catch (\Throwable $exception) {
            // Handled below
        }

        if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
            // Let's not throw anymore but keep logging
            $handler->throwAt(0, true);
            $trace = isset($error['backtrace']) ? $error['backtrace'] : null;

            if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
                $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
            } else {
                $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
            }
        }

        try {
            if (isset($exception)) {
                self::$exitCode = 255;
                $handler->handleException($exception, $error);
            }
        } catch (FatalErrorException $e) {
            // Ignore this re-throw
        }

        if ($exit && self::$exitCode) {
            $exitCode = self::$exitCode;
            register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
        }
    }

    /**
     * Configures the error handler for delayed handling.
     * Ensures also that non-catchable fatal errors are never silenced.
     *
     * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
     * PHP has a compile stage where it behaves unusually. To workaround it,
     * we plug an error handler that only stacks errors for later.
     *
     * The most important feature of this is to prevent
     * autoloading until unstackErrors() is called.
     */
    public static function stackErrors()
    {
        self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
    }

    /**
     * Unstacks stacked errors and forwards to the logger.
     */
    public static function unstackErrors()
    {
        $level = array_pop(self::$stackedErrorLevels);

        if (null !== $level) {
            $e = error_reporting($level);
            if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
                // If the user changed the error level, do not overwrite it
                error_reporting($e);
            }
        }

        if (empty(self::$stackedErrorLevels)) {
            $errors = self::$stackedErrors;
            self::$stackedErrors = array();

            foreach ($errors as $e) {
                $e[0]->log($e[1], $e[2], $e[3]);
            }
        }
    }

    /**
     * Gets the fatal error handlers.
     *
     * Override this method if you want to define more fatal error handlers.
     *
     * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
     */
    protected function getFatalErrorHandlers()
    {
        return array(
            new UndefinedFunctionFatalErrorHandler(),
            new UndefinedMethodFatalErrorHandler(),
            new ClassNotFoundFatalErrorHandler(),
        );
    }

    /**
     * Sets the level at which the conversion to Exception is done.
     *
     * @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
     */
    public function setLevel($level)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);

        $level = null === $level ? error_reporting() : $level;
        $this->throwAt($level, true);
    }

    /**
     * Sets the display_errors flag value.
     *
     * @param int $displayErrors The display_errors flag value
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
     */
    public function setDisplayErrors($displayErrors)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);

        if ($displayErrors) {
            $this->throwAt($this->displayErrors, true);
        } else {
            $displayErrors = $this->displayErrors;
            $this->throwAt(0, true);
            $this->displayErrors = $displayErrors;
        }
    }

    /**
     * Sets a logger for the given channel.
     *
     * @param LoggerInterface $logger  A logger interface
     * @param string          $channel The channel associated with the logger (deprecation, emergency or scream)
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
     */
    public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
    {
        @trigger_error('The '.__METHOD__.' static method is deprecated since version 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED);

        $handler = set_error_handler('var_dump');
        $handler = is_array($handler) ? $handler[0] : null;
        restore_error_handler();
        if (!$handler instanceof self) {
            return;
        }
        if ('deprecation' === $channel) {
            $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
            $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
        } elseif ('scream' === $channel) {
            $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
            $handler->screamAt(E_ALL | E_STRICT);
        } elseif ('emergency' === $channel) {
            $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
            $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
        }
    }

    /**
     * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead.
     */
    public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
    {
        $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array());

        return $this->handleError($level, $message, $file, $line, (array) $context);
    }

    /**
     * Handles PHP fatal errors.
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead.
     */
    public function handleFatal()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED);

        static::handleFatalError();
    }
}

/**
 * Private class used to work around https://bugs.php.net/54275.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
class ErrorHandlerCanary
{
    private static $displayErrors = null;

    public function __construct()
    {
        if (null === self::$displayErrors) {
            self::$displayErrors = ini_set('display_errors', 1);
        }
    }

    public function __destruct()
    {
        if (null !== self::$displayErrors) {
            ini_set('display_errors', self::$displayErrors);
            self::$displayErrors = 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\Debug\Exception;

/**
 * Class (or Trait or Interface) Not Found Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class ClassNotFoundException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?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\Debug\Exception;

/**
 * Error Exception with Variable Context.
 *
 * @author Christian Sciberras <uuf6429@gmail.com>
 */
class ContextErrorException extends \ErrorException
{
    private $context = array();

    public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
    {
        parent::__construct($message, $code, $severity, $filename, $lineno);
        $this->context = $context;
    }

    /**
     * @return array Array of variables that existed when the exception occurred
     */
    public function getContext()
    {
        return $this->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\Component\Debug\Exception;

@trigger_error('The '.__NAMESPACE__.'\DummyException class is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0.
 */
class DummyException extends \ErrorException
{
}
<?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\HttpKernel\Exception;

/**
 * Fatal Error Exception.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
 */
class FatalErrorException extends \ErrorException
{
}

namespace Symfony\Component\Debug\Exception;

use Symfony\Component\HttpKernel\Exception\FatalErrorException as LegacyFatalErrorException;

/**
 * Fatal Error Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class FatalErrorException extends LegacyFatalErrorException
{
    public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
    {
        parent::__construct($message, $code, $severity, $filename, $lineno);

        if (null !== $trace) {
            if (!$traceArgs) {
                foreach ($trace as &$frame) {
                    unset($frame['args'], $frame['this'], $frame);
                }
            }

            $this->setTrace($trace);
        } elseif (null !== $traceOffset) {
            if (function_exists('xdebug_get_function_stack')) {
                $trace = xdebug_get_function_stack();
                if (0 < $traceOffset) {
                    array_splice($trace, -$traceOffset);
                }

                foreach ($trace as &$frame) {
                    if (!isset($frame['type'])) {
                        // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
                        if (isset($frame['class'])) {
                            $frame['type'] = '::';
                        }
                    } elseif ('dynamic' === $frame['type']) {
                        $frame['type'] = '->';
                    } elseif ('static' === $frame['type']) {
                        $frame['type'] = '::';
                    }

                    // XDebug also has a different name for the parameters array
                    if (!$traceArgs) {
                        unset($frame['params'], $frame['args']);
                    } elseif (isset($frame['params']) && !isset($frame['args'])) {
                        $frame['args'] = $frame['params'];
                        unset($frame['params']);
                    }
                }

                unset($frame);
                $trace = array_reverse($trace);
            } elseif (function_exists('symfony_debug_backtrace')) {
                $trace = symfony_debug_backtrace();
                if (0 < $traceOffset) {
                    array_splice($trace, 0, $traceOffset);
                }
            } else {
                $trace = array();
            }

            $this->setTrace($trace);
        }
    }

    protected function setTrace($trace)
    {
        $traceReflector = new \ReflectionProperty('Exception', 'trace');
        $traceReflector->setAccessible(true);
        $traceReflector->setValue($this, $trace);
    }
}
<?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\Debug\Exception;

/**
 * Fatal Throwable Error.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class FatalThrowableError extends FatalErrorException
{
    public function __construct(\Throwable $e)
    {
        if ($e instanceof \ParseError) {
            $message = 'Parse error: '.$e->getMessage();
            $severity = E_PARSE;
        } elseif ($e instanceof \TypeError) {
            $message = 'Type error: '.$e->getMessage();
            $severity = E_RECOVERABLE_ERROR;
        } else {
            $message = $e->getMessage();
            $severity = E_ERROR;
        }

        \ErrorException::__construct(
            $message,
            $e->getCode(),
            $severity,
            $e->getFile(),
            $e->getLine()
        );

        $this->setTrace($e->getTrace());
    }
}
<?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\HttpKernel\Exception;

use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException;

/**
 * FlattenException wraps a PHP Exception to be able to serialize it.
 *
 * Basically, this class removes all objects from the trace.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead.
 */
class FlattenException
{
    private $handler;

    public static function __callStatic($method, $args)
    {
        if (!method_exists('Symfony\Component\Debug\Exception\FlattenException', $method)) {
            throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_called_class(), $method));
        }

        return call_user_func_array(array('Symfony\Component\Debug\Exception\FlattenException', $method), $args);
    }

    public function __call($method, $args)
    {
        if (!isset($this->handler)) {
            $this->handler = new DebugFlattenException();
        }

        if (!method_exists($this->handler, $method)) {
            throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $method));
        }

        return call_user_func_array(array($this->handler, $method), $args);
    }
}

namespace Symfony\Component\Debug\Exception;

use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

/**
 * FlattenException wraps a PHP Exception to be able to serialize it.
 *
 * Basically, this class removes all objects from the trace.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FlattenException extends LegacyFlattenException
{
    private $message;
    private $code;
    private $previous;
    private $trace;
    private $class;
    private $statusCode;
    private $headers;
    private $file;
    private $line;

    public static function create(\Exception $exception, $statusCode = null, array $headers = array())
    {
        $e = new static();
        $e->setMessage($exception->getMessage());
        $e->setCode($exception->getCode());

        if ($exception instanceof HttpExceptionInterface) {
            $statusCode = $exception->getStatusCode();
            $headers = array_merge($headers, $exception->getHeaders());
        }

        if (null === $statusCode) {
            $statusCode = 500;
        }

        $e->setStatusCode($statusCode);
        $e->setHeaders($headers);
        $e->setTraceFromException($exception);
        $e->setClass(get_class($exception));
        $e->setFile($exception->getFile());
        $e->setLine($exception->getLine());

        $previous = $exception->getPrevious();

        if ($previous instanceof \Exception) {
            $e->setPrevious(static::create($previous));
        } elseif ($previous instanceof \Throwable) {
            $e->setPrevious(static::create(new FatalThrowableError($previous)));
        }

        return $e;
    }

    public function toArray()
    {
        $exceptions = array();
        foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
            $exceptions[] = array(
                'message' => $exception->getMessage(),
                'class' => $exception->getClass(),
                'trace' => $exception->getTrace(),
            );
        }

        return $exceptions;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }

    public function setStatusCode($code)
    {
        $this->statusCode = $code;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function setHeaders(array $headers)
    {
        $this->headers = $headers;
    }

    public function getClass()
    {
        return $this->class;
    }

    public function setClass($class)
    {
        $this->class = $class;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function setFile($file)
    {
        $this->file = $file;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function setLine($line)
    {
        $this->line = $line;
    }

    public function getMessage()
    {
        return $this->message;
    }

    public function setMessage($message)
    {
        $this->message = $message;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function setCode($code)
    {
        $this->code = $code;
    }

    public function getPrevious()
    {
        return $this->previous;
    }

    public function setPrevious(FlattenException $previous)
    {
        $this->previous = $previous;
    }

    public function getAllPrevious()
    {
        $exceptions = array();
        $e = $this;
        while ($e = $e->getPrevious()) {
            $exceptions[] = $e;
        }

        return $exceptions;
    }

    public function getTrace()
    {
        return $this->trace;
    }

    public function setTraceFromException(\Exception $exception)
    {
        $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
    }

    public function setTrace($trace, $file, $line)
    {
        $this->trace = array();
        $this->trace[] = array(
            'namespace' => '',
            'short_class' => '',
            'class' => '',
            'type' => '',
            'function' => '',
            'file' => $file,
            'line' => $line,
            'args' => array(),
        );
        foreach ($trace as $entry) {
            $class = '';
            $namespace = '';
            if (isset($entry['class'])) {
                $parts = explode('\\', $entry['class']);
                $class = array_pop($parts);
                $namespace = implode('\\', $parts);
            }

            $this->trace[] = array(
                'namespace' => $namespace,
                'short_class' => $class,
                'class' => isset($entry['class']) ? $entry['class'] : '',
                'type' => isset($entry['type']) ? $entry['type'] : '',
                'function' => isset($entry['function']) ? $entry['function'] : null,
                'file' => isset($entry['file']) ? $entry['file'] : null,
                'line' => isset($entry['line']) ? $entry['line'] : null,
                'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
            );
        }
    }

    private function flattenArgs($args, $level = 0, &$count = 0)
    {
        $result = array();
        foreach ($args as $key => $value) {
            if (++$count > 1e4) {
                return array('array', '*SKIPPED over 10000 entries*');
            }
            if ($value instanceof \__PHP_Incomplete_Class) {
                // is_object() returns false on PHP<=7.1
                $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
            } elseif (is_object($value)) {
                $result[$key] = array('object', get_class($value));
            } elseif (is_array($value)) {
                if ($level > 10) {
                    $result[$key] = array('array', '*DEEP NESTED ARRAY*');
                } else {
                    $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
                }
            } elseif (null === $value) {
                $result[$key] = array('null', null);
            } elseif (is_bool($value)) {
                $result[$key] = array('boolean', $value);
            } elseif (is_resource($value)) {
                $result[$key] = array('resource', get_resource_type($value));
            } else {
                $result[$key] = array('string', (string) $value);
            }
        }

        return $result;
    }

    private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
    {
        $array = new \ArrayObject($value);

        return $array['__PHP_Incomplete_Class_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\Debug\Exception;

/**
 * Out of memory exception.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class OutOfMemoryException extends FatalErrorException
{
}
<?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\Debug\Exception;

/**
 * Undefined Function Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class UndefinedFunctionException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?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\Debug\Exception;

/**
 * Undefined Method Exception.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?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\Debug;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;

/**
 * ExceptionHandler converts an exception to a Response object.
 *
 * It is mostly useful in debug mode to replace the default PHP/XDebug
 * output with something prettier and more useful.
 *
 * As this class is mainly used during Kernel boot, where nothing is yet
 * available, the Response content is always HTML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ExceptionHandler
{
    private $debug;
    private $charset;
    private $handler;
    private $caughtBuffer;
    private $caughtLength;
    private $fileLinkFormat;

    public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
    {
        if (false !== strpos($charset, '%')) {
            @trigger_error('Providing $fileLinkFormat as second argument to '.__METHOD__.' is deprecated since version 2.8 and will be unsupported in 3.0. Please provide it as third argument, after $charset.', E_USER_DEPRECATED);

            // Swap $charset and $fileLinkFormat for BC reasons
            $pivot = $fileLinkFormat;
            $fileLinkFormat = $charset;
            $charset = $pivot;
        }
        $this->debug = $debug;
        $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
        $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
    }

    /**
     * Registers the exception handler.
     *
     * @param bool        $debug          Enable/disable debug mode, where the stack trace is displayed
     * @param string|null $charset        The charset used by exception messages
     * @param string|null $fileLinkFormat The IDE link template
     *
     * @return static
     */
    public static function register($debug = true, $charset = null, $fileLinkFormat = null)
    {
        $handler = new static($debug, $charset, $fileLinkFormat);

        $prev = set_exception_handler(array($handler, 'handle'));
        if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
            restore_exception_handler();
            $prev[0]->setExceptionHandler(array($handler, 'handle'));
        }

        return $handler;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler An handler that will be called on Exception
     *
     * @return callable|null The previous exception handler if any
     */
    public function setHandler($handler)
    {
        if (null !== $handler && !is_callable($handler)) {
            throw new \LogicException('The exception handler must be a valid PHP callable.');
        }
        $old = $this->handler;
        $this->handler = $handler;

        return $old;
    }

    /**
     * Sets the format for links to source files.
     *
     * @param string $format The format for links to source files
     *
     * @return string The previous file link format
     */
    public function setFileLinkFormat($format)
    {
        $old = $this->fileLinkFormat;
        $this->fileLinkFormat = $format;

        return $old;
    }

    /**
     * Sends a response for the given Exception.
     *
     * To be as fail-safe as possible, the exception is first handled
     * by our simple exception handler, then by the user exception handler.
     * The latter takes precedence and any output from the former is cancelled,
     * if and only if nothing bad happens in this handling path.
     */
    public function handle(\Exception $exception)
    {
        if (null === $this->handler || $exception instanceof OutOfMemoryException) {
            $this->failSafeHandle($exception);

            return;
        }

        $caughtLength = $this->caughtLength = 0;

        ob_start(array($this, 'catchOutput'));
        $this->failSafeHandle($exception);
        while (null === $this->caughtBuffer && ob_end_flush()) {
            // Empty loop, everything is in the condition
        }
        if (isset($this->caughtBuffer[0])) {
            ob_start(array($this, 'cleanOutput'));
            echo $this->caughtBuffer;
            $caughtLength = ob_get_length();
        }
        $this->caughtBuffer = null;

        try {
            call_user_func($this->handler, $exception);
            $this->caughtLength = $caughtLength;
        } catch (\Exception $e) {
            if (!$caughtLength) {
                // All handlers failed. Let PHP handle that now.
                throw $exception;
            }
        }
    }

    /**
     * Sends a response for the given Exception.
     *
     * If you have the Symfony HttpFoundation component installed,
     * this method will use it to create and send the response. If not,
     * it will fallback to plain PHP functions.
     *
     * @param \Exception $exception An \Exception instance
     */
    private function failSafeHandle(\Exception $exception)
    {
        if (class_exists('Symfony\Component\HttpFoundation\Response', false)
            && __CLASS__ !== get_class($this)
            && ($reflector = new \ReflectionMethod($this, 'createResponse'))
            && __CLASS__ !== $reflector->class
        ) {
            $response = $this->createResponse($exception);
            $response->sendHeaders();
            $response->sendContent();
            @trigger_error(sprintf("The %s::createResponse method is deprecated since 2.8 and won't be called anymore when handling an exception in 3.0.", $reflector->class), E_USER_DEPRECATED);

            return;
        }

        $this->sendPhpResponse($exception);
    }

    /**
     * Sends the error associated with the given Exception as a plain PHP response.
     *
     * This method uses plain PHP functions like header() and echo to output
     * the response.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     */
    public function sendPhpResponse($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        if (!headers_sent()) {
            header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
            foreach ($exception->getHeaders() as $name => $value) {
                header($name.': '.$value, false);
            }
            header('Content-Type: text/html; charset='.$this->charset);
        }

        echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Creates the error Response associated with the given Exception.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     *
     * @return Response A Response instance
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function createResponse($exception)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        return Response::create($this->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset);
    }

    /**
     * Gets the full HTML content associated with the given exception.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     *
     * @return string The HTML content as a string
     */
    public function getHtml($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Gets the HTML content associated with the given exception.
     *
     * @param FlattenException $exception A FlattenException instance
     *
     * @return string The content as a string
     */
    public function getContent(FlattenException $exception)
    {
        switch ($exception->getStatusCode()) {
            case 404:
                $title = 'Sorry, the page you are looking for could not be found.';
                break;
            default:
                $title = 'Whoops, looks like something went wrong.';
        }

        $content = '';
        if ($this->debug) {
            try {
                $count = count($exception->getAllPrevious());
                $total = $count + 1;
                foreach ($exception->toArray() as $position => $e) {
                    $ind = $count - $position + 1;
                    $class = $this->formatClass($e['class']);
                    $message = nl2br($this->escapeHtml($e['message']));
                    $content .= sprintf(<<<'EOF'
                        <h2 class="block_exception clear_fix">
                            <span class="exception_counter">%d/%d</span>
                            <span class="exception_title">%s%s:</span>
                            <span class="exception_message">%s</span>
                        </h2>
                        <div class="block">
                            <ol class="traces list_exception">

EOF
                        , $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message);
                    foreach ($e['trace'] as $trace) {
                        $content .= '       <li>';
                        if ($trace['function']) {
                            $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
                        }
                        if (isset($trace['file']) && isset($trace['line'])) {
                            $content .= $this->formatPath($trace['file'], $trace['line']);
                        }
                        $content .= "</li>\n";
                    }

                    $content .= "    </ol>\n</div>\n";
                }
            } catch (\Exception $e) {
                // something nasty happened and we cannot throw an exception anymore
                if ($this->debug) {
                    $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage()));
                } else {
                    $title = 'Whoops, looks like something went wrong.';
                }
            }
        }

        return <<<EOF
            <div id="sf-resetcontent" class="sf-reset">
                <h1>$title</h1>
                $content
            </div>
EOF;
    }

    /**
     * Gets the stylesheet associated with the given exception.
     *
     * @param FlattenException $exception A FlattenException instance
     *
     * @return string The stylesheet as a string
     */
    public function getStylesheet(FlattenException $exception)
    {
        return <<<'EOF'
            .sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 }
            .sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; }
            .sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; }
            .sf-reset .clear_fix { display:inline-block; }
            .sf-reset * html .clear_fix { height:1%; }
            .sf-reset .clear_fix { display:block; }
            .sf-reset, .sf-reset .block { margin: auto }
            .sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
            .sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
            .sf-reset strong { font-weight:bold; }
            .sf-reset a { color:#6c6159; cursor: default; }
            .sf-reset a img { border:none; }
            .sf-reset a:hover { text-decoration:underline; }
            .sf-reset em { font-style:italic; }
            .sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
            .sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; }
            .sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; }
            .sf-reset .exception_message { margin-left: 3em; display: block; }
            .sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
            .sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
                -webkit-border-bottom-right-radius: 16px;
                -webkit-border-bottom-left-radius: 16px;
                -moz-border-radius-bottomright: 16px;
                -moz-border-radius-bottomleft: 16px;
                border-bottom-right-radius: 16px;
                border-bottom-left-radius: 16px;
                border-bottom:1px solid #ccc;
                border-right:1px solid #ccc;
                border-left:1px solid #ccc;
                word-wrap: break-word;
            }
            .sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px;
                -webkit-border-top-left-radius: 16px;
                -webkit-border-top-right-radius: 16px;
                -moz-border-radius-topleft: 16px;
                -moz-border-radius-topright: 16px;
                border-top-left-radius: 16px;
                border-top-right-radius: 16px;
                border-top:1px solid #ccc;
                border-right:1px solid #ccc;
                border-left:1px solid #ccc;
                overflow: hidden;
                word-wrap: break-word;
            }
            .sf-reset a { background:none; color:#868686; text-decoration:none; }
            .sf-reset a:hover { background:none; color:#313131; text-decoration:underline; }
            .sf-reset ol { padding: 10px 0; }
            .sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                border-radius: 10px;
                border: 1px solid #ccc;
            }
EOF;
    }

    private function decorate($content, $css)
    {
        return <<<EOF
<!DOCTYPE html>
<html>
    <head>
        <meta charset="{$this->charset}" />
        <meta name="robots" content="noindex,nofollow" />
        <style>
            /* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html */
            html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}

            html { background: #eee; padding: 10px }
            img { border: 0; }
            #sf-resetcontent { width:970px; margin:0 auto; }
            $css
        </style>
    </head>
    <body>
        $content
    </body>
</html>
EOF;
    }

    private function formatClass($class)
    {
        $parts = explode('\\', $class);

        return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
    }

    private function formatPath($path, $line)
    {
        $path = $this->escapeHtml($path);
        $file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;

        if ($linkFormat = $this->fileLinkFormat) {
            $link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line));

            return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
        }

        return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
    }

    /**
     * Formats an array as a string.
     *
     * @param array $args The argument array
     *
     * @return string
     */
    private function formatArgs(array $args)
    {
        $result = array();
        foreach ($args as $key => $item) {
            if ('object' === $item[0]) {
                $formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
            } elseif ('array' === $item[0]) {
                $formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
            } elseif ('string' === $item[0]) {
                $formattedValue = sprintf("'%s'", $this->escapeHtml($item[1]));
            } elseif ('null' === $item[0]) {
                $formattedValue = '<em>null</em>';
            } elseif ('boolean' === $item[0]) {
                $formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
            } elseif ('resource' === $item[0]) {
                $formattedValue = '<em>resource</em>';
            } else {
                $formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true));
            }

            $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
        }

        return implode(', ', $result);
    }

    /**
     * Returns an UTF-8 and HTML encoded string.
     *
     * @deprecated since version 2.7, to be removed in 3.0.
     */
    protected static function utf8Htmlize($str)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);

        return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), 'UTF-8');
    }

    /**
     * HTML-encodes a string.
     */
    private function escapeHtml($str)
    {
        return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), $this->charset);
    }

    /**
     * @internal
     */
    public function catchOutput($buffer)
    {
        $this->caughtBuffer = $buffer;

        return '';
    }

    /**
     * @internal
     */
    public function cleanOutput($buffer)
    {
        if ($this->caughtLength) {
            // use substr_replace() instead of substr() for mbstring overloading resistance
            $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
            if (isset($cleanBuffer[0])) {
                $buffer = $cleanBuffer;
            }
        }

        return $buffer;
    }
}
<?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\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;

/**
 * ErrorHandler for classes that do not exist.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = strlen($error['message']);
        $notFoundSuffix = '\' not found';
        $notFoundSuffixLen = strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        foreach (array('class', 'interface', 'trait') as $typeName) {
            $prefix = ucfirst($typeName).' \'';
            $prefixLen = strlen($prefix);
            if (0 !== strpos($error['message'], $prefix)) {
                continue;
            }

            $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
            if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
                $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
                $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
                $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
                $tail = ' for another namespace?';
            } else {
                $className = $fullyQualifiedClassName;
                $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
                $tail = '?';
            }

            if ($candidates = $this->getClassCandidates($className)) {
                $tail = array_pop($candidates).'"?';
                if ($candidates) {
                    $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
                } else {
                    $tail = ' for "'.$tail;
                }
            }
            $message .= "\nDid you forget a \"use\" statement".$tail;

            return new ClassNotFoundException($message, $exception);
        }
    }

    /**
     * Tries to guess the full namespace for a given class name.
     *
     * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
     * autoloader (that should cover all common cases).
     *
     * @param string $class A class name (without its namespace)
     *
     * @return array An array of possible fully qualified class names
     */
    private function getClassCandidates($class)
    {
        if (!is_array($functions = spl_autoload_functions())) {
            return array();
        }

        // find Symfony and Composer autoloaders
        $classes = array();

        foreach ($functions as $function) {
            if (!is_array($function)) {
                continue;
            }
            // get class loaders wrapped by DebugClassLoader
            if ($function[0] instanceof DebugClassLoader) {
                $function = $function[0]->getClassLoader();

                // @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated.
                if (is_object($function)) {
                    $function = array($function);
                }

                if (!is_array($function)) {
                    continue;
                }
            }

            if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) {
                foreach ($function[0]->getPrefixes() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
            if ($function[0] instanceof ComposerClassLoader) {
                foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
        }

        return array_unique($classes);
    }

    /**
     * @param string $path
     * @param string $class
     * @param string $prefix
     *
     * @return array
     */
    private function findClassInPath($path, $class, $prefix)
    {
        if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
            return array();
        }

        $classes = array();
        $filename = $class.'.php';
        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
            if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
                $classes[] = $class;
            }
        }

        return $classes;
    }

    /**
     * @param string $path
     * @param string $file
     * @param string $prefix
     *
     * @return string|null
     */
    private function convertFileToClass($path, $file, $prefix)
    {
        $candidates = array(
            // namespaced class
            $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
            // namespaced class (with target dir)
            $prefix.$namespacedClass,
            // namespaced class (with target dir and separator)
            $prefix.'\\'.$namespacedClass,
            // PEAR class
            str_replace('\\', '_', $namespacedClass),
            // PEAR class (with target dir)
            str_replace('\\', '_', $prefix.$namespacedClass),
            // PEAR class (with target dir and separator)
            str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
        );

        if ($prefix) {
            $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
        }

        // We cannot use the autoloader here as most of them use require; but if the class
        // is not found, the new autoloader call will require the file again leading to a
        // "cannot redeclare class" error.
        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }

        require_once $file;

        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }
    }

    /**
     * @param string $class
     *
     * @return bool
     */
    private function classExists($class)
    {
        return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, 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\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * Attempts to convert fatal errors to exceptions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface FatalErrorHandlerInterface
{
    /**
     * Attempts to convert an error into an exception.
     *
     * @param array               $error     An array as returned by error_get_last()
     * @param FatalErrorException $exception A FatalErrorException instance
     *
     * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
     */
    public function handleError(array $error, FatalErrorException $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.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\UndefinedFunctionException;
use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * ErrorHandler for undefined functions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = strlen($error['message']);
        $notFoundSuffix = '()';
        $notFoundSuffixLen = strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        $prefix = 'Call to undefined function ';
        $prefixLen = strlen($prefix);
        if (0 !== strpos($error['message'], $prefix)) {
            return;
        }

        $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
        if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
            $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
            $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
            $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
        } else {
            $functionName = $fullyQualifiedFunctionName;
            $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
        }

        $candidates = array();
        foreach (get_defined_functions() as $type => $definedFunctionNames) {
            foreach ($definedFunctionNames as $definedFunctionName) {
                if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
                    $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
                } else {
                    $definedFunctionNameBasename = $definedFunctionName;
                }

                if ($definedFunctionNameBasename === $functionName) {
                    $candidates[] = '\\'.$definedFunctionName;
                }
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }
            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedFunctionException($message, $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.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;

/**
 * ErrorHandler for undefined methods.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
        if (!$matches) {
            return;
        }

        $className = $matches[1];
        $methodName = $matches[2];

        $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);

        if (!class_exists($className) || null === $methods = get_class_methods($className)) {
            // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
            return new UndefinedMethodException($message, $exception);
        }

        $candidates = array();
        foreach ($methods as $definedMethodName) {
            $lev = levenshtein($methodName, $definedMethodName);
            if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
                $candidates[] = $definedMethodName;
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }

            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedMethodException($message, $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.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * 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
{
    private $commands = array();
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $catchExceptions = true;
    private $autoExit = true;
    private $definition;
    private $helperSet;
    private $dispatcher;
    private $terminalDimensions;
    private $defaultCommand;
    private $initialized;

    /**
     * @param string $name    The name of the application
     * @param string $version The version of the application
     */
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->defaultCommand = 'list';
    }

    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * Runs the current application.
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @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)
    {
        if (null === $input) {
            $input = new ArgvInput();
        }

        if (null === $output) {
            $output = new ConsoleOutput();
        }

        $this->configureIO($input, $output);

        try {
            $e = null;
            $exitCode = $this->doRun($input, $output);
        } catch (\Exception $x) {
            $e = $x;
        } catch (\Throwable $x) {
            $e = new FatalThrowableError($x);
        }

        if (null !== $e) {
            if (!$this->catchExceptions || !$x instanceof \Exception) {
                throw $x;
            }

            if ($output instanceof ConsoleOutputInterface) {
                $this->renderException($e, $output->getErrorOutput());
            } else {
                $this->renderException($e, $output);
            }

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if (0 === $exitCode) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--version', '-V'))) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(array('--help', '-h'))) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(array('command' => 'help'));
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                array(
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                )
            ));
        }

        $this->runningCommand = null;
        // the command name MUST be the first element of the input
        $command = $this->find($name);

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    /**
     * Set a helper set to be used with the command.
     *
     * @param HelperSet $helperSet The helper set
     */
    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;
    }

    /**
     * Set an input definition to be used with this application.
     *
     * @param InputDefinition $definition The input definition
     */
    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();
        }

        return $this->definition;
    }

    /**
     * Gets the help message.
     *
     * @return string A help message
     */
    public function getHelp()
    {
        return $this->getLongVersion();
    }

    /**
     * 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;
    }

    /**
     * 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('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
            }

            return sprintf('<info>%s</info>', $this->getName());
        }

        return '<info>Console Tool</info>';
    }

    /**
     * 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.
     *
     * @param Command $command A Command object
     *
     * @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;
        }

        if (null === $command->getDefinition()) {
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', 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 (!isset($this->commands[$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]);
    }

    /**
     * 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 = array();
        foreach ($this->all() as $command) {
            $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 CommandNotFoundException 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 CommandNotFoundException($message, $alternatives);
        }

        $exact = in_array($namespace, $namespaces, true);
        if (count($namespaces) > 1 && !$exact) {
            throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%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();

        $allCommands = array_keys($this->commands);
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $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)) {
                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, $alternatives);
        }

        // filter out aliases for commands which are already on the list
        if (count($commands) > 1) {
            $commandList = $this->commands;
            $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
                $commandName = $commandList[$nameOrAlias]->getName();

                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
            });
        }

        $exact = in_array($name, $commands, true);
        if (count($commands) > 1 && !$exact) {
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));

            throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands));
        }

        return $this->get($exact ? $name : reset($commands));
    }

    /**
     * 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) {
            return $this->commands;
        }

        $commands = array();
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        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 = array();
        foreach ($names as $name) {
            for ($len = strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    /**
     * Returns a text representation of the Application.
     *
     * @param string $namespace An optional namespace name
     * @param bool   $raw       Whether to return raw command list
     *
     * @return string A string representing the Application
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText($namespace = null, $raw = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw);
        $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the Application.
     *
     * @param string $namespace An optional namespace name
     * @param bool   $asDom     Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the Application
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($namespace = null, $asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getApplicationDocument($this, $namespace);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this, array('namespace' => $namespace));

        return $output->fetch();
    }

    /**
     * Renders a caught exception.
     *
     * @param \Exception      $e      An exception instance
     * @param OutputInterface $output An OutputInterface instance
     */
    public function renderException($e, $output)
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        do {
            $title = sprintf('  [%s]  ', get_class($e));

            $len = Helper::strlen($title);

            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
            // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
            if (defined('HHVM_VERSION') && $width > 1 << 31) {
                $width = 1 << 31;
            }
            $lines = array();
            foreach (preg_split('/\r?\n/', trim($e->getMessage())) 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[] = array($line, $lineLength);

                    $len = max($lineLength, $len);
                }
            }

            $messages = array();
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
            $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, array(
                    'function' => '',
                    'file' => null !== $e->getFile() ? $e->getFile() : 'n/a',
                    'line' => null !== $e->getLine() ? $e->getLine() : 'n/a',
                    'args' => array(),
                ));

                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 = $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%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    /**
     * Tries to figure out the terminal width in which this application runs.
     *
     * @return int|null
     */
    protected function getTerminalWidth()
    {
        $dimensions = $this->getTerminalDimensions();

        return $dimensions[0];
    }

    /**
     * Tries to figure out the terminal height in which this application runs.
     *
     * @return int|null
     */
    protected function getTerminalHeight()
    {
        $dimensions = $this->getTerminalDimensions();

        return $dimensions[1];
    }

    /**
     * Tries to figure out the terminal dimensions based on the current environment.
     *
     * @return array Array containing width and height
     */
    public function getTerminalDimensions()
    {
        if ($this->terminalDimensions) {
            return $this->terminalDimensions;
        }

        if ('\\' === DIRECTORY_SEPARATOR) {
            // extract [w, H] from "wxh (WxH)"
            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
                return array((int) $matches[1], (int) $matches[2]);
            }
            // extract [w, h] from "wxh"
            if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
                return array((int) $matches[1], (int) $matches[2]);
            }
        }

        if ($sttyString = $this->getSttyColumns()) {
            // extract [w, h] from "rows h; columns w;"
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                return array((int) $matches[2], (int) $matches[1]);
            }
            // extract [w, h] from "; h rows; w columns"
            if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                return array((int) $matches[2], (int) $matches[1]);
            }
        }

        return array(null, null);
    }

    /**
     * Sets terminal dimensions.
     *
     * Can be useful to force terminal dimensions for functional tests.
     *
     * @param int $width  The width
     * @param int $height The height
     *
     * @return $this
     */
    public function setTerminalDimensions($width, $height)
    {
        $this->terminalDimensions = array($width, $height);

        return $this;
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--ansi'))) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
            $input->setInteractive(false);
        } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
            $inputStream = $this->getHelperSet()->get('question')->getInputStream();
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
                $input->setInteractive(false);
            }
        }

        if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $input->setInteractive(false);
        } else {
            if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || 3 === $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
            } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || 2 === $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
            } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
            }
        }
    }

    /**
     * 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.
     *
     * @param Command         $command A Command instance
     * @param InputInterface  $input   An Input instance
     * @param OutputInterface $output  An Output instance
     *
     * @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(ConsoleEvents::COMMAND, $event);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }
        if (null !== $e) {
            $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
            $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
            $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

            if ($x !== $event->getException()) {
                $e = $event->getException();
            }
            $exitCode = $e->getCode();
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     *
     * @param InputInterface $input The input interface
     *
     * @return string The command name
     */
    protected function getCommandName(InputInterface $input)
    {
        return $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition(array(
            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 array(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(array(
            new FormatterHelper(),
            new DialogHelper(false),
            new ProgressHelper(false),
            new TableHelper(false),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ));
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     *
     * @return string
     */
    private function getSttyColumns()
    {
        if (!function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            return $info;
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return string|null <width>x<height> or null if it could not be parsed
     */
    private function getConsoleMode()
    {
        if (!function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
                return $matches[2].'x'.$matches[1];
            }
        }
    }

    /**
     * Returns abbreviated suggestions in string format.
     *
     * @param array $abbrevs Abbreviated suggestions to convert
     *
     * @return string A formatted string of abbreviated suggestions
     */
    private function getAbbreviationSuggestions($abbrevs)
    {
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
    }

    /**
     * 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);
        array_pop($parts);

        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.
     *
     * @param string             $name       The string
     * @param array|\Traversable $collection The collection
     *
     * @return string[] A sorted array of similar string
     */
    private function findAlternatives($name, $collection)
    {
        $threshold = 1e3;
        $alternatives = array();

        $collectionParts = array();
        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; });
        asort($alternatives);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @param string $commandName The Command name
     */
    public function setDefaultCommand($commandName)
    {
        $this->defaultCommand = $commandName;
    }

    private function splitStringByWidth($string, $width)
    {
        // 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 = array();
        $line = '';
        foreach (preg_split('//u', $utf8String) 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.
     *
     * @param string $name The full name of the command
     *
     * @return string[] The namespaces of the command
     */
    private function extractAllNamespaces($name)
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = array();

        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\Command;

use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    private $application;
    private $name;
    private $processTitle;
    private $aliases = array();
    private $definition;
    private $help;
    private $description;
    private $ignoreValidationErrors = false;
    private $applicationDefinitionMerged = false;
    private $applicationDefinitionMergedWithArgs = false;
    private $code;
    private $synopsis = array();
    private $usages = array();
    private $helperSet;

    /**
     * @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($name = null)
    {
        $this->definition = new InputDefinition();

        if (null !== $name) {
            $this->setName($name);
        }

        $this->configure();

        if (!$this->name) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
        }
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    /**
     * Sets the application instance for this command.
     *
     * @param Application $application An Application instance
     */
    public function setApplication(Application $application = null)
    {
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }
    }

    /**
     * Sets the helper set.
     *
     * @param HelperSet $helperSet A HelperSet instance
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     *
     * @return HelperSet A HelperSet instance
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     *
     * @return Application 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.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     *
     * @return null|int null or 0 if everything went fine, or an error 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.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command just after the input has been 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.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     */
    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.
     *
     * @param InputInterface  $input  An InputInterface instance
     * @param OutputInterface $output An OutputInterface instance
     *
     * @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 (false === @cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === PHP_OS) {
                        $output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
                    } else {
                        $error = error_get_last();
                        trigger_error($error['message'], E_USER_WARNING);
                    }
                }
            } 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 = call_user_func($this->code, $input, $output);
        } else {
            $statusCode = $this->execute($input, $output);
        }

        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($code)
    {
        if (!is_callable($code)) {
            throw new InvalidArgumentException('Invalid callable provided to Command::setCode.');
        }

        if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                if (PHP_VERSION_ID < 70000) {
                    // Bug in PHP5: https://bugs.php.net/bug.php?id=64761
                    // This means that we cannot bind static closures and therefore we must
                    // ignore any errors here.  There is no way to test if the closure is
                    // bindable.
                    $code = @\Closure::bind($code, $this);
                } else {
                    $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());

        if ($mergeArgs) {
            $currentArguments = $this->definition->getArguments();
            $this->definition->setArguments($this->application->getDefinition()->getArguments());
            $this->definition->addArguments($currentArguments);
        }

        $this->applicationDefinitionMerged = true;
        if ($mergeArgs) {
            $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()
    {
        return $this->definition;
    }

    /**
     * Gets the InputDefinition to be used to create XML and Text 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    $mode        The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param string $description A description text
     * @param mixed  $default     The default value (for InputArgument::OPTIONAL mode only)
     *
     * @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 $shortcut    The shortcut (can be null)
     * @param int    $mode        The option mode: One of the InputOption::VALUE_* constants
     * @param string $description A description text
     * @param mixed  $default     The default value (must be null for InputOption::VALUE_NONE)
     *
     * @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 The command name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 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;

        $placeholders = array(
            '%command.name%',
            '%command.full_name%',
        );
        $replacements = array(
            $name,
            $_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);
    }

    /**
     * Returns a text representation of the command.
     *
     * @return string A string representing the command
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
        $descriptor->describe($output, $this, array('raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the command.
     *
     * @param bool $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the command
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getCommandDocument($this);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this);

        return $output->fetch();
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @param string $name
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName($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\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
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(array(
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
                new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
                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
            )
        ;
    }

    /**
     * Sets the command.
     *
     * @param Command $command The command to set
     */
    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'));
        }

        if ($input->getOption('xml')) {
            @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED);

            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ));

        $this->command = 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\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputDefinition;

/**
 * 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)
    {
        if ($input->getOption('xml')) {
            @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED);

            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
        ));
    }

    /**
     * {@inheritdoc}
     */
    private function createDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
            new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'),
            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;

/**
 * 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.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent
     * instance.
     *
     * @Event
     *
     * @var string
     */
    const COMMAND = 'console.command';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent
     * instance.
     *
     * @Event
     *
     * @var string
     */
    const TERMINATE = 'console.terminate';

    /**
     * The EXCEPTION event occurs when an uncaught exception appears.
     *
     * This event allows you to deal with the exception or
     * to modify the thrown exception. The event listener method receives
     * a Symfony\Component\Console\Event\ConsoleExceptionEvent
     * instance.
     *
     * @Event
     *
     * @var string
     */
    const EXCEPTION = 'console.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.
 */

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';

    /**
     * @var Application
     */
    private $application;

    /**
     * @var null|string
     */
    private $namespace;

    /**
     * @var array
     */
    private $namespaces;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var Command[]
     */
    private $aliases;

    /**
     * @param Application $application
     * @param string|null $namespace
     */
    public function __construct(Application $application, $namespace = null)
    {
        $this->application = $application;
        $this->namespace = $namespace;
    }

    /**
     * @return array
     */
    public function getNamespaces()
    {
        if (null === $this->namespaces) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands()
    {
        if (null === $this->commands) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function getCommand($name)
    {
        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 = array();
        $this->namespaces = array();

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = array();

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName()) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names);
        }
    }

    /**
     * @param array $commands
     *
     * @return array
     */
    private function sortCommands(array $commands)
    {
        $namespacedCommands = array();
        $globalCommands = array();
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (!$key) {
                $globalCommands['_global'][$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }
        ksort($namespacedCommands);
        $namespacedCommands = array_merge($globalCommands, $namespacedCommands);

        foreach ($namespacedCommands as &$commandsSet) {
            ksort($commandsSet);
        }
        // unset reference to keep scope clear
        unset($commandsSet);

        return $namespacedCommands;
    }
}
<?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;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $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.
     *
     * @param InputArgument $argument
     * @param array         $options
     *
     * @return string|mixed
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = array());

    /**
     * Describes an InputOption instance.
     *
     * @param InputOption $option
     * @param array       $options
     *
     * @return string|mixed
     */
    abstract protected function describeInputOption(InputOption $option, array $options = array());

    /**
     * Describes an InputDefinition instance.
     *
     * @param InputDefinition $definition
     * @param array           $options
     *
     * @return string|mixed
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());

    /**
     * Describes a Command instance.
     *
     * @param Command $command
     * @param array   $options
     *
     * @return string|mixed
     */
    abstract protected function describeCommand(Command $command, array $options = array());

    /**
     * Describes an Application instance.
     *
     * @param Application $application
     * @param array       $options
     *
     * @return string|mixed
     */
    abstract protected function describeApplication(Application $application, array $options = 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\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * Describes an InputArgument instance.
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     */
    public function describe(OutputInterface $output, $object, array $options = 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\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 = array())
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeData($this->getInputOptionData($option), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeData($this->getCommandData($command), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $commands = array();

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command);
        }

        $data = $describedNamespace
            ? array('commands' => $commands, 'namespace' => $describedNamespace)
            : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces()));

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     *
     * @param array $data
     * @param array $options
     *
     * @return array|string
     */
    private function writeData(array $data, array $options)
    {
        $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
    }

    /**
     * @param InputArgument $argument
     *
     * @return array
     */
    private function getInputArgumentData(InputArgument $argument)
    {
        return array(
            '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(),
        );
    }

    /**
     * @param InputOption $option
     *
     * @return array
     */
    private function getInputOptionData(InputOption $option)
    {
        return array(
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $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(),
        );
    }

    /**
     * @param InputDefinition $definition
     *
     * @return array
     */
    private function getInputDefinitionData(InputDefinition $definition)
    {
        $inputArguments = array();
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = array();
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
        }

        return array('arguments' => $inputArguments, 'options' => $inputOptions);
    }

    /**
     * @param Command $command
     *
     * @return array
     */
    private function getCommandData(Command $command)
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        return array(
            'name' => $command->getName(),
            'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
            'description' => $command->getDescription(),
            'help' => $command->getProcessedHelp(),
            'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
        );
    }
}
<?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;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->write(
            '**'.$argument->getName().':**'."\n\n"
            .'* Name: '.($argument->getName() ?: '<none>')."\n"
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n  ", $argument->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->write(
            '**'.$option->getName().':**'."\n\n"
            .'* Name: `--'.$option->getName().'`'."\n"
            .'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '<none>')."\n"
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n  ", $option->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        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 = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $this->write(
            $command->getName()."\n"
            .str_repeat('-', Helper::strlen($command->getName()))."\n\n"
            .'* Description: '.($command->getDescription() ?: '<none>')."\n"
            .'* Usage:'."\n\n"
            .array_reduce(array_merge(array($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 = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);

        $this->write($application->getName()."\n".str_repeat('=', Helper::strlen($application->getName())));

        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) {
                return '* '.$commandName;
            }, $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->write($this->describeCommand($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\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 = array())
    {
        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 = array())
    {
        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(array($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 = array())
    {
        $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, array('total_width' => $totalWidth)));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = array();

            $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, array('total_width' => $totalWidth)));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis(true);
        $command->getSynopsis(false);
        $command->mergeApplicationDefinition(false);

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.$usage, $options);
        }
        $this->writeText("\n");

        $definition = $command->getNativeDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        if ($help = $command->getProcessedHelp()) {
            $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 = array())
    {
        $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");

            $width = $this->getColumnWidth($description->getCommands());

            if ($describedNamespace) {
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            // add commands by namespace
            foreach ($description->getNamespaces() as $namespace) {
                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);
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    private function writeText($content, array $options = array())
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     *
     * @return string
     */
    private function formatDefaultValue($default)
    {
        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);
                }
            }
        }

        if (\PHP_VERSION_ID < 50400) {
            return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default));
        }

        return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param Command[] $commands
     *
     * @return int
     */
    private function getColumnWidth(array $commands)
    {
        $widths = array();

        foreach ($commands as $command) {
            $widths[] = Helper::strlen($command->getName());
            foreach ($command->getAliases() as $alias) {
                $widths[] = Helper::strlen($alias);
            }
        }

        return max($widths) + 2;
    }

    /**
     * @param InputOption[] $options
     *
     * @return int
     */
    private function calculateTotalWidthForOptions($options)
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(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\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
{
    /**
     * @param InputDefinition $definition
     *
     * @return \DOMDocument
     */
    public function getInputDefinitionDocument(InputDefinition $definition)
    {
        $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;
    }

    /**
     * @param Command $command
     *
     * @return \DOMDocument
     */
    public function getCommandDocument(Command $command)
    {
        $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->appendChild($usagesXML = $dom->createElement('usages'));

        foreach (array_merge(array($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;
    }

    /**
     * @param Application $application
     * @param string|null $namespace
     *
     * @return \DOMDocument
     */
    public function getApplicationDocument(Application $application, $namespace = null)
    {
        $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);

        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 = array())
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeDocument($this->getCommandDocument($command));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
    }

    /**
     * Appends document children to parent node.
     *
     * @param \DOMNode $parentNode
     * @param \DOMNode $importedParent
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     *
     * @param \DOMDocument $dom
     *
     * @return \DOMDocument|string
     */
    private function writeDocument(\DOMDocument $dom)
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    /**
     * @param InputArgument $argument
     *
     * @return \DOMDocument
     */
    private function getInputArgumentDocument(InputArgument $argument)
    {
        $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()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    /**
     * @param InputOption $option
     *
     * @return \DOMDocument
     */
    private function getInputOptionDocument(InputOption $option)
    {
        $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', '-'.implode('|-', explode('|', $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()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
            $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\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or changing the input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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.
     *
     * @var bool
     */
    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, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     *
     * @return Command 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\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle exception thrown in a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleExceptionEvent extends ConsoleEvent
{
    private $exception;
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setException($exception);
        $this->exitCode = (int) $exitCode;
    }

    /**
     * Returns the thrown exception.
     *
     * @return \Exception The thrown exception
     */
    public function getException()
    {
        return $this->exception;
    }

    /**
     * Replaces the thrown exception.
     *
     * This exception will be thrown if no response is set in the event.
     *
     * @param \Exception $exception The thrown exception
     */
    public function setException(\Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * 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 manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleTerminateEvent extends ConsoleEvent
{
    /**
     * The exit code of the command.
     *
     * @var int
     */
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, $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\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 Exception $previous     previous exception used for the exception chaining
     */
    public function __construct($message, array $alternatives = array(), $code = 0, \Exception $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\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface 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;

/**
 * @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 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;

/**
 * @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;

/**
 * @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\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatter implements OutputFormatterInterface
{
    private $decorated;
    private $styles = array();
    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.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     *
     * @internal
     */
    public static function escapeTrailingBackslash($text)
    {
        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 bool                            $decorated Whether this formatter should actually decorate strings
     * @param OutputFormatterStyleInterface[] $styles    Array of "name => FormatterStyle" instances
     */
    public function __construct($decorated = false, array $styles = array())
    {
        $this->decorated = (bool) $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)
    {
        $message = (string) $message;
        $offset = 0;
        $output = '';
        $tagRegex = '[a-z][a-z0-9_=;-]*+';
        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));
            $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 (false === $style = $this->createStyleFromString(strtolower($tag))) {
                $output .= $this->applyCurrentStyle($text);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle(substr($message, $offset));

        if (false !== strpos($output, "\0")) {
            return strtr($output, array("\0" => '\\', '\\<' => '<'));
        }

        return str_replace('\\<', '<', $output);
    }

    /**
     * @return OutputFormatterStyleStack
     */
    public function getStyleStack()
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     *
     * @param string $string
     *
     * @return OutputFormatterStyle|false false if string is not format string
     */
    private function createStyleFromString($string)
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
            return false;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);

            if ('fg' == $match[0]) {
                $style->setForeground($match[1]);
            } elseif ('bg' == $match[0]) {
                $style->setBackground($match[1]);
            } else {
                try {
                    $style->setOption($match[1]);
                } catch (\InvalidArgumentException $e) {
                    return false;
                }
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     *
     * @param string $text Input text
     *
     * @return string Styled text
     */
    private function applyCurrentStyle($text)
    {
        return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $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
     * @param OutputFormatterStyleInterface $style The style instance
     */
    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 style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static $availableForegroundColors = array(
        'black' => array('set' => 30, 'unset' => 39),
        'red' => array('set' => 31, 'unset' => 39),
        'green' => array('set' => 32, 'unset' => 39),
        'yellow' => array('set' => 33, 'unset' => 39),
        'blue' => array('set' => 34, 'unset' => 39),
        'magenta' => array('set' => 35, 'unset' => 39),
        'cyan' => array('set' => 36, 'unset' => 39),
        'white' => array('set' => 37, 'unset' => 39),
        'default' => array('set' => 39, 'unset' => 39),
    );
    private static $availableBackgroundColors = array(
        'black' => array('set' => 40, 'unset' => 49),
        'red' => array('set' => 41, 'unset' => 49),
        'green' => array('set' => 42, 'unset' => 49),
        'yellow' => array('set' => 43, 'unset' => 49),
        'blue' => array('set' => 44, 'unset' => 49),
        'magenta' => array('set' => 45, 'unset' => 49),
        'cyan' => array('set' => 46, 'unset' => 49),
        'white' => array('set' => 47, 'unset' => 49),
        'default' => array('set' => 49, 'unset' => 49),
    );
    private static $availableOptions = array(
        'bold' => array('set' => 1, 'unset' => 22),
        'underscore' => array('set' => 4, 'unset' => 24),
        'blink' => array('set' => 5, 'unset' => 25),
        'reverse' => array('set' => 7, 'unset' => 27),
        'conceal' => array('set' => 8, 'unset' => 28),
    );

    private $foreground;
    private $background;
    private $options = array();

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     * @param array       $options    The style options
     */
    public function __construct($foreground = null, $background = null, array $options = array())
    {
        if (null !== $foreground) {
            $this->setForeground($foreground);
        }
        if (null !== $background) {
            $this->setBackground($background);
        }
        if (count($options)) {
            $this->setOptions($options);
        }
    }

    /**
     * Sets style foreground color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    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];
    }

    /**
     * Sets style background color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    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];
    }

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    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];
        }
    }

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    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]);
        }
    }

    /**
     * Sets multiple style options at once.
     *
     * @param array $options
     */
    public function setOptions(array $options)
    {
        $this->options = array();

        foreach ($options as $option) {
            $this->setOption($option);
        }
    }

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text)
    {
        $setCodes = array();
        $unsetCodes = array();

        if (null !== $this->foreground) {
            $setCodes[] = $this->foreground['set'];
            $unsetCodes[] = $this->foreground['unset'];
        }
        if (null !== $this->background) {
            $setCodes[] = $this->background['set'];
            $unsetCodes[] = $this->background['unset'];
        }
        if (count($this->options)) {
            foreach ($this->options as $option) {
                $setCodes[] = $option['set'];
                $unsetCodes[] = $option['unset'];
            }
        }

        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 $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.
     *
     * @param array $options
     */
    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;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private $styles;

    /**
     * @var OutputFormatterStyleInterface
     */
    private $emptyStyle;

    /**
     * @param OutputFormatterStyleInterface|null $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 = array();
    }

    /**
     * Pushes a style in the stack.
     *
     * @param OutputFormatterStyleInterface $style
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @param OutputFormatterStyleInterface|null $style
     *
     * @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];
    }

    /**
     * @param OutputFormatterStyleInterface $emptyStyle
     *
     * @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\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 = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
    private $started = array();
    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] = array('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;
    }

    /**
     * @param string $id The id of the formatting session
     *
     * @return string
     */
    private function getBorder($id)
    {
        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\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\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * 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 = array();

    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 OutputInterface $output
     * @param object          $object
     * @param array           $options
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $options = array_merge(array(
            '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
     * @param DescriptorInterface $descriptor
     *
     * @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\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * The Dialog class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0.
 *             Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead.
 */
class DialogHelper extends InputAwareHelper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED);
        }
    }

    /**
     * Asks the user to select a value.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param array           $choices      List of choices to pick from
     * @param bool|string     $default      The default answer if the user enters nothing
     * @param bool|int        $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string          $errorMessage Message which will be shown if invalid value from choice list would be picked
     * @param bool            $multiselect  Select more than one value separated by comma
     *
     * @return int|string|array The selected value or values (the key of the choices array)
     *
     * @throws InvalidArgumentException
     */
    public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $width = max(array_map('strlen', array_keys($choices)));

        $messages = (array) $question;
        foreach ($choices as $key => $value) {
            $messages[] = sprintf("  [<info>%-{$width}s</info>] %s", $key, $value);
        }

        $output->writeln($messages);

        $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $picked);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $picked));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = array($picked);
            }

            $multiselectChoices = array();

            foreach ($selectedChoices as $value) {
                if (empty($choices[$value])) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
                }
                $multiselectChoices[] = $value;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return $picked;
        }, $attempts, $default);

        return $result;
    }

    /**
     * Asks a question to the user.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return string The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
    {
        if ($this->input && !$this->input->isInteractive()) {
            return $default;
        }

        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $output->write($question);

        $inputStream = $this->inputStream ?: STDIN;

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = fgets($inputStream, 4096);
            if (false === $ret) {
                throw new RuntimeException('Aborted');
            }
            $ret = trim($ret);
        } else {
            $ret = '';

            $i = 0;
            $ofs = -1;
            $matches = $autocomplete;
            $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);

                // Backspace Character
                if ("\177" === $c) {
                    if (0 === $numMatches && 0 !== $i) {
                        --$i;
                        // Move cursor backwards
                        $output->write("\033[1D");
                    }

                    if (0 === $i) {
                        $ofs = -1;
                        $matches = $autocomplete;
                        $numMatches = count($matches);
                    } else {
                        $numMatches = 0;
                    }

                    // Pop the last character off the end of our string
                    $ret = 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 = $matches[$ofs];
                            // Echo out remaining chars for current match
                            $output->write(substr($ret, $i));
                            $i = strlen($ret);
                        }

                        if ("\n" === $c) {
                            $output->write($c);
                            break;
                        }

                        $numMatches = 0;
                    }

                    continue;
                } else {
                    $output->write($c);
                    $ret .= $c;
                    ++$i;

                    $numMatches = 0;
                    $ofs = 0;

                    foreach ($autocomplete as $value) {
                        // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                        if (0 === strpos($value, $ret) && $i !== strlen($value)) {
                            $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
                    $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
                    // Restore cursor position
                    $output->write("\0338");
                }
            }

            // Reset stty so it behaves normally again
            shell_exec(sprintf('stty %s', $sttyMode));
        }

        return strlen($ret) > 0 ? $ret : $default;
    }

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $question The question to ask
     * @param bool            $default  The default answer if the user enters nothing
     *
     * @return bool true if the user has confirmed, false otherwise
     */
    public function askConfirmation(OutputInterface $output, $question, $default = true)
    {
        $answer = 'z';
        while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
            $answer = $this->ask($output, $question);
        }

        if (false === $default) {
            return $answer && 'y' == strtolower($answer[0]);
        }

        return !$answer || 'y' == strtolower($answer[0]);
    }

    /**
     * Asks a question to the user, the response is hidden.
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $question The question
     * @param bool            $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden
     */
    public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        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;
            }

            $output->write($question);
            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->hasSttyAvailable()) {
            $output->write($question);

            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($this->inputStream ?: STDIN, 4096);
            shell_exec(sprintf('stty %s', $sttyMode));

            if (false === $value) {
                throw new RuntimeException('Aborted');
            }

            $value = trim($value);
            $output->writeln('');

            return $value;
        }

        if (false !== $shell = $this->getShell()) {
            $output->write($question);
            $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
            $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
            $value = rtrim(shell_exec($command));
            $output->writeln('');

            return $value;
        }

        if ($fallback) {
            return $this->ask($output, $question);
        }

        throw new RuntimeException('Unable to hide the response');
    }

    /**
     * Asks for a value and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param callable        $validator    A PHP callback
     * @param int|false       $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return mixed
     *
     * @throws \Exception When any of the validators return an error
     */
    public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null)
    {
        $that = $this;

        $interviewer = function () use ($output, $question, $default, $autocomplete, $that) {
            return $that->ask($output, $question, $default, $autocomplete);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * Asks for a value, hide and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param OutputInterface $output    An Output instance
     * @param string|array    $question  The question to ask
     * @param callable        $validator A PHP callback
     * @param int|false       $attempts  Max number of times to ask before giving up (false by default, which means infinite)
     * @param bool            $fallback  In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string The response
     *
     * @throws \Exception       When any of the validators return an error
     * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden
     */
    public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
    {
        $that = $this;

        $interviewer = function () use ($output, $question, $fallback, $that) {
            return $that->askHiddenResponse($output, $question, $fallback);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * 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 setInputStream($stream)
    {
        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @return resource|null The input stream or null if the default STDIN is used
     */
    public function getInputStream()
    {
        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'dialog';
    }

    /**
     * Return 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 (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }

    /**
     * Validate an attempt.
     *
     * @param callable        $interviewer A callable that will ask for a question and return the result
     * @param OutputInterface $output      An Output instance
     * @param callable        $validator   A PHP callback
     * @param int|false       $attempts    Max number of times to ask before giving up; false will ask infinitely
     *
     * @return string 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($interviewer, OutputInterface $output, $validator, $attempts)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $e = null;
        while (false === $attempts || $attempts--) {
            if (null !== $e) {
                $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error'));
            }

            try {
                return call_user_func($validator, $interviewer());
            } catch (\Exception $e) {
            }
        }

        throw $e;
    }
}
<?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 = array($messages);
        }

        $len = 0;
        $lines = array();
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? array(str_repeat(' ', $len)) : array();
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - $this->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);
    }

    /**
     * {@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\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;

    /**
     * Sets the helper set associated with this helper.
     *
     * @param HelperSet $helperSet A HelperSet instance
     */
    public function setHelperSet(HelperSet $helperSet = null)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet|null
     */
    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);
    }

    public static function formatTime($secs)
    {
        static $timeFormats = array(
            array(0, '< 1 sec'),
            array(1, '1 sec'),
            array(2, 'secs', 1),
            array(60, '1 min'),
            array(120, 'mins', 60),
            array(3600, '1 hr'),
            array(7200, 'hrs', 3600),
            array(86400, '1 day'),
            array(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;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     *
     * @param HelperSet $helperSet A HelperSet instance
     */
    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\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 = array();
    private $command;

    /**
     * @param Helper[] $helpers An array of helper
     */
    public function __construct(array $helpers = array())
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, is_int($alias) ? null : $alias);
        }
    }

    /**
     * Sets a helper.
     *
     * @param HelperInterface $helper The helper instance
     * @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));
        }

        if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED);
        } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED);
        } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED);
        }

        return $this->helpers[$name];
    }

    /**
     * Sets the command associated with this helper set.
     *
     * @param Command $command A Command instance
     */
    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\Input\InputInterface;
use Symfony\Component\Console\Input\InputAwareInterface;

/**
 * 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\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param OutputInterface      $output    An OutputInterface instance
     * @param string|array|Process $cmd       An instance of Process or an array of arguments to escape and run 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
     * @param int                  $verbosity The threshold for verbosity
     *
     * @return Process The process that ran
     */
    public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if (is_array($cmd)) {
            $process = ProcessBuilder::create($cmd)->getProcess();
        } elseif ($cmd instanceof Process) {
            $process = $cmd;
        } else {
            $process = new Process($cmd);
        }

        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);

        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 OutputInterface $output   An OutputInterface instance
     * @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, $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.
     *
     * @param OutputInterface $output   An OutputInterface interface
     * @param Process         $process  The Process
     * @param callable|null   $callback A PHP callable
     *
     * @return callable
     */
    public function wrapCallback(OutputInterface $output, Process $process, $callback = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        $that = $this;

        return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) {
            $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                call_user_func($callback, $type, $buffer);
            }
        };
    }

    /**
     * This method is public for PHP 5.3 compatibility, it should be private.
     *
     * @internal
     */
    public function escapeString($str)
    {
        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;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\LogicException;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
class ProgressBar
{
    // options
    private $barWidth = 28;
    private $barChar;
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format;
    private $internalFormat;
    private $redrawFreq = 1;

    /**
     * @var OutputInterface
     */
    private $output;
    private $step = 0;
    private $max;
    private $startTime;
    private $stepWidth;
    private $percent = 0.0;
    private $formatLineCount;
    private $messages = array();
    private $overwrite = true;
    private $firstRun = true;

    private static $formatters;
    private static $formats;

    /**
     * @param OutputInterface $output An OutputInterface instance
     * @param int             $max    Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, $max = 0)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);

        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->setRedrawFrequency($max / 10);
        }

        $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($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;
    }

    /**
     * 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($name, $format)
    {
        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($name)
    {
        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($message, $name = 'message')
    {
        $this->messages[$name] = $message;
    }

    public function getMessage($name = 'message')
    {
        return $this->messages[$name];
    }

    /**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Gets the progress bar maximal steps.
     *
     * @return int The progress bar max steps
     */
    public function getMaxSteps()
    {
        return $this->max;
    }

    /**
     * Gets the progress bar step.
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead.
     *
     * @return int The progress bar step
     */
    public function getStep()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED);

        return $this->getProgress();
    }

    /**
     * Gets the current step position.
     *
     * @return int The progress bar step
     */
    public function getProgress()
    {
        return $this->step;
    }

    /**
     * Gets the progress bar step width.
     *
     * @internal This method is public for PHP 5.3 compatibility, it should not be used.
     *
     * @return int The progress bar step width
     */
    public function getStepWidth()
    {
        return $this->stepWidth;
    }

    /**
     * Gets the current progress bar percent.
     *
     * @return float The current progress bar percent
     */
    public function getProgressPercent()
    {
        return $this->percent;
    }

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = (int) $size;
    }

    /**
     * Gets the progress bar width.
     *
     * @return int The progress bar size
     */
    public function getBarWidth()
    {
        return $this->barWidth;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Gets the bar character.
     *
     * @return string A character
     */
    public function getBarCharacter()
    {
        if (null === $this->barChar) {
            return $this->max ? '=' : $this->emptyBarChar;
        }

        return $this->barChar;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Gets the empty bar character.
     *
     * @return string A character
     */
    public function getEmptyBarCharacter()
    {
        return $this->emptyBarChar;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Gets the progress bar character.
     *
     * @return string A character
     */
    public function getProgressCharacter()
    {
        return $this->progressChar;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|float $freq The frequency in steps
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = max((int) $freq, 1);
    }

    /**
     * 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($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
     *
     * @throws LogicException
     */
    public function advance($step = 1)
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets the current progress.
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead.
     *
     * @param int $step The current progress
     *
     * @throws LogicException
     */
    public function setCurrent($step)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED);

        $this->setProgress($step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     *
     * @param bool $overwrite
     */
    public function setOverwrite($overwrite)
    {
        $this->overwrite = (bool) $overwrite;
    }

    /**
     * Sets the current progress.
     *
     * @param int $step The current progress
     *
     * @throws LogicException
     */
    public function setProgress($step)
    {
        $step = (int) $step;
        if ($step < $this->step) {
            throw new LogicException('You can\'t regress the progress bar.');
        }

        if ($this->max && $step > $this->max) {
            $this->max = $step;
        }

        $prevPeriod = (int) ($this->step / $this->redrawFreq);
        $currPeriod = (int) ($step / $this->redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        if ($prevPeriod !== $currPeriod || $this->max === $step) {
            $this->display();
        }
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        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()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
        $self = $this;
        $output = $this->output;
        $messages = $this->messages;
        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) {
            if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
                $text = call_user_func($formatter, $self, $output);
            } elseif (isset($messages[$matches[1]])) {
                $text = $messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = sprintf('%'.$matches[2], $text);
            }

            return $text;
        }, $this->format));
    }

    /**
     * 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()
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    private function setRealFormat($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");
    }

    /**
     * Sets the progress bar maximal steps.
     *
     * @param int $max The progress bar max steps
     */
    private function setMaxSteps($max)
    {
        $this->max = max(0, (int) $max);
        $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->overwrite) {
            if (!$this->firstRun) {
                // Move the cursor to the beginning of the line
                $this->output->write("\x0D");

                // Erase the line
                $this->output->write("\x1B[2K");

                // Erase previous lines
                if ($this->formatLineCount > 0) {
                    $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
                }
            }
        } elseif ($this->step > 0) {
            $this->output->writeln('');
        }

        $this->firstRun = false;

        $this->output->write($message);
    }

    private function determineBestFormat()
    {
        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()
    {
        return array(
            'bar' => function (ProgressBar $bar, OutputInterface $output) {
                $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
                $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 (ProgressBar $bar) {
                return Helper::formatTime(time() - $bar->getStartTime());
            },
            'remaining' => function (ProgressBar $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 (ProgressBar $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 (ProgressBar $bar) {
                return Helper::formatMemory(memory_get_usage(true));
            },
            'current' => function (ProgressBar $bar) {
                return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
            },
            'max' => function (ProgressBar $bar) {
                return $bar->getMaxSteps();
            },
            'percent' => function (ProgressBar $bar) {
                return floor($bar->getProgressPercent() * 100);
            },
        );
    }

    private static function initFormats()
    {
        return array(
            '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%',
        );
    }
}
<?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\NullOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\LogicException;

/**
 * The Progress class provides helpers to display progress output.
 *
 * @author Chris Jones <leeked@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0
 *             Use {@link ProgressBar} instead.
 */
class ProgressHelper extends Helper
{
    const FORMAT_QUIET = ' %percent%%';
    const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
    const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
    const FORMAT_QUIET_NOMAX = ' %current%';
    const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
    const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';

    // options
    private $barWidth = 28;
    private $barChar = '=';
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format = null;
    private $redrawFreq = 1;

    private $lastMessagesLength;
    private $barCharOriginal;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Current step.
     *
     * @var int
     */
    private $current;

    /**
     * Maximum number of steps.
     *
     * @var int
     */
    private $max;

    /**
     * Start time of the progress bar.
     *
     * @var int
     */
    private $startTime;

    /**
     * List of formatting variables.
     *
     * @var array
     */
    private $defaultFormatVars = array(
        'current',
        'max',
        'bar',
        'percent',
        'elapsed',
    );

    /**
     * Available formatting variables.
     *
     * @var array
     */
    private $formatVars;

    /**
     * Stored format part widths (used for padding).
     *
     * @var array
     */
    private $widths = array(
        'current' => 4,
        'max' => 4,
        'percent' => 3,
        'elapsed' => 6,
    );

    /**
     * Various time formats.
     *
     * @var array
     */
    private $timeFormats = array(
        array(0, '???'),
        array(2, '1 sec'),
        array(59, 'secs', 1),
        array(60, '1 min'),
        array(3600, 'mins', 60),
        array(5400, '1 hr'),
        array(86400, 'hrs', 3600),
        array(129600, '1 day'),
        array(604800, 'days', 86400),
    );

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED);
        }
    }

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = (int) $size;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int $freq The frequency in steps
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = (int) $freq;
    }

    /**
     * Starts the progress output.
     *
     * @param OutputInterface $output An Output instance
     * @param int|null        $max    Maximum steps
     */
    public function start(OutputInterface $output, $max = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->startTime = time();
        $this->current = 0;
        $this->max = (int) $max;

        // Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
        $this->output = $output->isDecorated() ? $output : new NullOutput();
        $this->lastMessagesLength = 0;
        $this->barCharOriginal = '';

        if (null === $this->format) {
            switch ($output->getVerbosity()) {
                case OutputInterface::VERBOSITY_QUIET:
                    $this->format = self::FORMAT_QUIET_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_QUIET;
                    }
                    break;
                case OutputInterface::VERBOSITY_VERBOSE:
                case OutputInterface::VERBOSITY_VERY_VERBOSE:
                case OutputInterface::VERBOSITY_DEBUG:
                    $this->format = self::FORMAT_VERBOSE_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_VERBOSE;
                    }
                    break;
                default:
                    $this->format = self::FORMAT_NORMAL_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_NORMAL;
                    }
                    break;
            }
        }

        $this->initialize();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int  $step   Number of steps to advance
     * @param bool $redraw Whether to redraw or not
     *
     * @throws LogicException
     */
    public function advance($step = 1, $redraw = false)
    {
        $this->setCurrent($this->current + $step, $redraw);
    }

    /**
     * Sets the current progress.
     *
     * @param int  $current The current progress
     * @param bool $redraw  Whether to redraw or not
     *
     * @throws LogicException
     */
    public function setCurrent($current, $redraw = false)
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling setCurrent().');
        }

        $current = (int) $current;

        if ($current < $this->current) {
            throw new LogicException('You can\'t regress the progress bar');
        }

        if (0 === $this->current) {
            $redraw = true;
        }

        $prevPeriod = (int) ($this->current / $this->redrawFreq);

        $this->current = $current;

        $currPeriod = (int) ($this->current / $this->redrawFreq);
        if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) {
            $this->display();
        }
    }

    /**
     * Outputs the current progress string.
     *
     * @param bool $finish Forces the end result
     *
     * @throws LogicException
     */
    public function display($finish = false)
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling display().');
        }

        $message = $this->format;
        foreach ($this->generate($finish) as $name => $value) {
            $message = str_replace("%{$name}%", $value, $message);
        }
        $this->overwrite($this->output, $message);
    }

    /**
     * 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()
    {
        $this->overwrite($this->output, '');
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling finish().');
        }

        if (null !== $this->startTime) {
            if (!$this->max) {
                $this->barChar = $this->barCharOriginal;
                $this->display(true);
            }
            $this->startTime = null;
            $this->output->writeln('');
            $this->output = null;
        }
    }

    /**
     * Initializes the progress helper.
     */
    private function initialize()
    {
        $this->formatVars = array();
        foreach ($this->defaultFormatVars as $var) {
            if (false !== strpos($this->format, "%{$var}%")) {
                $this->formatVars[$var] = true;
            }
        }

        if ($this->max > 0) {
            $this->widths['max'] = $this->strlen($this->max);
            $this->widths['current'] = $this->widths['max'];
        } else {
            $this->barCharOriginal = $this->barChar;
            $this->barChar = $this->emptyBarChar;
        }
    }

    /**
     * Generates the array map of format variables to values.
     *
     * @param bool $finish Forces the end result
     *
     * @return array Array of format vars and values
     */
    private function generate($finish = false)
    {
        $vars = array();
        $percent = 0;
        if ($this->max > 0) {
            $percent = (float) $this->current / $this->max;
        }

        if (isset($this->formatVars['bar'])) {
            if ($this->max > 0) {
                $completeBars = floor($percent * $this->barWidth);
            } else {
                if (!$finish) {
                    $completeBars = floor($this->current % $this->barWidth);
                } else {
                    $completeBars = $this->barWidth;
                }
            }

            $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar);
            $bar = str_repeat($this->barChar, $completeBars);
            if ($completeBars < $this->barWidth) {
                $bar .= $this->progressChar;
                $bar .= str_repeat($this->emptyBarChar, $emptyBars);
            }

            $vars['bar'] = $bar;
        }

        if (isset($this->formatVars['elapsed'])) {
            $elapsed = time() - $this->startTime;
            $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['current'])) {
            $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['max'])) {
            $vars['max'] = $this->max;
        }

        if (isset($this->formatVars['percent'])) {
            $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT);
        }

        return $vars;
    }

    /**
     * Converts seconds into human-readable format.
     *
     * @param int $secs Number of seconds
     *
     * @return string Time in readable format
     */
    private function humaneTime($secs)
    {
        $text = '';
        foreach ($this->timeFormats as $format) {
            if ($secs < $format[0]) {
                if (2 == count($format)) {
                    $text = $format[1];
                    break;
                } else {
                    $text = ceil($secs / $format[2]).' '.$format[1];
                    break;
                }
            }
        }

        return $text;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param OutputInterface $output  An Output instance
     * @param string          $message The message
     */
    private function overwrite(OutputInterface $output, $message)
    {
        $length = $this->strlen($message);

        // append whitespace to match the last line's length
        if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
            $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
        }

        // carriage return
        $output->write("\x0D");
        $output->write($message);

        $this->lastMessagesLength = $this->strlen($message);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'progress';
    }
}
<?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 OutputInterface $output
     * @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, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null)
    {
        $this->output = $output;

        if (null === $format) {
            $format = $this->determineBestFormat();
        }

        if (null === $indicatorValues) {
            $indicatorValues = array('-', '\\', '|', '/');
        }

        $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();
    }

    /**
     * Gets the current indicator message.
     *
     * @return string|null
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Gets the current animated indicator character.
     *
     * @return string
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getCurrentValue()
    {
        return $this->indicatorValues[$this->indicatorCurrent % count($this->indicatorValues)];
    }

    /**
     * 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;
        }

        $self = $this;

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
            if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
                return call_user_func($formatter, $self);
            }

            return $matches[0];
        }, $this->format));
    }

    private function determineBestFormat()
    {
        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.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds()
    {
        return round(microtime(true) * 1000);
    }

    private static function initPlaceholderFormatters()
    {
        return array(
            'indicator' => function (ProgressIndicator $indicator) {
                return $indicator->getCurrentValue();
            },
            'message' => function (ProgressIndicator $indicator) {
                return $indicator->getMessage();
            },
            'elapsed' => function (ProgressIndicator $indicator) {
                return Helper::formatTime(time() - $indicator->getStartTime());
            },
            'memory' => function () {
                return Helper::formatMemory(memory_get_usage(true));
            },
        );
    }

    private static function initFormats()
    {
        return array(
            '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\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ChoiceQuestion;

/**
 * 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.
     *
     * @param InputInterface  $input    An InputInterface instance
     * @param OutputInterface $output   An OutputInterface instance
     * @param Question        $question The question to ask
     *
     * @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()) {
            return $question->getDefault();
        }

        if (!$question->getValidator()) {
            return $this->doAsk($output, $question);
        }

        $that = $this;

        $interviewer = function () use ($output, $question, $that) {
            return $that->doAsk($output, $question);
        };

        return $this->validateAttempts($interviewer, $output, $question);
    }

    /**
     * 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
     *
     * @throws InvalidArgumentException In case the stream is not a resource
     */
    public function setInputStream($stream)
    {
        if (!is_resource($stream)) {
            throw new InvalidArgumentException('Input stream must be a valid resource.');
        }

        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @return resource
     */
    public function getInputStream()
    {
        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'question';
    }

    /**
     * Asks the question to the user.
     *
     * This method is public for PHP 5.3 compatibility, it should be private.
     *
     * @param OutputInterface $output
     * @param Question        $question
     *
     * @return bool|mixed|null|string
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    public function doAsk(OutputInterface $output, Question $question)
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: STDIN;
        $autocomplete = $question->getAutocompleterValues();

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $ret = trim($this->getHiddenResponse($output, $inputStream));
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $ret = fgets($inputStream, 4096);
                if (false === $ret) {
                    throw new RuntimeException('Aborted');
                }
                $ret = trim($ret);
            }
        } else {
            $ret = trim($this->autocomplete($output, $question, $inputStream));
        }

        $ret = strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    /**
     * Outputs the question prompt.
     *
     * @param OutputInterface $output
     * @param Question        $question
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));

            $messages = (array) $question->getQuestion();
            foreach ($question->getChoices() as $key => $value) {
                $width = $maxWidth - $this->strlen($key);
                $messages[] = '  [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
            }

            $output->writeln($messages);

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * Outputs an error message.
     *
     * @param OutputInterface $output
     * @param \Exception      $error
     */
    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 OutputInterface $output
     * @param Question        $question
     * @param resource        $inputStream
     *
     * @return string
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream)
    {
        $autocomplete = $question->getAutocompleterValues();
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete;
        $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);

            // Backspace Character
            if ("\177" === $c) {
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    // Move cursor backwards
                    $output->write("\033[1D");
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete;
                    $numMatches = count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = 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 = $matches[$ofs];
                        // Echo out remaining chars for current match
                        $output->write(substr($ret, $i));
                        $i = strlen($ret);
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                $output->write($c);
                $ret .= $c;
                ++$i;

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (0 === strpos($value, $ret) && $i !== strlen($value)) {
                        $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
                $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
                // Restore cursor position
                $output->write("\0338");
            }
        }

        // Reset stty so it behaves normally again
        shell_exec(sprintf('stty %s', $sttyMode));

        return $ret;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param OutputInterface $output      An Output instance
     * @param resource        $inputStream The handler resource
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream)
    {
        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;
            }

            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->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');
            }

            $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);
            $value = rtrim(shell_exec($command));
            $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
     * @param OutputInterface $output      An Output instance
     * @param Question        $question    A Question instance
     *
     * @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($interviewer, OutputInterface $output, Question $question)
    {
        $error = null;
        $attempts = $question->getMaxAttempts();
        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return call_user_func($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 (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    /**
     * Returns whether Stty is available or not.
     *
     * @return bool
     */
    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $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\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
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;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * {@inheritdoc}
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        $validator = $question->getValidator();
        $question->setValidator(function ($value) use ($validator) {
            if (null !== $validator) {
                $value = $validator($value);
            } else {
                // make required
                if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) {
                    throw new LogicException('A value is required.');
                }
            }

            return $value;
        });

        return parent::ask($input, $output, $question);
    }

    /**
     * {@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($choices[$default]));

                break;

            default:
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        if ($question instanceof ChoiceQuestion) {
            $width = max(array_map('strlen', array_keys($question->getChoices())));

            foreach ($question->getChoices() as $key => $value) {
                $output->writeln(sprintf("  [<comment>%-${width}s</comment>] %s", $key, $value));
            }
        }

        $output->write(' > ');
    }

    /**
     * {@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;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * 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>
 */
class Table
{
    /**
     * Table headers.
     *
     * @var array
     */
    private $headers = array();

    /**
     * Table rows.
     *
     * @var array
     */
    private $rows = array();

    /**
     * Column widths cache.
     *
     * @var array
     */
    private $columnWidths = array();

    /**
     * Number of columns cache.
     *
     * @var array
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var TableStyle
     */
    private $style;

    /**
     * @var array
     */
    private $columnStyles = array();

    private static $styles;

    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
     * @param TableStyle $style A TableStyle instance
     */
    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)
    {
        if (isset($this->columnStyles[$columnIndex])) {
            return $this->columnStyles[$columnIndex];
        }

        return $this->getStyle();
    }

    public function setHeaders(array $headers)
    {
        $headers = array_values($headers);
        if (!empty($headers) && !is_array($headers[0])) {
            $headers = array($headers);
        }

        $this->headers = $headers;

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->rows = array();

        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;
    }

    public function setRow($column, array $row)
    {
        $this->rows[$column] = $row;

        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()
    {
        $this->calculateNumberOfColumns();
        $rows = $this->buildTableRows($this->rows);
        $headers = $this->buildTableRows($this->headers);

        $this->calculateColumnsWidth(array_merge($headers, $rows));

        $this->renderRowSeparator();
        if (!empty($headers)) {
            foreach ($headers as $header) {
                $this->renderRow($header, $this->style->getCellHeaderFormat());
                $this->renderRowSeparator();
            }
        }
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                $this->renderRowSeparator();
            } else {
                $this->renderRow($row, $this->style->getCellRowFormat());
            }
        }
        if (!empty($rows)) {
            $this->renderRowSeparator();
        }

        $this->cleanup();
    }

    /**
     * Renders horizontal header separator.
     *
     * Example: +-----+-----------+-------+
     */
    private function renderRowSeparator()
    {
        if (0 === $count = $this->numberOfColumns) {
            return;
        }

        if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
            return;
        }

        $markup = $this->style->getCrossingChar();
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->columnWidths[$column]).$this->style->getCrossingChar();
        }

        $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator()
    {
        return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
    }

    /**
     * Renders table row.
     *
     * Example: | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *
     * @param array  $row
     * @param string $cellFormat
     */
    private function renderRow(array $row, $cellFormat)
    {
        if (empty($row)) {
            return;
        }

        $rowContent = $this->renderColumnSeparator();
        foreach ($this->getRowColumns($row) as $column) {
            $rowContent .= $this->renderCell($row, $column, $cellFormat);
            $rowContent .= $this->renderColumnSeparator();
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     *
     * @param array  $row
     * @param int    $column
     * @param string $cellFormat
     */
    private function renderCell(array $row, $column, $cellFormat)
    {
        $cell = isset($row[$column]) ? $row[$column] : '';
        $width = $this->columnWidths[$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->columnWidths[$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->getHorizontalBorderChar(), $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()
    {
        if (null !== $this->numberOfColumns) {
            return;
        }

        $columns = array(0);
        foreach (array_merge($this->headers, $this->rows) as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows($rows)
    {
        $unmergedRows = array();
        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) {
                if (!strstr($cell, "\n")) {
                    continue;
                }
                $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($cell instanceof TableCell) {
                        $line = new TableCell($line, array('colspan' => $cell->getColspan()));
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        $tableRows = array();
        foreach ($rows as $rowKey => $row) {
            $tableRows[] = $this->fillCells($row);
            if (isset($unmergedRows[$rowKey])) {
                $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
            }
        }

        return $tableRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @param array $rows
     * @param int   $line
     *
     * @return array
     */
    private function fillNextRows($rows, $line)
    {
        $unmergedRows = array();
        foreach ($rows[$line] as $column => $cell) {
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = array($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], array('colspan' => $cell->getColspan()));
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('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, array($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, array($row));
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     *
     * @param array $row
     *
     * @return array
     */
    private function fillCells($row)
    {
        $newRow = array();
        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;
    }

    /**
     * @param array $rows
     * @param int   $line
     *
     * @return array
     */
    private function copyRow($rows, $line)
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan()));
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     *
     * @param array $row
     *
     * @return int
     */
    private function getNumberOfColumns(array $row)
    {
        $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.
     *
     * @param array $row
     *
     * @return array
     */
    private function getRowColumns($row)
    {
        $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.
     *
     * @param array $rows
     */
    private function calculateColumnsWidth($rows)
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = array();
            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->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2;
        }
    }

    /**
     * Gets column width.
     *
     * @return int
     */
    private function getColumnSeparatorWidth()
    {
        return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
    }

    /**
     * Gets cell width.
     *
     * @param array $row
     * @param int   $column
     *
     * @return int
     */
    private function getCellWidth(array $row, $column)
    {
        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);

            return $cellWidth;
        }

        return 0;
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->columnWidths = array();
        $this->numberOfColumns = null;
    }

    private static function initStyles()
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChar('=')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChar('')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar('')
            ->setCellRowContentFormat('%s')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChar('-')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        return array(
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
        );
    }

    private function resolveStyle($name)
    {
        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;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    /**
     * @var string
     */
    private $value;

    /**
     * @var array
     */
    private $options = array(
        'rowspan' => 1,
        'colspan' => 1,
    );

    /**
     * @param string $value
     * @param array  $options
     */
    public function __construct($value = '', array $options = array())
    {
        if (is_numeric($value) && !is_string($value)) {
            $value = (string) $value;
        }

        $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\OutputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Provides helpers to display table output.
 *
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0
 *             Use {@link Table} instead.
 */
class TableHelper extends Helper
{
    const LAYOUT_DEFAULT = 0;
    const LAYOUT_BORDERLESS = 1;
    const LAYOUT_COMPACT = 2;

    /**
     * @var Table
     */
    private $table;

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED);
        }

        $this->table = new Table(new NullOutput());
    }

    /**
     * Sets table layout type.
     *
     * @param int $layout self::LAYOUT_*
     *
     * @return $this
     *
     * @throws InvalidArgumentException when the table layout is not known
     */
    public function setLayout($layout)
    {
        switch ($layout) {
            case self::LAYOUT_BORDERLESS:
                $this->table->setStyle('borderless');
                break;

            case self::LAYOUT_COMPACT:
                $this->table->setStyle('compact');
                break;

            case self::LAYOUT_DEFAULT:
                $this->table->setStyle('default');
                break;

            default:
                throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout));
        }

        return $this;
    }

    public function setHeaders(array $headers)
    {
        $this->table->setHeaders($headers);

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->table->setRows($rows);

        return $this;
    }

    public function addRows(array $rows)
    {
        $this->table->addRows($rows);

        return $this;
    }

    public function addRow(array $row)
    {
        $this->table->addRow($row);

        return $this;
    }

    public function setRow($column, array $row)
    {
        $this->table->setRow($column, $row);

        return $this;
    }

    /**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return $this
     */
    public function setPaddingChar($paddingChar)
    {
        $this->table->getStyle()->setPaddingChar($paddingChar);

        return $this;
    }

    /**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar);

        return $this;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar);

        return $this;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    public function setCrossingChar($crossingChar)
    {
        $this->table->getStyle()->setCrossingChar($crossingChar);

        return $this;
    }

    /**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return $this
     */
    public function setCellHeaderFormat($cellHeaderFormat)
    {
        $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat);

        return $this;
    }

    /**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return $this
     */
    public function setCellRowFormat($cellRowFormat)
    {
        $this->table->getStyle()->setCellHeaderFormat($cellRowFormat);

        return $this;
    }

    /**
     * Sets row cell content format.
     *
     * @param string $cellRowContentFormat
     *
     * @return $this
     */
    public function setCellRowContentFormat($cellRowContentFormat)
    {
        $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat);

        return $this;
    }

    /**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return $this
     */
    public function setBorderFormat($borderFormat)
    {
        $this->table->getStyle()->setBorderFormat($borderFormat);

        return $this;
    }

    /**
     * Sets cell padding type.
     *
     * @param int $padType STR_PAD_*
     *
     * @return $this
     */
    public function setPadType($padType)
    {
        $this->table->getStyle()->setPadType($padType);

        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 |
     * +---------------+-----------------------+------------------+
     *
     * @param OutputInterface $output
     */
    public function render(OutputInterface $output)
    {
        $p = new \ReflectionProperty($this->table, 'output');
        $p->setAccessible(true);
        $p->setValue($this->table, $output);

        $this->table->render();
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'table';
    }
}
<?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
{
    /**
     * @param array $options
     */
    public function __construct(array $options = array())
    {
        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\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 */
class TableStyle
{
    private $paddingChar = ' ';
    private $horizontalBorderChar = '-';
    private $verticalBorderChar = '|';
    private $crossingChar = '+';
    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 character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->horizontalBorderChar = $horizontalBorderChar;

        return $this;
    }

    /**
     * Gets horizontal border character.
     *
     * @return string
     */
    public function getHorizontalBorderChar()
    {
        return $this->horizontalBorderChar;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->verticalBorderChar = $verticalBorderChar;

        return $this;
    }

    /**
     * Gets vertical border character.
     *
     * @return string
     */
    public function getVerticalBorderChar()
    {
        return $this->verticalBorderChar;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    public function setCrossingChar($crossingChar)
    {
        $this->crossingChar = $crossingChar;

        return $this;
    }

    /**
     * Gets crossing character.
     *
     * @return string $crossingChar
     */
    public function getCrossingChar()
    {
        return $this->crossingChar;
    }

    /**
     * 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, array(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;
    }
}
<?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)
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    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.
     *
     * @param string $token The current token
     */
    private function parseShortOption($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.
     *
     * @param string $name The current token
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet($name)
    {
        $len = strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
            }

            $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.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if (0 === strlen($value = substr($name, $pos + 1))) {
                array_unshift($this->parsed, null);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @param string $token The current token
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument($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() ? array($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.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption($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.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        // Convert empty values to null
        if (!isset($value[0])) {
            $value = null;
        }

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (null === $value && $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]) {
                $value = $next;
            } elseif (empty($next)) {
                $value = null;
            } 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()) {
                $value = $option->isValueOptional() ? $option->getDefault() : true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->tokens as $token) {
            if ($token && '-' === $token[0]) {
                continue;
            }

            return $token;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            foreach ($values as $value) {
                if ($token === $value || 0 === strpos($token, $value.'=')) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < count($tokens)) {
            $token = array_shift($tokens);

            foreach ($values as $value) {
                if ($token === $value || 0 === strpos($token, $value.'=')) {
                    if (false !== $pos = strpos($token, '=')) {
                        return substr($token, $pos + 1);
                    }

                    return array_shift($tokens);
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $self = $this;
        $tokens = array_map(function ($token) use ($self) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$self->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $self->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\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private $parameters;

    /**
     * @param array                $parameters An array of parameters
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $key => $value) {
            if ($key && '-' === $key[0]) {
                continue;
            }

            return $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!is_int($k)) {
                $v = $k;
            }

            if (in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            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 = array();
        foreach ($this->parameters as $param => $val) {
            if ($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) ? array_map(array($this, 'escapeToken'), $val) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if (0 === strpos($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif ('-' === $key[0]) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption($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.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption($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));
            }

            $value = $option->isValueOptional() ? $option->getDefault() : true;
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @param string $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\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
{
    /**
     * @var InputDefinition
     */
    protected $definition;
    protected $options = array();
    protected $arguments = array();
    protected $interactive = true;

    /**
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    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 = array();
        $this->options = array();
        $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 isset($this->options[$name]) ? $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);
    }
}
<?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    $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string $description A description text
     * @param mixed  $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct($name, $mode = null, $description = '', $default = null)
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif (!is_int($mode) || $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 mixed $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 = array();
            } 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 mixed 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.
     *
     * @param InputInterface
     */
    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\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Output\BufferedOutput;
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(array(
 *       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 = array())
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     *
     * @param array $definition The definition array
     */
    public function setDefinition(array $definition)
    {
        $arguments = array();
        $options = array();
        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 = array())
    {
        $this->arguments = array();
        $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 = array())
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * Adds an InputArgument object.
     *
     * @param InputArgument $argument An InputArgument object
     *
     * @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 = array();
        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 = array())
    {
        $this->options = array();
        $this->shortcuts = array();
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function addOptions($options = array())
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * Adds an InputOption object.
     *
     * @param InputOption $option An InputOption object
     *
     * @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 = array();
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @param string $shortcut The shortcut
     *
     * @return string The InputOption name
     *
     * @throws InvalidArgumentException When option given does not exist
     */
    private function shortcutToName($shortcut)
    {
        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 = array();

        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[] = '[--]';
        }

        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if (!$argument->isRequired()) {
                $element = '['.$element.']';
            } elseif ($argument->isArray()) {
                $element = $element.' ('.$element.')';
            }

            if ($argument->isArray()) {
                $element .= '...';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements);
    }

    /**
     * Returns a textual representation of the InputDefinition.
     *
     * @return string A string representing the InputDefinition
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
        $descriptor->describe($output, $this, array('raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the InputDefinition.
     *
     * @param bool $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the InputDefinition
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getInputDefinitionDocument($this);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this);

        return $output->fetch();
    }
}
<?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 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.
     *
     * @param string|array $values The values to look for in the raw parameters (can be an array)
     *
     * @return bool true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values);

    /**
     * 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.
     *
     * @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
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @param InputDefinition $definition A InputDefinition instance
     */
    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 mixed 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 $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 mixed 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|bool $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\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 $shortcut    The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int          $mode        The option mode: One of the VALUE_* constants
     * @param string       $description A description text
     * @param mixed        $default     The default value (must be null for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct($name, $shortcut = null, $mode = null, $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 (!is_int($mode) || $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 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 mixed $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 = array();
            } 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 mixed 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.
     *
     * @param InputOption $option option to compare
     *
     * @return bool
     */
    public function equals(InputOption $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;

/**
 * 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      An array of parameters from the CLI (in the argv format)
     * @param InputDefinition $definition A InputDefinition instance
     *
     * @deprecated The second argument is deprecated as it does not work (will be removed in 3.0), use 'bind' method instead
     */
    public function __construct($input, InputDefinition $definition = null)
    {
        if ($definition) {
            @trigger_error('The $definition argument of the '.__METHOD__.' method is deprecated and will be removed in 3.0. Set this parameter with the bind() method instead.', E_USER_DEPRECATED);
        }

        parent::__construct(array(), null);

        $this->setTokens($this->tokenize($input));

        if (null !== $definition) {
            $this->bind($definition);
        }
    }

    /**
     * Tokenizes a string.
     *
     * @param string $input The input to tokenize
     *
     * @return array An array of tokens
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize($input)
    {
        $tokens = array();
        $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(array('"\'', '\'"', '\'\'', '""'), '', 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\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see http://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    const INFO = 'info';
    const ERROR = 'error';

    /**
     * @var OutputInterface
     */
    private $output;
    /**
     * @var array
     */
    private $verbosityLevelMap = array(
        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,
    );
    /**
     * @var array
     */
    private $formatLevelMap = array(
        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,
    );

    /**
     * @param OutputInterface $output
     * @param array           $verbosityLevelMap
     * @param array           $formatLevelMap
     */
    public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array())
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = array())
    {
        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
        if (self::ERROR === $this->formatLevelMap[$level] && $this->output instanceof ConsoleOutputInterface) {
            $output = $this->output->getErrorOutput();
        } else {
            $output = $this->output;
        }

        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)));
        }
    }

    /**
     * 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

/*
 * 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
{
    /**
     * @var string
     */
    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;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT.
 *
 * This class is a convenient wrapper around `StreamOutput`.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    /**
     * @var StreamOutput
     */
    private $stderr;

    /**
     * @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($verbosity = self::VERBOSITY_NORMAL, $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());
        }
    }

    /**
     * {@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.
     *
     * @return bool
     */
    private function isRunningOS400()
    {
        $checks = array(
            function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            PHP_OS,
        );

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output';

        return @fopen($outputStream, 'w') ?: fopen('php://output', 'w');
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output';

        return fopen($errorStream, '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;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     *
     * @return OutputInterface
     */
    public function getErrorOutput();

    /**
     * Sets the OutputInterface used for errors.
     *
     * @param OutputInterface $error
     */
    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;

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\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * 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($verbosity = self::VERBOSITY_NORMAL, $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)
    {
        $messages = (array) $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;

/**
 * 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|array $messages The message as an array of lines 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|array $messages The message as an array of lines of 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();

    /**
     * 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();

    /**
     * Sets output formatter.
     *
     * @param OutputFormatterInterface $formatter
     */
    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\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, $verbosity = self::VERBOSITY_NORMAL, $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 (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) {
            // 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:
     *
     *  -  Windows != 10.0.10586 without Ansicon, ConEmu or Mintty
     *  -  non tty consoles
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport()
    {
        if (DIRECTORY_SEPARATOR === '\\') {
            return
                '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        return function_exists('posix_isatty') && @posix_isatty($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\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($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;
    }

    /**
     * Returns the default answer validator.
     *
     * @return callable
     */
    private function getDefaultValidator()
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $selected);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = array($selected);
            }

            $multiselectChoices = array();
            foreach ($selectedChoices as $value) {
                $results = array();
                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;

/**
 * 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($question, $default = true, $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, (bool) $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    private function getDefaultNormalizer()
    {
        $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\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 $autocompleterValues;
    private $validator;
    private $default;
    private $normalizer;

    /**
     * @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($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->autocompleterValues) {
            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 null|array|\Traversable
     */
    public function getAutocompleterValues()
    {
        return $this->autocompleterValues;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @param null|array|\Traversable $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);
        }

        if (null !== $values && !is_array($values)) {
            if (!$values instanceof \Traversable || !$values instanceof \Countable) {
                throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
            }
        }

        if ($this->hidden) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterValues = $values;

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @param null|callable $validator
     *
     * @return $this
     */
    public function setValidator($validator)
    {
        $this->validator = $validator;

        return $this;
    }

    /**
     * Gets the validator for the question.
     *
     * @return null|callable
     */
    public function getValidator()
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @param null|int $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 null|int
     */
    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.
     *
     * @param callable $normalizer
     *
     * @return $this
     */
    public function setNormalizer($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
     */
    public function getNormalizer()
    {
        return $this->normalizer;
    }

    protected function isAssoc($array)
    {
        return (bool) count(array_filter(array_keys($array), 'is_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;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\PhpExecutableFinder;

/**
 * A Shell wraps an Application to add shell capabilities to it.
 *
 * Support for history and completion only works with a PHP compiled
 * with readline support (either --with-readline or --with-libedit)
 *
 * @deprecated since version 2.8, to be removed in 3.0.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class Shell
{
    private $application;
    private $history;
    private $output;
    private $hasReadline;
    private $processIsolation = false;

    /**
     * If there is no readline support for the current PHP executable
     * a \RuntimeException exception is thrown.
     *
     * @param Application $application An application instance
     */
    public function __construct(Application $application)
    {
        @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->hasReadline = function_exists('readline');
        $this->application = $application;
        $this->history = getenv('HOME').'/.history_'.$application->getName();
        $this->output = new ConsoleOutput();
    }

    /**
     * Runs the shell.
     */
    public function run()
    {
        $this->application->setAutoExit(false);
        $this->application->setCatchExceptions(true);

        if ($this->hasReadline) {
            readline_read_history($this->history);
            readline_completion_function(array($this, 'autocompleter'));
        }

        $this->output->writeln($this->getHeader());
        $php = null;
        if ($this->processIsolation) {
            $finder = new PhpExecutableFinder();
            $php = $finder->find();
            $this->output->writeln(<<<'EOF'
<info>Running with process isolation, you should consider this:</info>
  * each command is executed as separate process,
  * commands don't support interactivity, all params must be passed explicitly,
  * commands output is not colorized.

EOF
            );
        }

        while (true) {
            $command = $this->readline();

            if (false === $command) {
                $this->output->writeln("\n");

                break;
            }

            if ($this->hasReadline) {
                readline_add_history($command);
                readline_write_history($this->history);
            }

            if ($this->processIsolation) {
                $pb = new ProcessBuilder();

                $process = $pb
                    ->add($php)
                    ->add($_SERVER['argv'][0])
                    ->add($command)
                    ->inheritEnvironmentVariables(true)
                    ->getProcess()
                ;

                $output = $this->output;
                $process->run(function ($type, $data) use ($output) {
                    $output->writeln($data);
                });

                $ret = $process->getExitCode();
            } else {
                $ret = $this->application->run(new StringInput($command), $this->output);
            }

            if (0 !== $ret) {
                $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
            }
        }
    }

    /**
     * Returns the shell header.
     *
     * @return string The header string
     */
    protected function getHeader()
    {
        return <<<EOF

Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).

At the prompt, type <comment>help</comment> for some help,
or <comment>list</comment> to get a list of available commands.

To exit the shell, type <comment>^D</comment>.

EOF;
    }

    /**
     * Renders a prompt.
     *
     * @return string The prompt
     */
    protected function getPrompt()
    {
        // using the formatter here is required when using readline
        return $this->output->getFormatter()->format($this->application->getName().' > ');
    }

    protected function getOutput()
    {
        return $this->output;
    }

    protected function getApplication()
    {
        return $this->application;
    }

    /**
     * Tries to return autocompletion for the current entered text.
     *
     * @param string $text The last segment of the entered text
     *
     * @return bool|array A list of guessed strings or true
     */
    private function autocompleter($text)
    {
        $info = readline_info();
        $text = substr($info['line_buffer'], 0, $info['end']);

        if ($info['point'] !== $info['end']) {
            return true;
        }

        // task name?
        if (false === strpos($text, ' ') || !$text) {
            return array_keys($this->application->all());
        }

        // options and arguments?
        try {
            $command = $this->application->find(substr($text, 0, strpos($text, ' ')));
        } catch (\Exception $e) {
            return true;
        }

        $list = array('--help');
        foreach ($command->getDefinition()->getOptions() as $option) {
            $list[] = '--'.$option->getName();
        }

        return $list;
    }

    /**
     * Reads a single line from standard input.
     *
     * @return string The single line from standard input
     */
    private function readline()
    {
        if ($this->hasReadline) {
            $line = readline($this->getPrompt());
        } else {
            $this->output->write($this->getPrompt());
            $line = fgets(STDIN, 1024);
            $line = (false === $line || '' === $line) ? false : rtrim($line);
        }

        return $line;
    }

    public function getProcessIsolation()
    {
        return $this->processIsolation;
    }

    public function setProcessIsolation($processIsolation)
    {
        $this->processIsolation = (bool) $processIsolation;

        if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) {
            throw new RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.');
        }
    }
}
<?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\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private $output;

    /**
     * @param OutputInterface $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();
    }
}
<?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.
     *
     * @param array $elements
     */
    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.
     *
     * @param array $headers
     * @param array $rows
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     *
     * @param string        $question
     * @param string|null   $default
     * @param callable|null $validator
     *
     * @return string
     */
    public function ask($question, $default = null, $validator = null);

    /**
     * Asks a question with the user input hidden.
     *
     * @param string        $question
     * @param callable|null $validator
     *
     * @return string
     */
    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 array           $choices
     * @param string|int|null $default
     *
     * @return string
     */
    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\Style;

use Symfony\Component\Console\Application;
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\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;

/**
 * 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;

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     */
    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.
        $this->lineLength = min($this->getTerminalWidth() - (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
     */
    public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
    {
        $messages = is_array($messages) ? array_values($messages) : array($messages);

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function title($message)
    {
        $this->autoPrependBlock();
        $this->writeln(array(
            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(array(
            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) : array($message);
        foreach ($messages as $message) {
            $this->writeln(sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    public function comment($message)
    {
        $messages = is_array($message) ? array_values($message) : array($message);

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, null, null, '<fg=default;bg=default> // </>'));
        $this->newLine();
    }

    /**
     * {@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=white;bg=red', ' ', 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();
    }

    /**
     * {@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) {
            $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('▓'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @param Question $question
     *
     * @return string
     */
    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)
    {
        parent::writeln($messages, $type);
        $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        parent::write($messages, $newline, $type);
        $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function newLine($count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * @return ProgressBar
     */
    private function getProgressBar()
    {
        if (!$this->progressBar) {
            throw new RuntimeException('The ProgressBar is not started.');
        }

        return $this->progressBar;
    }

    private function getTerminalWidth()
    {
        $application = new Application();
        $dimensions = $application->getTerminalDimensions();

        return $dimensions[0] ?: self::MAX_LINE_LENGTH;
    }

    private function autoPrependBlock()
    {
        $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            return $this->newLine(); //empty history, so we should start with a new line.
        }
        //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()
    {
        $fetched = $this->bufferedOutput->fetch();
        //Prepend new line if last char isn't EOL:
        if ("\n" !== substr($fetched, -1)) {
            $this->newLine();
        }
    }

    private function reduceBuffer($messages)
    {
        // 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
        return array_map(function ($value) {
            return substr($value, -4);
        }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages));
    }

    private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false)
    {
        $indentLength = 0;
        $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
        $lines = array();

        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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts Amqp related classes to array representation.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class AmqpCaster
{
    private static $flags = array(
        AMQP_DURABLE => 'AMQP_DURABLE',
        AMQP_PASSIVE => 'AMQP_PASSIVE',
        AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE',
        AMQP_AUTODELETE => 'AMQP_AUTODELETE',
        AMQP_INTERNAL => 'AMQP_INTERNAL',
        AMQP_NOLOCAL => 'AMQP_NOLOCAL',
        AMQP_AUTOACK => 'AMQP_AUTOACK',
        AMQP_IFEMPTY => 'AMQP_IFEMPTY',
        AMQP_IFUNUSED => 'AMQP_IFUNUSED',
        AMQP_MANDATORY => 'AMQP_MANDATORY',
        AMQP_IMMEDIATE => 'AMQP_IMMEDIATE',
        AMQP_MULTIPLE => 'AMQP_MULTIPLE',
        AMQP_NOWAIT => 'AMQP_NOWAIT',
        AMQP_REQUEUE => 'AMQP_REQUEUE',
    );

    private static $exchangeTypes = array(
        AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT',
        AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT',
        AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC',
        AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS',
    );

    public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $a += array(
            $prefix.'is_connected' => $c->isConnected(),
        );

        // Recent version of the extension already expose private properties
        if (isset($a["\x00AMQPConnection\x00login"])) {
            return $a;
        }

        // BC layer in the amqp lib
        if (method_exists($c, 'getReadTimeout')) {
            $timeout = $c->getReadTimeout();
        } else {
            $timeout = $c->getTimeout();
        }

        $a += array(
            $prefix.'is_connected' => $c->isConnected(),
            $prefix.'login' => $c->getLogin(),
            $prefix.'password' => $c->getPassword(),
            $prefix.'host' => $c->getHost(),
            $prefix.'vhost' => $c->getVhost(),
            $prefix.'port' => $c->getPort(),
            $prefix.'read_timeout' => $timeout,
        );

        return $a;
    }

    public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $a += array(
            $prefix.'is_connected' => $c->isConnected(),
            $prefix.'channel_id' => $c->getChannelId(),
        );

        // Recent version of the extension already expose private properties
        if (isset($a["\x00AMQPChannel\x00connection"])) {
            return $a;
        }

        $a += array(
            $prefix.'connection' => $c->getConnection(),
            $prefix.'prefetch_size' => $c->getPrefetchSize(),
            $prefix.'prefetch_count' => $c->getPrefetchCount(),
        );

        return $a;
    }

    public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $a += array(
            $prefix.'flags' => self::extractFlags($c->getFlags()),
        );

        // Recent version of the extension already expose private properties
        if (isset($a["\x00AMQPQueue\x00name"])) {
            return $a;
        }

        $a += array(
            $prefix.'connection' => $c->getConnection(),
            $prefix.'channel' => $c->getChannel(),
            $prefix.'name' => $c->getName(),
            $prefix.'arguments' => $c->getArguments(),
        );

        return $a;
    }

    public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $a += array(
            $prefix.'flags' => self::extractFlags($c->getFlags()),
        );

        $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType();

        // Recent version of the extension already expose private properties
        if (isset($a["\x00AMQPExchange\x00name"])) {
            $a["\x00AMQPExchange\x00type"] = $type;

            return $a;
        }

        $a += array(
            $prefix.'connection' => $c->getConnection(),
            $prefix.'channel' => $c->getChannel(),
            $prefix.'name' => $c->getName(),
            $prefix.'type' => $type,
            $prefix.'arguments' => $c->getArguments(),
        );

        return $a;
    }

    public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode());

        // Recent version of the extension already expose private properties
        if (isset($a["\x00AMQPEnvelope\x00body"])) {
            $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode;

            return $a;
        }

        if (!($filter & Caster::EXCLUDE_VERBOSE)) {
            $a += array($prefix.'body' => $c->getBody());
        }

        $a += array(
            $prefix.'delivery_tag' => $c->getDeliveryTag(),
            $prefix.'is_redelivery' => $c->isRedelivery(),
            $prefix.'exchange_name' => $c->getExchangeName(),
            $prefix.'routing_key' => $c->getRoutingKey(),
            $prefix.'content_type' => $c->getContentType(),
            $prefix.'content_encoding' => $c->getContentEncoding(),
            $prefix.'headers' => $c->getHeaders(),
            $prefix.'delivery_mode' => $deliveryMode,
            $prefix.'priority' => $c->getPriority(),
            $prefix.'correlation_id' => $c->getCorrelationId(),
            $prefix.'reply_to' => $c->getReplyTo(),
            $prefix.'expiration' => $c->getExpiration(),
            $prefix.'message_id' => $c->getMessageId(),
            $prefix.'timestamp' => $c->getTimeStamp(),
            $prefix.'type' => $c->getType(),
            $prefix.'user_id' => $c->getUserId(),
            $prefix.'app_id' => $c->getAppId(),
        );

        return $a;
    }

    private static function extractFlags($flags)
    {
        $flagsArray = array();

        foreach (self::$flags as $value => $name) {
            if ($flags & $value) {
                $flagsArray[] = $name;
            }
        }

        if (!$flagsArray) {
            $flagsArray = array('AMQP_NOPARAM');
        }

        return new ConstStub(implode('|', $flagsArray), $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\VarDumper\Caster;

/**
 * Helper for filtering out properties in casters.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Caster
{
    const EXCLUDE_VERBOSE = 1;
    const EXCLUDE_VIRTUAL = 2;
    const EXCLUDE_DYNAMIC = 4;
    const EXCLUDE_PUBLIC = 8;
    const EXCLUDE_PROTECTED = 16;
    const EXCLUDE_PRIVATE = 32;
    const EXCLUDE_NULL = 64;
    const EXCLUDE_EMPTY = 128;
    const EXCLUDE_NOT_IMPORTANT = 256;
    const EXCLUDE_STRICT = 512;

    const PREFIX_VIRTUAL = "\0~\0";
    const PREFIX_DYNAMIC = "\0+\0";
    const PREFIX_PROTECTED = "\0*\0";

    /**
     * Casts objects to arrays and adds the dynamic property prefix.
     *
     * @param object           $obj       The object to cast
     * @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition
     *
     * @return array The array-cast of the object, with prefixed dynamic properties
     */
    public static function castObject($obj, \ReflectionClass $reflector)
    {
        if ($reflector->hasMethod('__debugInfo')) {
            $a = $obj->__debugInfo();
        } elseif ($obj instanceof \Closure) {
            $a = array();
        } else {
            $a = (array) $obj;
        }

        if ($a) {
            $p = array_keys($a);
            foreach ($p as $i => $k) {
                if (isset($k[0]) ? "\0" !== $k[0] && !$reflector->hasProperty($k) : \PHP_VERSION_ID >= 70200) {
                    $p[$i] = self::PREFIX_DYNAMIC.$k;
                } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) {
                    $p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0");
                }
            }
            $a = array_combine($p, $a);
        }

        return $a;
    }

    /**
     * Filters out the specified properties.
     *
     * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
     * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
     *
     * @param array    $a                The array containing the properties to filter
     * @param int      $filter           A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
     * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
     *
     * @return array The filtered array
     */
    public static function filter(array $a, $filter, array $listedProperties = array())
    {
        foreach ($a as $k => $v) {
            $type = self::EXCLUDE_STRICT & $filter;

            if (null === $v) {
                $type |= self::EXCLUDE_NULL & $filter;
            }
            if (empty($v)) {
                $type |= self::EXCLUDE_EMPTY & $filter;
            }
            if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) {
                $type |= self::EXCLUDE_NOT_IMPORTANT;
            }
            if ((self::EXCLUDE_VERBOSE & $filter) && in_array($k, $listedProperties, true)) {
                $type |= self::EXCLUDE_VERBOSE;
            }

            if (!isset($k[1]) || "\0" !== $k[0]) {
                $type |= self::EXCLUDE_PUBLIC & $filter;
            } elseif ('~' === $k[1]) {
                $type |= self::EXCLUDE_VIRTUAL & $filter;
            } elseif ('+' === $k[1]) {
                $type |= self::EXCLUDE_DYNAMIC & $filter;
            } elseif ('*' === $k[1]) {
                $type |= self::EXCLUDE_PROTECTED & $filter;
            } else {
                $type |= self::EXCLUDE_PRIVATE & $filter;
            }

            if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
                unset($a[$k]);
            }
        }

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Represents a PHP constant and its value.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ConstStub extends Stub
{
    public function __construct($name, $value)
    {
        $this->class = $name;
        $this->value = $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\VarDumper\Caster;

/**
 * Represents a cut array.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class CutArrayStub extends CutStub
{
    public $preservedSubset;

    public function __construct(array $value, array $preservedKeys)
    {
        parent::__construct($value);

        $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys));
        $this->cut -= count($this->preservedSubset);
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Represents the main properties of a PHP variable, pre-casted by a caster.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class CutStub extends Stub
{
    public function __construct($value)
    {
        $this->value = $value;

        switch (gettype($value)) {
            case 'object':
                $this->type = self::TYPE_OBJECT;
                $this->class = get_class($value);
                $this->cut = -1;
                break;

            case 'array':
                $this->type = self::TYPE_ARRAY;
                $this->class = self::ARRAY_ASSOC;
                $this->cut = $this->value = count($value);
                break;

            case 'resource':
            case 'unknown type':
            case 'resource (closed)':
                $this->type = self::TYPE_RESOURCE;
                $this->handle = (int) $value;
                if ('Unknown' === $this->class = @get_resource_type($value)) {
                    $this->class = 'Closed';
                }
                $this->cut = -1;
                break;

            case 'string':
                $this->type = self::TYPE_STRING;
                $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY;
                $this->cut = self::STRING_BINARY === $this->class ? strlen($value) : mb_strlen($value, 'UTF-8');
                $this->value = '';
                break;
        }
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts DOM related classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class DOMCaster
{
    private static $errorCodes = array(
        DOM_PHP_ERR => 'DOM_PHP_ERR',
        DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR',
        DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR',
        DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR',
        DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR',
        DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR',
        DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR',
        DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR',
        DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR',
        DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR',
        DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR',
        DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR',
        DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR',
        DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR',
        DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR',
        DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR',
        DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR',
    );

    private static $nodeTypes = array(
        XML_ELEMENT_NODE => 'XML_ELEMENT_NODE',
        XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE',
        XML_TEXT_NODE => 'XML_TEXT_NODE',
        XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE',
        XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE',
        XML_ENTITY_NODE => 'XML_ENTITY_NODE',
        XML_PI_NODE => 'XML_PI_NODE',
        XML_COMMENT_NODE => 'XML_COMMENT_NODE',
        XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE',
        XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE',
        XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE',
        XML_NOTATION_NODE => 'XML_NOTATION_NODE',
        XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE',
        XML_DTD_NODE => 'XML_DTD_NODE',
        XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE',
        XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE',
        XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE',
        XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE',
    );

    public static function castException(\DOMException $e, array $a, Stub $stub, $isNested)
    {
        $k = Caster::PREFIX_PROTECTED.'code';
        if (isset($a[$k], self::$errorCodes[$a[$k]])) {
            $a[$k] = new ConstStub(self::$errorCodes[$a[$k]], $a[$k]);
        }

        return $a;
    }

    public static function castLength($dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'length' => $dom->length,
        );

        return $a;
    }

    public static function castImplementation($dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            Caster::PREFIX_VIRTUAL.'Core' => '1.0',
            Caster::PREFIX_VIRTUAL.'XML' => '2.0',
        );

        return $a;
    }

    public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'nodeName' => $dom->nodeName,
            'nodeValue' => new CutStub($dom->nodeValue),
            'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
            'parentNode' => new CutStub($dom->parentNode),
            'childNodes' => $dom->childNodes,
            'firstChild' => new CutStub($dom->firstChild),
            'lastChild' => new CutStub($dom->lastChild),
            'previousSibling' => new CutStub($dom->previousSibling),
            'nextSibling' => new CutStub($dom->nextSibling),
            'attributes' => $dom->attributes,
            'ownerDocument' => new CutStub($dom->ownerDocument),
            'namespaceURI' => $dom->namespaceURI,
            'prefix' => $dom->prefix,
            'localName' => $dom->localName,
            'baseURI' => $dom->baseURI,
            'textContent' => new CutStub($dom->textContent),
        );

        return $a;
    }

    public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'nodeName' => $dom->nodeName,
            'nodeValue' => new CutStub($dom->nodeValue),
            'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
            'prefix' => $dom->prefix,
            'localName' => $dom->localName,
            'namespaceURI' => $dom->namespaceURI,
            'ownerDocument' => new CutStub($dom->ownerDocument),
            'parentNode' => new CutStub($dom->parentNode),
        );

        return $a;
    }

    public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0)
    {
        $a += array(
            'doctype' => $dom->doctype,
            'implementation' => $dom->implementation,
            'documentElement' => new CutStub($dom->documentElement),
            'actualEncoding' => $dom->actualEncoding,
            'encoding' => $dom->encoding,
            'xmlEncoding' => $dom->xmlEncoding,
            'standalone' => $dom->standalone,
            'xmlStandalone' => $dom->xmlStandalone,
            'version' => $dom->version,
            'xmlVersion' => $dom->xmlVersion,
            'strictErrorChecking' => $dom->strictErrorChecking,
            'documentURI' => $dom->documentURI,
            'config' => $dom->config,
            'formatOutput' => $dom->formatOutput,
            'validateOnParse' => $dom->validateOnParse,
            'resolveExternals' => $dom->resolveExternals,
            'preserveWhiteSpace' => $dom->preserveWhiteSpace,
            'recover' => $dom->recover,
            'substituteEntities' => $dom->substituteEntities,
        );

        if (!($filter & Caster::EXCLUDE_VERBOSE)) {
            $formatOutput = $dom->formatOutput;
            $dom->formatOutput = true;
            $a += array(Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML());
            $dom->formatOutput = $formatOutput;
        }

        return $a;
    }

    public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'data' => $dom->data,
            'length' => $dom->length,
        );

        return $a;
    }

    public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'name' => $dom->name,
            'specified' => $dom->specified,
            'value' => $dom->value,
            'ownerElement' => $dom->ownerElement,
            'schemaTypeInfo' => $dom->schemaTypeInfo,
        );

        return $a;
    }

    public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'tagName' => $dom->tagName,
            'schemaTypeInfo' => $dom->schemaTypeInfo,
        );

        return $a;
    }

    public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'wholeText' => $dom->wholeText,
        );

        return $a;
    }

    public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'typeName' => $dom->typeName,
            'typeNamespace' => $dom->typeNamespace,
        );

        return $a;
    }

    public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'severity' => $dom->severity,
            'message' => $dom->message,
            'type' => $dom->type,
            'relatedException' => $dom->relatedException,
            'related_data' => $dom->related_data,
            'location' => $dom->location,
        );

        return $a;
    }

    public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'lineNumber' => $dom->lineNumber,
            'columnNumber' => $dom->columnNumber,
            'offset' => $dom->offset,
            'relatedNode' => $dom->relatedNode,
            'uri' => $dom->uri,
        );

        return $a;
    }

    public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'name' => $dom->name,
            'entities' => $dom->entities,
            'notations' => $dom->notations,
            'publicId' => $dom->publicId,
            'systemId' => $dom->systemId,
            'internalSubset' => $dom->internalSubset,
        );

        return $a;
    }

    public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'publicId' => $dom->publicId,
            'systemId' => $dom->systemId,
        );

        return $a;
    }

    public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'publicId' => $dom->publicId,
            'systemId' => $dom->systemId,
            'notationName' => $dom->notationName,
            'actualEncoding' => $dom->actualEncoding,
            'encoding' => $dom->encoding,
            'version' => $dom->version,
        );

        return $a;
    }

    public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'target' => $dom->target,
            'data' => $dom->data,
        );

        return $a;
    }

    public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested)
    {
        $a += array(
            'document' => $dom->document,
        );

        return $a;
    }
}
<?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\VarDumper\Caster;

use Doctrine\Common\Proxy\Proxy as CommonProxy;
use Doctrine\ORM\Proxy\Proxy as OrmProxy;
use Doctrine\ORM\PersistentCollection;
use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts Doctrine related classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class DoctrineCaster
{
    public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested)
    {
        foreach (array('__cloner__', '__initializer__') as $k) {
            if (array_key_exists($k, $a)) {
                unset($a[$k]);
                ++$stub->cut;
            }
        }

        return $a;
    }

    public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested)
    {
        foreach (array('_entityPersister', '_identifier') as $k) {
            if (array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) {
                unset($a[$k]);
                ++$stub->cut;
            }
        }

        return $a;
    }

    public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested)
    {
        foreach (array('snapshot', 'association', 'typeClass') as $k) {
            if (array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) {
                $a[$k] = new CutStub($a[$k]);
            }
        }

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Represents an enumeration of values.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class EnumStub extends Stub
{
    public function __construct(array $values)
    {
        $this->value = $values;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts common Exception classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ExceptionCaster
{
    public static $srcContext = 1;
    public static $traceArgs = true;
    public static $errorTypes = array(
        E_DEPRECATED => 'E_DEPRECATED',
        E_USER_DEPRECATED => 'E_USER_DEPRECATED',
        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
        E_ERROR => 'E_ERROR',
        E_WARNING => 'E_WARNING',
        E_PARSE => 'E_PARSE',
        E_NOTICE => 'E_NOTICE',
        E_CORE_ERROR => 'E_CORE_ERROR',
        E_CORE_WARNING => 'E_CORE_WARNING',
        E_COMPILE_ERROR => 'E_COMPILE_ERROR',
        E_COMPILE_WARNING => 'E_COMPILE_WARNING',
        E_USER_ERROR => 'E_USER_ERROR',
        E_USER_WARNING => 'E_USER_WARNING',
        E_USER_NOTICE => 'E_USER_NOTICE',
        E_STRICT => 'E_STRICT',
    );

    public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0)
    {
        return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter);
    }

    public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0)
    {
        return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter);
    }

    public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested)
    {
        if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) {
            $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
        }

        return $a;
    }

    public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_PROTECTED;
        $xPrefix = "\0Exception\0";

        if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace']) && $a[$xPrefix.'previous'] instanceof \Exception) {
            $b = (array) $a[$xPrefix.'previous'];
            array_unshift($b[$xPrefix.'trace'], array(
                'function' => 'new '.get_class($a[$xPrefix.'previous']),
                'file' => $b[$prefix.'file'],
                'line' => $b[$prefix.'line'],
            ));
            $a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - count($a[$xPrefix.'trace']->value));
        }

        unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']);

        return $a;
    }

    public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested)
    {
        if (!$isNested) {
            return $a;
        }
        $stub->class = '';
        $stub->handle = 0;
        $frames = $trace->value;

        $a = array();
        $j = count($frames);
        if (0 > $i = $trace->sliceOffset) {
            $i = max(0, $j + $i);
        }
        if (!isset($trace->value[$i])) {
            return array();
        }
        $lastCall = isset($frames[$i]['function']) ? ' ==> '.(isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';

        for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
            $call = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[$i]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '???';

            $a[Caster::PREFIX_VIRTUAL.$j.'. '.$call.$lastCall] = new FrameStub(
                array(
                    'object' => isset($frames[$i]['object']) ? $frames[$i]['object'] : null,
                    'class' => isset($frames[$i]['class']) ? $frames[$i]['class'] : null,
                    'type' => isset($frames[$i]['type']) ? $frames[$i]['type'] : null,
                    'function' => isset($frames[$i]['function']) ? $frames[$i]['function'] : null,
                ) + $frames[$i - 1],
                $trace->keepArgs,
                true
            );

            $lastCall = ' ==> '.$call;
        }
        $a[Caster::PREFIX_VIRTUAL.$j.'. {main}'.$lastCall] = new FrameStub(
            array(
                'object' => null,
                'class' => null,
                'type' => null,
                'function' => '{main}',
            ) + $frames[$i - 1],
            $trace->keepArgs,
            true
        );
        if (null !== $trace->sliceLength) {
            $a = array_slice($a, 0, $trace->sliceLength, true);
        }

        return $a;
    }

    public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested)
    {
        if (!$isNested) {
            return $a;
        }
        $f = $frame->value;
        $prefix = Caster::PREFIX_VIRTUAL;

        if (isset($f['file'], $f['line'])) {
            if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) {
                $f['file'] = substr($f['file'], 0, -strlen($match[0]));
                $f['line'] = (int) $match[1];
            }
            if (file_exists($f['file']) && 0 <= self::$srcContext) {
                $src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext);

                if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) {
                    $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', strlen($f['class']), $f['class']));

                    $templateName = $template->getTemplateName();
                    $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : '');
                    $templateInfo = $template->getDebugInfo();
                    if (isset($templateInfo[$f['line']])) {
                        if (method_exists($template, 'getSourceContext')) {
                            $templateName = $template->getSourceContext()->getPath() ?: $templateName;
                        }
                        if ($templateSrc) {
                            $templateSrc = explode("\n", $templateSrc);
                            $src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext);
                        } else {
                            $src[$templateName] = $templateInfo[$f['line']];
                        }
                    }
                }
            } else {
                $src[$f['file']] = $f['line'];
            }
            $a[$prefix.'src'] = new EnumStub($src);
        }

        unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']);
        if ($frame->inTraceStub) {
            unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']);
        }
        foreach ($a as $k => $v) {
            if (!$v) {
                unset($a[$k]);
            }
        }
        if ($frame->keepArgs && isset($f['args'])) {
            $a[$prefix.'args'] = $f['args'];
        }

        return $a;
    }

    /**
     * @deprecated since 2.8, to be removed in 3.0. Use the castTraceStub method instead.
     */
    public static function filterTrace(&$trace, $dumpArgs, $offset = 0)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use the castTraceStub method instead.', E_USER_DEPRECATED);

        if (0 > $offset || empty($trace[$offset])) {
            return $trace = null;
        }

        $t = $trace[$offset];

        if (empty($t['class']) && isset($t['function'])) {
            if ('user_error' === $t['function'] || 'trigger_error' === $t['function']) {
                ++$offset;
            }
        }

        if ($offset) {
            array_splice($trace, 0, $offset);
        }

        foreach ($trace as &$t) {
            $t = array(
                'call' => (isset($t['class']) ? $t['class'].$t['type'] : '').$t['function'].'()',
                'file' => isset($t['line']) ? "{$t['file']}:{$t['line']}" : '',
                'args' => &$t['args'],
            );

            if (!isset($t['args']) || !$dumpArgs) {
                unset($t['args']);
            }
        }
    }

    private static function filterExceptionArray($xClass, array $a, $xPrefix, $filter)
    {
        if (isset($a[$xPrefix.'trace'])) {
            $trace = $a[$xPrefix.'trace'];
            unset($a[$xPrefix.'trace']); // Ensures the trace is always last
        } else {
            $trace = array();
        }

        if (!($filter & Caster::EXCLUDE_VERBOSE)) {
            if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
                array_unshift($trace, array(
                    'function' => $xClass ? 'new '.$xClass : null,
                    'file' => $a[Caster::PREFIX_PROTECTED.'file'],
                    'line' => $a[Caster::PREFIX_PROTECTED.'line'],
                ));
            }
            $a[$xPrefix.'trace'] = new TraceStub($trace, self::$traceArgs);
        }
        if (empty($a[$xPrefix.'previous'])) {
            unset($a[$xPrefix.'previous']);
        }
        unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']);

        return $a;
    }

    private static function extractSource(array $srcArray, $line, $srcContext)
    {
        $src = array();

        for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
            $src[] = (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n";
        }

        $ltrim = 0;
        do {
            $pad = null;
            for ($i = $srcContext << 1; $i >= 0; --$i) {
                if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) {
                    if (null === $pad) {
                        $pad = $c;
                    }
                    if ((' ' !== $c && "\t" !== $c) || $pad !== $c) {
                        break;
                    }
                }
            }
            ++$ltrim;
        } while (0 > $i && null !== $pad);

        if (--$ltrim) {
            foreach ($src as $i => $line) {
                $src[$i] = isset($line[$ltrim]) && "\r" !== $line[$ltrim] ? substr($line, $ltrim) : ltrim($line, " \t");
            }
        }

        return implode('', $src);
    }
}
<?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\VarDumper\Caster;

/**
 * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace().
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class FrameStub extends EnumStub
{
    public $keepArgs;
    public $inTraceStub;

    public function __construct(array $frame, $keepArgs = true, $inTraceStub = false)
    {
        $this->value = $frame;
        $this->keepArgs = $keepArgs;
        $this->inTraceStub = $inTraceStub;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts classes from the MongoDb extension to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class MongoCaster
{
    public static function castCursor(\MongoCursorInterface $cursor, array $a, Stub $stub, $isNested)
    {
        if ($info = $cursor->info()) {
            foreach ($info as $k => $v) {
                $a[Caster::PREFIX_VIRTUAL.$k] = $v;
            }
        }
        $a[Caster::PREFIX_VIRTUAL.'dead'] = $cursor->dead();

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts PDO related classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class PdoCaster
{
    private static $pdoAttributes = array(
        'CASE' => array(
            \PDO::CASE_LOWER => 'LOWER',
            \PDO::CASE_NATURAL => 'NATURAL',
            \PDO::CASE_UPPER => 'UPPER',
        ),
        'ERRMODE' => array(
            \PDO::ERRMODE_SILENT => 'SILENT',
            \PDO::ERRMODE_WARNING => 'WARNING',
            \PDO::ERRMODE_EXCEPTION => 'EXCEPTION',
        ),
        'TIMEOUT',
        'PREFETCH',
        'AUTOCOMMIT',
        'PERSISTENT',
        'DRIVER_NAME',
        'SERVER_INFO',
        'ORACLE_NULLS' => array(
            \PDO::NULL_NATURAL => 'NATURAL',
            \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING',
            \PDO::NULL_TO_STRING => 'TO_STRING',
        ),
        'CLIENT_VERSION',
        'SERVER_VERSION',
        'STATEMENT_CLASS',
        'EMULATE_PREPARES',
        'CONNECTION_STATUS',
        'STRINGIFY_FETCHES',
        'DEFAULT_FETCH_MODE' => array(
            \PDO::FETCH_ASSOC => 'ASSOC',
            \PDO::FETCH_BOTH => 'BOTH',
            \PDO::FETCH_LAZY => 'LAZY',
            \PDO::FETCH_NUM => 'NUM',
            \PDO::FETCH_OBJ => 'OBJ',
        ),
    );

    public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested)
    {
        $attr = array();
        $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE);
        $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

        foreach (self::$pdoAttributes as $k => $v) {
            if (!isset($k[0])) {
                $k = $v;
                $v = array();
            }

            try {
                $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(constant('PDO::ATTR_'.$k));
                if ($v && isset($v[$attr[$k]])) {
                    $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]);
                }
            } catch (\Exception $e) {
            }
        }

        $prefix = Caster::PREFIX_VIRTUAL;
        $a += array(
            $prefix.'inTransaction' => method_exists($c, 'inTransaction'),
            $prefix.'errorInfo' => $c->errorInfo(),
            $prefix.'attributes' => new EnumStub($attr),
        );

        if ($a[$prefix.'inTransaction']) {
            $a[$prefix.'inTransaction'] = $c->inTransaction();
        } else {
            unset($a[$prefix.'inTransaction']);
        }

        if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
            unset($a[$prefix.'errorInfo']);
        }

        $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode);

        return $a;
    }

    public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;
        $a[$prefix.'errorInfo'] = $c->errorInfo();

        if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
            unset($a[$prefix.'errorInfo']);
        }

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts pqsql resources to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class PgSqlCaster
{
    private static $paramCodes = array(
        'server_encoding',
        'client_encoding',
        'is_superuser',
        'session_authorization',
        'DateStyle',
        'TimeZone',
        'IntervalStyle',
        'integer_datetimes',
        'application_name',
        'standard_conforming_strings',
    );

    private static $transactionStatus = array(
        PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE',
        PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE',
        PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS',
        PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR',
        PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN',
    );

    private static $resultStatus = array(
        PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY',
        PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK',
        PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK',
        PGSQL_COPY_OUT => 'PGSQL_COPY_OUT',
        PGSQL_COPY_IN => 'PGSQL_COPY_IN',
        PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE',
        PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR',
        PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR',
    );

    private static $diagCodes = array(
        'severity' => PGSQL_DIAG_SEVERITY,
        'sqlstate' => PGSQL_DIAG_SQLSTATE,
        'message' => PGSQL_DIAG_MESSAGE_PRIMARY,
        'detail' => PGSQL_DIAG_MESSAGE_DETAIL,
        'hint' => PGSQL_DIAG_MESSAGE_HINT,
        'statement position' => PGSQL_DIAG_STATEMENT_POSITION,
        'internal position' => PGSQL_DIAG_INTERNAL_POSITION,
        'internal query' => PGSQL_DIAG_INTERNAL_QUERY,
        'context' => PGSQL_DIAG_CONTEXT,
        'file' => PGSQL_DIAG_SOURCE_FILE,
        'line' => PGSQL_DIAG_SOURCE_LINE,
        'function' => PGSQL_DIAG_SOURCE_FUNCTION,
    );

    public static function castLargeObject($lo, array $a, Stub $stub, $isNested)
    {
        $a['seek position'] = pg_lo_tell($lo);

        return $a;
    }

    public static function castLink($link, array $a, Stub $stub, $isNested)
    {
        $a['status'] = pg_connection_status($link);
        $a['status'] = new ConstStub(PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']);
        $a['busy'] = pg_connection_busy($link);

        $a['transaction'] = pg_transaction_status($link);
        if (isset(self::$transactionStatus[$a['transaction']])) {
            $a['transaction'] = new ConstStub(self::$transactionStatus[$a['transaction']], $a['transaction']);
        }

        $a['pid'] = pg_get_pid($link);
        $a['last error'] = pg_last_error($link);
        $a['last notice'] = pg_last_notice($link);
        $a['host'] = pg_host($link);
        $a['port'] = pg_port($link);
        $a['dbname'] = pg_dbname($link);
        $a['options'] = pg_options($link);
        $a['version'] = pg_version($link);

        foreach (self::$paramCodes as $v) {
            if (false !== $s = pg_parameter_status($link, $v)) {
                $a['param'][$v] = $s;
            }
        }

        $a['param']['client_encoding'] = pg_client_encoding($link);
        $a['param'] = new EnumStub($a['param']);

        return $a;
    }

    public static function castResult($result, array $a, Stub $stub, $isNested)
    {
        $a['num rows'] = pg_num_rows($result);
        $a['status'] = pg_result_status($result);
        if (isset(self::$resultStatus[$a['status']])) {
            $a['status'] = new ConstStub(self::$resultStatus[$a['status']], $a['status']);
        }
        $a['command-completion tag'] = pg_result_status($result, PGSQL_STATUS_STRING);

        if (-1 === $a['num rows']) {
            foreach (self::$diagCodes as $k => $v) {
                $a['error'][$k] = pg_result_error_field($result, $v);
            }
        }

        $a['affected rows'] = pg_affected_rows($result);
        $a['last OID'] = pg_last_oid($result);

        $fields = pg_num_fields($result);

        for ($i = 0; $i < $fields; ++$i) {
            $field = array(
                'name' => pg_field_name($result, $i),
                'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)),
                'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)),
                'nullable' => (bool) pg_field_is_null($result, $i),
                'storage' => pg_field_size($result, $i).' bytes',
                'display' => pg_field_prtlen($result, $i).' chars',
            );
            if (' (OID: )' === $field['table']) {
                $field['table'] = null;
            }
            if ('-1 bytes' === $field['storage']) {
                $field['storage'] = 'variable size';
            } elseif ('1 bytes' === $field['storage']) {
                $field['storage'] = '1 byte';
            }
            if ('1 chars' === $field['display']) {
                $field['display'] = '1 char';
            }
            $a['fields'][] = new EnumStub($field);
        }

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts Reflector related classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ReflectionCaster
{
    private static $extraMap = array(
        'docComment' => 'getDocComment',
        'extension' => 'getExtensionName',
        'isDisabled' => 'isDisabled',
        'isDeprecated' => 'isDeprecated',
        'isInternal' => 'isInternal',
        'isUserDefined' => 'isUserDefined',
        'isGenerator' => 'isGenerator',
        'isVariadic' => 'isVariadic',
    );

    /**
     * @deprecated since Symfony 2.7, to be removed in 3.0.
     */
    public static function castReflector(\Reflector $c, array $a, Stub $stub, $isNested)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
        $a[Caster::PREFIX_VIRTUAL.'reflection'] = $c->__toString();

        return $a;
    }

    public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;
        $c = new \ReflectionFunction($c);

        $stub->class = 'Closure'; // HHVM generates unique class names for closures
        $a = static::castFunctionAbstract($c, $a, $stub, $isNested);

        if (isset($a[$prefix.'parameters'])) {
            foreach ($a[$prefix.'parameters']->value as &$v) {
                $param = $v;
                $v = new EnumStub(array());
                foreach (static::castParameter($param, array(), $stub, true) as $k => $param) {
                    if ("\0" === $k[0]) {
                        $v->value[substr($k, 3)] = $param;
                    }
                }
                unset($v->value['position'], $v->value['isVariadic'], $v->value['byReference'], $v);
            }
        }

        if ($f = $c->getFileName()) {
            $a[$prefix.'file'] = $f;
            $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
        }

        $prefix = Caster::PREFIX_DYNAMIC;
        unset($a['name'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']);

        return $a;
    }

    public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
    {
        if (!class_exists('ReflectionGenerator', false)) {
            return $a;
        }

        // Cannot create ReflectionGenerator based on a terminated Generator
        try {
            $reflectionGenerator = new \ReflectionGenerator($c);
        } catch (\Exception $e) {
            $a[Caster::PREFIX_VIRTUAL.'closed'] = true;

            return $a;
        }

        return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested);
    }

    public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        $a += array(
            $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(),
            $prefix.'allowsNull' => $c->allowsNull(),
            $prefix.'isBuiltin' => $c->isBuiltin(),
        );

        return $a;
    }

    public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        if ($c->getThis()) {
            $a[$prefix.'this'] = new CutStub($c->getThis());
        }
        $function = $c->getFunction();
        $frame = array(
            'class' => isset($function->class) ? $function->class : null,
            'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null,
            'function' => $function->name,
            'file' => $c->getExecutingFile(),
            'line' => $c->getExecutingLine(),
        );
        if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) {
            $function = new \ReflectionGenerator($c->getExecutingGenerator());
            array_unshift($trace, array(
                'function' => 'yield',
                'file' => $function->getExecutingFile(),
                'line' => $function->getExecutingLine() - 1,
            ));
            $trace[] = $frame;
            $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
        } else {
            $function = new FrameStub($frame, false, true);
            $function = ExceptionCaster::castFrameStub($function, array(), $function, true);
            $a[$prefix.'executing'] = new EnumStub(array(
                $frame['class'].$frame['type'].$frame['function'].'()' => $function[$prefix.'src'],
            ));
        }

        $a[Caster::PREFIX_VIRTUAL.'closed'] = false;

        return $a;
    }

    public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        if ($n = \Reflection::getModifierNames($c->getModifiers())) {
            $a[$prefix.'modifiers'] = implode(' ', $n);
        }

        self::addMap($a, $c, array(
            'extends' => 'getParentClass',
            'implements' => 'getInterfaceNames',
            'constants' => 'getConstants',
        ));

        foreach ($c->getProperties() as $n) {
            $a[$prefix.'properties'][$n->name] = $n;
        }

        foreach ($c->getMethods() as $n) {
            $a[$prefix.'methods'][$n->name] = $n;
        }

        if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
            self::addExtra($a, $c);
        }

        return $a;
    }

    public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        self::addMap($a, $c, array(
            'returnsReference' => 'returnsReference',
            'returnType' => 'getReturnType',
            'class' => 'getClosureScopeClass',
            'this' => 'getClosureThis',
        ));

        if (isset($a[$prefix.'returnType'])) {
            $v = $a[$prefix.'returnType'];
            $v = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
            $a[$prefix.'returnType'] = $a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v;
        }
        if (isset($a[$prefix.'this'])) {
            $a[$prefix.'this'] = new CutStub($a[$prefix.'this']);
        }

        foreach ($c->getParameters() as $v) {
            $k = '$'.$v->name;
            if (method_exists($v, 'isVariadic') && $v->isVariadic()) {
                $k = '...'.$k;
            }
            if ($v->isPassedByReference()) {
                $k = '&'.$k;
            }
            $a[$prefix.'parameters'][$k] = $v;
        }
        if (isset($a[$prefix.'parameters'])) {
            $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']);
        }

        if ($v = $c->getStaticVariables()) {
            foreach ($v as $k => &$v) {
                $a[$prefix.'use']['$'.$k] = &$v;
            }
            unset($v);
            $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']);
        }

        if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
            self::addExtra($a, $c);
        }

        // Added by HHVM
        unset($a[Caster::PREFIX_DYNAMIC.'static']);

        return $a;
    }

    public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested)
    {
        $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));

        return $a;
    }

    public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;

        // Added by HHVM
        unset($a['info']);

        self::addMap($a, $c, array(
            'position' => 'getPosition',
            'isVariadic' => 'isVariadic',
            'byReference' => 'isPassedByReference',
            'allowsNull' => 'allowsNull',
        ));

        if (method_exists($c, 'getType')) {
            if ($v = $c->getType()) {
                $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
            }
        } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) {
            $a[$prefix.'typeHint'] = $v[1];
        }
        if (!isset($a[$prefix.'typeHint'])) {
            unset($a[$prefix.'allowsNull']);
        }

        try {
            $a[$prefix.'default'] = $v = $c->getDefaultValue();
            if (method_exists($c, 'isDefaultValueConstant') && $c->isDefaultValueConstant()) {
                $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
            }
            if (null === $v) {
                unset($a[$prefix.'allowsNull']);
            }
        } catch (\ReflectionException $e) {
            if (isset($a[$prefix.'typeHint']) && $c->allowsNull() && !class_exists('ReflectionNamedType', false)) {
                $a[$prefix.'default'] = null;
                unset($a[$prefix.'allowsNull']);
            }
        }

        return $a;
    }

    public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested)
    {
        $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));
        self::addExtra($a, $c);

        return $a;
    }

    public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested)
    {
        self::addMap($a, $c, array(
            'version' => 'getVersion',
            'dependencies' => 'getDependencies',
            'iniEntries' => 'getIniEntries',
            'isPersistent' => 'isPersistent',
            'isTemporary' => 'isTemporary',
            'constants' => 'getConstants',
            'functions' => 'getFunctions',
            'classes' => 'getClasses',
        ));

        return $a;
    }

    public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested)
    {
        self::addMap($a, $c, array(
            'version' => 'getVersion',
            'author' => 'getAuthor',
            'copyright' => 'getCopyright',
            'url' => 'getURL',
        ));

        return $a;
    }

    private static function addExtra(&$a, \Reflector $c)
    {
        $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : array();

        if (method_exists($c, 'getFileName') && $m = $c->getFileName()) {
            $x['file'] = $m;
            $x['line'] = $c->getStartLine().' to '.$c->getEndLine();
        }

        self::addMap($x, $c, self::$extraMap, '');

        if ($x) {
            $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x);
        }
    }

    private static function addMap(&$a, \Reflector $c, $map, $prefix = Caster::PREFIX_VIRTUAL)
    {
        foreach ($map as $k => $m) {
            if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) {
                $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m;
            }
        }
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts common resource types to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ResourceCaster
{
    public static function castCurl($h, array $a, Stub $stub, $isNested)
    {
        return curl_getinfo($h);
    }

    public static function castDba($dba, array $a, Stub $stub, $isNested)
    {
        $list = dba_list();
        $a['file'] = $list[(int) $dba];

        return $a;
    }

    public static function castProcess($process, array $a, Stub $stub, $isNested)
    {
        return proc_get_status($process);
    }

    public static function castStream($stream, array $a, Stub $stub, $isNested)
    {
        return stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested);
    }

    public static function castStreamContext($stream, array $a, Stub $stub, $isNested)
    {
        return @stream_context_get_params($stream) ?: $a;
    }

    public static function castGd($gd, array $a, Stub $stub, $isNested)
    {
        $a['size'] = imagesx($gd).'x'.imagesy($gd);
        $a['trueColor'] = imageistruecolor($gd);

        return $a;
    }

    public static function castMysqlLink($h, array $a, Stub $stub, $isNested)
    {
        $a['host'] = mysql_get_host_info($h);
        $a['protocol'] = mysql_get_proto_info($h);
        $a['server'] = mysql_get_server_info($h);

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts SPL related classes to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class SplCaster
{
    private static $splFileObjectFlags = array(
        \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE',
        \SplFileObject::READ_AHEAD => 'READ_AHEAD',
        \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY',
        \SplFileObject::READ_CSV => 'READ_CSV',
    );

    public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;
        $class = $stub->class;
        $flags = $c->getFlags();

        $b = array(
            $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
            $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
            $prefix.'iteratorClass' => $c->getIteratorClass(),
            $prefix.'storage' => $c->getArrayCopy(),
        );

        if ('ArrayObject' === $class) {
            $a = $b;
        } else {
            if (!($flags & \ArrayObject::STD_PROP_LIST)) {
                $c->setFlags(\ArrayObject::STD_PROP_LIST);
                $a = Caster::castObject($c, new \ReflectionClass($class));
                $c->setFlags($flags);
            }

            $a += $b;
        }

        return $a;
    }

    public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
    {
        $a += array(
            Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c),
        );

        return $a;
    }

    public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested)
    {
        $prefix = Caster::PREFIX_VIRTUAL;
        $mode = $c->getIteratorMode();
        $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE);

        $a += array(
            $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode),
            $prefix.'dllist' => iterator_to_array($c),
        );
        $c->setIteratorMode($mode);

        return $a;
    }

    public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested)
    {
        static $map = array(
            'path' => 'getPath',
            'filename' => 'getFilename',
            'basename' => 'getBasename',
            'pathname' => 'getPathname',
            'extension' => 'getExtension',
            'realPath' => 'getRealPath',
            'aTime' => 'getATime',
            'mTime' => 'getMTime',
            'cTime' => 'getCTime',
            'inode' => 'getInode',
            'size' => 'getSize',
            'perms' => 'getPerms',
            'owner' => 'getOwner',
            'group' => 'getGroup',
            'type' => 'getType',
            'writable' => 'isWritable',
            'readable' => 'isReadable',
            'executable' => 'isExecutable',
            'file' => 'isFile',
            'dir' => 'isDir',
            'link' => 'isLink',
            'linkTarget' => 'getLinkTarget',
        );

        $prefix = Caster::PREFIX_VIRTUAL;

        foreach ($map as $key => $accessor) {
            try {
                $a[$prefix.$key] = $c->$accessor();
            } catch (\Exception $e) {
            }
        }

        if (isset($a[$prefix.'perms'])) {
            $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']);
        }

        static $mapDate = array('aTime', 'mTime', 'cTime');
        foreach ($mapDate as $key) {
            if (isset($a[$prefix.$key])) {
                $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]);
            }
        }

        return $a;
    }

    public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested)
    {
        static $map = array(
            'csvControl' => 'getCsvControl',
            'flags' => 'getFlags',
            'maxLineLen' => 'getMaxLineLen',
            'fstat' => 'fstat',
            'eof' => 'eof',
            'key' => 'key',
        );

        $prefix = Caster::PREFIX_VIRTUAL;

        foreach ($map as $key => $accessor) {
            try {
                $a[$prefix.$key] = $c->$accessor();
            } catch (\Exception $e) {
            }
        }

        if (isset($a[$prefix.'flags'])) {
            $flagsArray = array();
            foreach (self::$splFileObjectFlags as $value => $name) {
                if ($a[$prefix.'flags'] & $value) {
                    $flagsArray[] = $name;
                }
            }
            $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']);
        }

        if (isset($a[$prefix.'fstat'])) {
            $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], array('dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks'));
        }

        return $a;
    }

    public static function castFixedArray(\SplFixedArray $c, array $a, Stub $stub, $isNested)
    {
        $a += array(
            Caster::PREFIX_VIRTUAL.'storage' => $c->toArray(),
        );

        return $a;
    }

    public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested)
    {
        $storage = array();
        unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967

        foreach (clone $c as $obj) {
            $storage[spl_object_hash($obj)] = array(
                'object' => $obj,
                'info' => $c->getInfo(),
             );
        }

        $a += array(
            Caster::PREFIX_VIRTUAL.'storage' => $storage,
        );

        return $a;
    }

    public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested)
    {
        $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator();

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts a caster's Stub.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class StubCaster
{
    public static function castStub(Stub $c, array $a, Stub $stub, $isNested)
    {
        if ($isNested) {
            $stub->type = $c->type;
            $stub->class = $c->class;
            $stub->value = $c->value;
            $stub->handle = $c->handle;
            $stub->cut = $c->cut;

            $a = array();
        }

        return $a;
    }

    public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested)
    {
        return $isNested ? $c->preservedSubset : $a;
    }

    public static function cutInternals($obj, array $a, Stub $stub, $isNested)
    {
        if ($isNested) {
            $stub->cut += count($a);

            return array();
        }

        return $a;
    }

    public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
    {
        if ($isNested) {
            $stub->class = '';
            $stub->handle = 0;
            $stub->value = null;

            $a = array();

            if ($c->value) {
                foreach (array_keys($c->value) as $k) {
                    $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k;
                }
                // Preserve references with array_combine()
                $a = array_combine($keys, $c->value);
            }
        }

        return $a;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace().
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class TraceStub extends Stub
{
    public $keepArgs;
    public $sliceOffset;
    public $sliceLength;
    public $numberingOffset;

    public function __construct(array $trace, $keepArgs = true, $sliceOffset = 0, $sliceLength = null, $numberingOffset = 0)
    {
        $this->value = $trace;
        $this->keepArgs = $keepArgs;
        $this->sliceOffset = $sliceOffset;
        $this->sliceLength = $sliceLength;
        $this->numberingOffset = $numberingOffset;
    }
}
<?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\VarDumper\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * Casts XML resources to array representation.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class XmlResourceCaster
{
    private static $xmlErrors = array(
        XML_ERROR_NONE => 'XML_ERROR_NONE',
        XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY',
        XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX',
        XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS',
        XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN',
        XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN',
        XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR',
        XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH',
        XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE',
        XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT',
        XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF',
        XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY',
        XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF',
        XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY',
        XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF',
        XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF',
        XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF',
        XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI',
        XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING',
        XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING',
        XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION',
        XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING',
    );

    public static function castXml($h, array $a, Stub $stub, $isNested)
    {
        $a['current_byte_index'] = xml_get_current_byte_index($h);
        $a['current_column_number'] = xml_get_current_column_number($h);
        $a['current_line_number'] = xml_get_current_line_number($h);
        $a['error_code'] = xml_get_error_code($h);

        if (isset(self::$xmlErrors[$a['error_code']])) {
            $a['error_code'] = new ConstStub(self::$xmlErrors[$a['error_code']], $a['error_code']);
        }

        return $a;
    }
}
<?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\VarDumper\Cloner;

use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Exception\ThrowingCasterException;

/**
 * AbstractCloner implements a generic caster mechanism for objects and resources.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
abstract class AbstractCloner implements ClonerInterface
{
    public static $defaultCasters = array(
        'Symfony\Component\VarDumper\Caster\CutStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
        'Symfony\Component\VarDumper\Caster\CutArrayStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castCutArray',
        'Symfony\Component\VarDumper\Caster\ConstStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
        'Symfony\Component\VarDumper\Caster\EnumStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castEnum',

        'Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
        'Generator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castGenerator',
        'ReflectionType' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castType',
        'ReflectionGenerator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflectionGenerator',
        'ReflectionClass' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClass',
        'ReflectionFunctionAbstract' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castFunctionAbstract',
        'ReflectionMethod' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castMethod',
        'ReflectionParameter' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castParameter',
        'ReflectionProperty' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castProperty',
        'ReflectionExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castExtension',
        'ReflectionZendExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castZendExtension',

        'Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
        'Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy',
        'Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy',
        'Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection',

        'DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException',
        'DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
        'DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
        'DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation',
        'DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
        'DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode',
        'DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode',
        'DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument',
        'DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
        'DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
        'DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData',
        'DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr',
        'DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement',
        'DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText',
        'DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo',
        'DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError',
        'DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator',
        'DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType',
        'DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation',
        'DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity',
        'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
        'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',

        'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
        'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
        'Error' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castError',
        'Symfony\Component\DependencyInjection\ContainerInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
        'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException',
        'Symfony\Component\VarDumper\Caster\TraceStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castTraceStub',
        'Symfony\Component\VarDumper\Caster\FrameStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castFrameStub',

        'PHPUnit_Framework_MockObject_MockObject' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
        'Prophecy\Prophecy\ProphecySubjectInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
        'Mockery\MockInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',

        'PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo',
        'PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement',

        'AMQPConnection' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castConnection',
        'AMQPChannel' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castChannel',
        'AMQPQueue' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castQueue',
        'AMQPExchange' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castExchange',
        'AMQPEnvelope' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castEnvelope',

        'ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject',
        'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList',
        'SplFileInfo' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileInfo',
        'SplFileObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileObject',
        'SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray',
        'SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
        'SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage',
        'SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
        'OuterIterator' => 'Symfony\Component\VarDumper\Caster\SplCaster::castOuterIterator',

        'MongoCursorInterface' => 'Symfony\Component\VarDumper\Caster\MongoCaster::castCursor',

        ':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl',
        ':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
        ':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
        ':gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd',
        ':mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink',
        ':pgsql large object' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLargeObject',
        ':pgsql link' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink',
        ':pgsql link persistent' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink',
        ':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult',
        ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
        ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
        ':persistent stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
        ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
        ':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml',
    );

    protected $maxItems = 2500;
    protected $maxString = -1;
    protected $useExt;

    private $casters = array();
    private $prevErrorHandler;
    private $classInfo = array();
    private $filter = 0;

    /**
     * @param callable[]|null $casters A map of casters
     *
     * @see addCasters
     */
    public function __construct(array $casters = null)
    {
        if (null === $casters) {
            $casters = static::$defaultCasters;
        }
        $this->addCasters($casters);
        $this->useExt = extension_loaded('symfony_debug');
    }

    /**
     * Adds casters for resources and objects.
     *
     * Maps resources or objects types to a callback.
     * Types are in the key, with a callable caster for value.
     * Resource types are to be prefixed with a `:`,
     * see e.g. static::$defaultCasters.
     *
     * @param callable[] $casters A map of casters
     */
    public function addCasters(array $casters)
    {
        foreach ($casters as $type => $callback) {
            $this->casters[strtolower($type)][] = $callback;
        }
    }

    /**
     * Sets the maximum number of items to clone past the first level in nested structures.
     *
     * @param int $maxItems
     */
    public function setMaxItems($maxItems)
    {
        $this->maxItems = (int) $maxItems;
    }

    /**
     * Sets the maximum cloned length for strings.
     *
     * @param int $maxString
     */
    public function setMaxString($maxString)
    {
        $this->maxString = (int) $maxString;
    }

    /**
     * Clones a PHP variable.
     *
     * @param mixed $var    Any PHP variable
     * @param int   $filter A bit field of Caster::EXCLUDE_* constants
     *
     * @return Data The cloned variable represented by a Data object
     */
    public function cloneVar($var, $filter = 0)
    {
        $this->filter = $filter;
        $this->prevErrorHandler = set_error_handler(array($this, 'handleError'));
        try {
            $data = $this->doClone($var);
        } catch (\Exception $e) {
        }
        restore_error_handler();
        $this->prevErrorHandler = null;

        if (isset($e)) {
            throw $e;
        }

        return new Data($data);
    }

    /**
     * Effectively clones the PHP variable.
     *
     * @param mixed $var Any PHP variable
     *
     * @return array The cloned variable represented in an array
     */
    abstract protected function doClone($var);

    /**
     * Casts an object to an array representation.
     *
     * @param Stub $stub     The Stub for the casted object
     * @param bool $isNested True if the object is nested in the dumped structure
     *
     * @return array The object casted as array
     */
    protected function castObject(Stub $stub, $isNested)
    {
        $obj = $stub->value;
        $class = $stub->class;

        if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) {
            $stub->class = get_parent_class($class).'@anonymous';
        }
        if (isset($this->classInfo[$class])) {
            $classInfo = $this->classInfo[$class];
        } else {
            $classInfo = array(
                new \ReflectionClass($class),
                array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')),
            );

            $this->classInfo[$class] = $classInfo;
        }

        $a = $this->callCaster('Symfony\Component\VarDumper\Caster\Caster::castObject', $obj, $classInfo[0], null, $isNested);

        foreach ($classInfo[1] as $p) {
            if (!empty($this->casters[$p = strtolower($p)])) {
                foreach ($this->casters[$p] as $p) {
                    $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
                }
            }
        }

        return $a;
    }

    /**
     * Casts a resource to an array representation.
     *
     * @param Stub $stub     The Stub for the casted resource
     * @param bool $isNested True if the object is nested in the dumped structure
     *
     * @return array The resource casted as array
     */
    protected function castResource(Stub $stub, $isNested)
    {
        $a = array();
        $res = $stub->value;
        $type = $stub->class;

        if (!empty($this->casters[':'.$type])) {
            foreach ($this->casters[':'.$type] as $c) {
                $a = $this->callCaster($c, $res, $a, $stub, $isNested);
            }
        }

        return $a;
    }

    /**
     * Calls a custom caster.
     *
     * @param callable        $callback The caster
     * @param object|resource $obj      The object/resource being casted
     * @param array           $a        The result of the previous cast for chained casters
     * @param Stub            $stub     The Stub for the casted object/resource
     * @param bool            $isNested True if $obj is nested in the dumped structure
     *
     * @return array The casted object/resource
     */
    private function callCaster($callback, $obj, $a, $stub, $isNested)
    {
        try {
            $cast = call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter);

            if (is_array($cast)) {
                $a = $cast;
            }
        } catch (\Exception $e) {
            $a[(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠'] = new ThrowingCasterException($e);
        }

        return $a;
    }

    /**
     * Special handling for errors: cloning must be fail-safe.
     *
     * @internal
     */
    public function handleError($type, $msg, $file, $line, $context)
    {
        if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) {
            // Cloner never dies
            throw new \ErrorException($msg, 0, $type, $file, $line);
        }

        if ($this->prevErrorHandler) {
            return call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context);
        }

        return 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\VarDumper\Cloner;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface ClonerInterface
{
    /**
     * Clones a PHP variable.
     *
     * @param mixed $var Any PHP variable
     *
     * @return Data The cloned variable represented by a Data object
     */
    public function cloneVar($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\VarDumper\Cloner;

/**
 * Represents the current state of a dumper while dumping.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Cursor
{
    const HASH_INDEXED = Stub::ARRAY_INDEXED;
    const HASH_ASSOC = Stub::ARRAY_ASSOC;
    const HASH_OBJECT = Stub::TYPE_OBJECT;
    const HASH_RESOURCE = Stub::TYPE_RESOURCE;

    public $depth = 0;
    public $refIndex = 0;
    public $softRefTo = 0;
    public $softRefCount = 0;
    public $softRefHandle = 0;
    public $hardRefTo = 0;
    public $hardRefCount = 0;
    public $hardRefHandle = 0;
    public $hashType;
    public $hashKey;
    public $hashKeyIsBinary;
    public $hashIndex = 0;
    public $hashLength = 0;
    public $hashCut = 0;
    public $stop = 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\VarDumper\Cloner;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Data
{
    private $data;
    private $maxDepth = 20;
    private $maxItemsPerDepth = -1;
    private $useRefHandles = -1;

    /**
     * @param array $data A array as returned by ClonerInterface::cloneVar()
     */
    public function __construct(array $data)
    {
        $this->data = $data;
    }

    /**
     * @return array The raw data structure
     */
    public function getRawData()
    {
        return $this->data;
    }

    /**
     * Returns a depth limited clone of $this.
     *
     * @param int $maxDepth The max dumped depth level
     *
     * @return self A clone of $this
     */
    public function withMaxDepth($maxDepth)
    {
        $data = clone $this;
        $data->maxDepth = (int) $maxDepth;

        return $data;
    }

    /**
     * Limits the number of elements per depth level.
     *
     * @param int $maxItemsPerDepth The max number of items dumped per depth level
     *
     * @return self A clone of $this
     */
    public function withMaxItemsPerDepth($maxItemsPerDepth)
    {
        $data = clone $this;
        $data->maxItemsPerDepth = (int) $maxItemsPerDepth;

        return $data;
    }

    /**
     * Enables/disables objects' identifiers tracking.
     *
     * @param bool $useRefHandles False to hide global ref. handles
     *
     * @return self A clone of $this
     */
    public function withRefHandles($useRefHandles)
    {
        $data = clone $this;
        $data->useRefHandles = $useRefHandles ? -1 : 0;

        return $data;
    }

    /**
     * Returns a depth limited clone of $this.
     *
     * @param int  $maxDepth         The max dumped depth level
     * @param int  $maxItemsPerDepth The max number of items dumped per depth level
     * @param bool $useRefHandles    False to hide ref. handles
     *
     * @return self A depth limited clone of $this
     *
     * @deprecated since Symfony 2.7, to be removed in 3.0. Use withMaxDepth, withMaxItemsPerDepth or withRefHandles instead.
     */
    public function getLimitedClone($maxDepth, $maxItemsPerDepth, $useRefHandles = true)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0. Use withMaxDepth, withMaxItemsPerDepth or withRefHandles methods instead.', E_USER_DEPRECATED);

        $data = clone $this;
        $data->maxDepth = (int) $maxDepth;
        $data->maxItemsPerDepth = (int) $maxItemsPerDepth;
        $data->useRefHandles = $useRefHandles ? -1 : 0;

        return $data;
    }

    /**
     * Dumps data with a DumperInterface dumper.
     */
    public function dump(DumperInterface $dumper)
    {
        $refs = array(0);
        $this->dumpItem($dumper, new Cursor(), $refs, $this->data[0][0]);
    }

    /**
     * Depth-first dumping of items.
     *
     * @param DumperInterface $dumper The dumper being used for dumping
     * @param Cursor          $cursor A cursor used for tracking dumper state position
     * @param array           &$refs  A map of all references discovered while dumping
     * @param mixed           $item   A Stub object or the original value being dumped
     */
    private function dumpItem($dumper, $cursor, &$refs, $item)
    {
        $cursor->refIndex = 0;
        $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
        $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0;
        $firstSeen = true;

        if (!$item instanceof Stub) {
            $type = gettype($item);
        } elseif (Stub::TYPE_REF === $item->type) {
            if ($item->handle) {
                if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
                    $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
                } else {
                    $firstSeen = false;
                }
                $cursor->hardRefTo = $refs[$r];
                $cursor->hardRefHandle = $this->useRefHandles & $item->handle;
                $cursor->hardRefCount = $item->refCount;
            }
            $type = $item->class ?: gettype($item->value);
            $item = $item->value;
        }
        if ($item instanceof Stub) {
            if ($item->refCount) {
                if (!isset($refs[$r = $item->handle])) {
                    $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
                } else {
                    $firstSeen = false;
                }
                $cursor->softRefTo = $refs[$r];
            }
            $cursor->softRefHandle = $this->useRefHandles & $item->handle;
            $cursor->softRefCount = $item->refCount;
            $cut = $item->cut;

            if ($item->position && $firstSeen) {
                $children = $this->data[$item->position];

                if ($cursor->stop) {
                    if ($cut >= 0) {
                        $cut += count($children);
                    }
                    $children = array();
                }
            } else {
                $children = array();
            }
            switch ($item->type) {
                case Stub::TYPE_STRING:
                    $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut);
                    break;

                case Stub::TYPE_ARRAY:
                    $item = clone $item;
                    $item->type = $item->class;
                    $item->class = $item->value;
                    // no break
                case Stub::TYPE_OBJECT:
                case Stub::TYPE_RESOURCE:
                    $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
                    $dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
                    if ($withChildren) {
                        $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type);
                    } elseif ($children && 0 <= $cut) {
                        $cut += count($children);
                    }
                    $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
                    break;

                default:
                    throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type));
            }
        } elseif ('array' === $type) {
            $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false);
            $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0);
        } elseif ('string' === $type) {
            $dumper->dumpString($cursor, $item, false, 0);
        } else {
            $dumper->dumpScalar($cursor, $type, $item);
        }
    }

    /**
     * Dumps children of hash structures.
     *
     * @param DumperInterface $dumper
     * @param Cursor          $parentCursor The cursor of the parent hash
     * @param array           &$refs        A map of all references discovered while dumping
     * @param array           $children     The children to dump
     * @param int             $hashCut      The number of items removed from the original hash
     * @param string          $hashType     A Cursor::HASH_* const
     *
     * @return int The final number of removed items
     */
    private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType)
    {
        $cursor = clone $parentCursor;
        ++$cursor->depth;
        $cursor->hashType = $hashType;
        $cursor->hashIndex = 0;
        $cursor->hashLength = count($children);
        $cursor->hashCut = $hashCut;
        foreach ($children as $key => $child) {
            $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
            $cursor->hashKey = $key;
            $this->dumpItem($dumper, $cursor, $refs, $child);
            if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
                $parentCursor->stop = true;

                return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut;
            }
        }

        return $hashCut;
    }
}
<?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\VarDumper\Cloner;

/**
 * DumperInterface used by Data objects.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface DumperInterface
{
    /**
     * Dumps a scalar value.
     *
     * @param Cursor $cursor The Cursor position in the dump
     * @param string $type   The PHP type of the value being dumped
     * @param scalar $value  The scalar value being dumped
     */
    public function dumpScalar(Cursor $cursor, $type, $value);

    /**
     * Dumps a string.
     *
     * @param Cursor $cursor The Cursor position in the dump
     * @param string $str    The string being dumped
     * @param bool   $bin    Whether $str is UTF-8 or binary encoded
     * @param int    $cut    The number of characters $str has been cut by
     */
    public function dumpString(Cursor $cursor, $str, $bin, $cut);

    /**
     * Dumps while entering an hash.
     *
     * @param Cursor $cursor   The Cursor position in the dump
     * @param int    $type     A Cursor::HASH_* const for the type of hash
     * @param string $class    The object class, resource type or array count
     * @param bool   $hasChild When the dump of the hash has child item
     */
    public function enterHash(Cursor $cursor, $type, $class, $hasChild);

    /**
     * Dumps while leaving an hash.
     *
     * @param Cursor $cursor   The Cursor position in the dump
     * @param int    $type     A Cursor::HASH_* const for the type of hash
     * @param string $class    The object class, resource type or array count
     * @param bool   $hasChild When the dump of the hash has child item
     * @param int    $cut      The number of items the hash has been cut by
     */
    public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut);
}
<?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\VarDumper\Cloner;

/**
 * Represents the main properties of a PHP variable.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Stub
{
    const TYPE_REF = 'ref';
    const TYPE_STRING = 'string';
    const TYPE_ARRAY = 'array';
    const TYPE_OBJECT = 'object';
    const TYPE_RESOURCE = 'resource';

    const STRING_BINARY = 'bin';
    const STRING_UTF8 = 'utf8';

    const ARRAY_ASSOC = 'assoc';
    const ARRAY_INDEXED = 'indexed';

    public $type = self::TYPE_REF;
    public $class = '';
    public $value;
    public $cut = 0;
    public $handle = 0;
    public $refCount = 0;
    public $position = 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\VarDumper\Cloner;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
class VarCloner extends AbstractCloner
{
    private static $hashMask = 0;
    private static $hashOffset = 0;

    /**
     * {@inheritdoc}
     */
    protected function doClone($var)
    {
        $useExt = $this->useExt;
        $len = 1;                       // Length of $queue
        $pos = 0;                       // Number of cloned items past the first level
        $refsCounter = 0;               // Hard references counter
        $queue = array(array($var));    // This breadth-first queue is the return value
        $arrayRefs = array();           // Map of queue indexes to stub array objects
        $hardRefs = array();            // Map of original zval hashes to stub objects
        $objRefs = array();             // Map of original object handles to their stub object couterpart
        $resRefs = array();             // Map of original resource handles to their stub object couterpart
        $values = array();              // Map of stub objects' hashes to original values
        $maxItems = $this->maxItems;
        $maxString = $this->maxString;
        $cookie = (object) array();     // Unique object used to detect hard references
        $gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable
        $a = null;                      // Array cast for nested structures
        $stub = null;                   // Stub capturing the main properties of an original item value
                                        // or null if the original value is used directly
        $zval = array(                  // Main properties of the current value
            'type' => null,
            'zval_isref' => null,
            'zval_hash' => null,
            'array_count' => null,
            'object_class' => null,
            'object_handle' => null,
            'resource_type' => null,
        );
        if (!self::$hashMask) {
            self::initHashMask();
        }
        $hashMask = self::$hashMask;
        $hashOffset = self::$hashOffset;

        for ($i = 0; $i < $len; ++$i) {
            $indexed = true;            // Whether the currently iterated array is numerically indexed or not
            $j = -1;                    // Position in the currently iterated array
            $fromObjCast = array_keys($queue[$i]);
            $fromObjCast = array_keys(array_flip($fromObjCast)) !== $fromObjCast;
            $refs = $vals = $fromObjCast ? array_values($queue[$i]) : $queue[$i];
            foreach ($queue[$i] as $k => $v) {
                // $k is the original key
                // $v is the original value or a stub object in case of hard references
                if ($k !== ++$j) {
                    $indexed = false;
                }
                if ($fromObjCast) {
                    $k = $j;
                }
                if ($useExt) {
                    $zval = symfony_zval_info($k, $refs);
                } else {
                    $refs[$k] = $cookie;
                    if ($zval['zval_isref'] = $vals[$k] === $cookie) {
                        $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null;
                    }
                    $zval['type'] = gettype($v);
                }
                if ($zval['zval_isref']) {
                    $vals[$k] = &$stub;         // Break hard references to make $queue completely
                    unset($stub);               // independent from the original structure
                    if (isset($hardRefs[$zval['zval_hash']])) {
                        $vals[$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($refs[$k] = $v);
                        if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
                            ++$v->value->refCount;
                        }
                        ++$v->refCount;
                        continue;
                    }
                }
                // Create $stub when the original value $v can not be used directly
                // If $v is a nested structure, put that structure in array $a
                switch ($zval['type']) {
                    case 'string':
                        if (isset($v[0]) && !preg_match('//u', $v)) {
                            $stub = new Stub();
                            $stub->type = Stub::TYPE_STRING;
                            $stub->class = Stub::STRING_BINARY;
                            if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
                                $stub->cut = $cut;
                                $stub->value = substr($v, 0, -$cut);
                            } else {
                                $stub->value = $v;
                            }
                        } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) {
                            $stub = new Stub();
                            $stub->type = Stub::TYPE_STRING;
                            $stub->class = Stub::STRING_UTF8;
                            $stub->cut = $cut;
                            $stub->value = mb_substr($v, 0, $maxString, 'UTF-8');
                        }
                        break;

                    case 'integer':
                        break;

                    case 'array':
                        if ($v) {
                            $stub = $arrayRefs[$len] = new Stub();
                            $stub->type = Stub::TYPE_ARRAY;
                            $stub->class = Stub::ARRAY_ASSOC;

                            // Copies of $GLOBALS have very strange behavior,
                            // let's detect them with some black magic
                            $a = $v;
                            $a[$gid] = true;

                            // Happens with copies of $GLOBALS
                            if (isset($v[$gid])) {
                                unset($v[$gid]);
                                $a = array();
                                foreach ($v as $gk => &$gv) {
                                    $a[$gk] = &$gv;
                                }
                            } else {
                                $a = $v;
                            }

                            $stub->value = $zval['array_count'] ?: count($a);
                        }
                        break;

                    case 'object':
                        if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) {
                            $stub = new Stub();
                            $stub->type = Stub::TYPE_OBJECT;
                            $stub->class = $zval['object_class'] ?: get_class($v);
                            $stub->value = $v;
                            $stub->handle = $h;
                            $a = $this->castObject($stub, 0 < $i);
                            if ($v !== $stub->value) {
                                if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
                                    break;
                                }
                                if ($useExt) {
                                    $zval['type'] = $stub->value;
                                    $zval = symfony_zval_info('type', $zval);
                                    $h = $zval['object_handle'];
                                } else {
                                    $h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, PHP_INT_SIZE));
                                }
                                $stub->handle = $h;
                            }
                            $stub->value = null;
                            if (0 <= $maxItems && $maxItems <= $pos) {
                                $stub->cut = count($a);
                                $a = null;
                            }
                        }
                        if (empty($objRefs[$h])) {
                            $objRefs[$h] = $stub;
                        } else {
                            $stub = $objRefs[$h];
                            ++$stub->refCount;
                            $a = null;
                        }
                        break;

                    case 'resource':
                    case 'unknown type':
                    case 'resource (closed)':
                        if (empty($resRefs[$h = (int) $v])) {
                            $stub = new Stub();
                            $stub->type = Stub::TYPE_RESOURCE;
                            if ('Unknown' === $stub->class = $zval['resource_type'] ?: @get_resource_type($v)) {
                                $stub->class = 'Closed';
                            }
                            $stub->value = $v;
                            $stub->handle = $h;
                            $a = $this->castResource($stub, 0 < $i);
                            $stub->value = null;
                            if (0 <= $maxItems && $maxItems <= $pos) {
                                $stub->cut = count($a);
                                $a = null;
                            }
                        }
                        if (empty($resRefs[$h])) {
                            $resRefs[$h] = $stub;
                        } else {
                            $stub = $resRefs[$h];
                            ++$stub->refCount;
                            $a = null;
                        }
                        break;
                }

                if (isset($stub)) {
                    if ($zval['zval_isref']) {
                        if ($useExt) {
                            $vals[$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
                            $v->value = $stub;
                        } else {
                            $refs[$k] = new Stub();
                            $refs[$k]->value = $stub;
                            $h = spl_object_hash($refs[$k]);
                            $vals[$k] = $hardRefs[$h] = &$refs[$k];
                            $values[$h] = $v;
                        }
                        $vals[$k]->handle = ++$refsCounter;
                    } else {
                        $vals[$k] = $stub;
                    }

                    if ($a) {
                        if ($i && 0 <= $maxItems) {
                            $k = count($a);
                            if ($pos < $maxItems) {
                                if ($maxItems < $pos += $k) {
                                    $a = array_slice($a, 0, $maxItems - $pos);
                                    if ($stub->cut >= 0) {
                                        $stub->cut += $pos - $maxItems;
                                    }
                                }
                            } else {
                                if ($stub->cut >= 0) {
                                    $stub->cut += $k;
                                }
                                $stub = $a = null;
                                unset($arrayRefs[$len]);
                                continue;
                            }
                        }
                        $queue[$len] = $a;
                        $stub->position = $len++;
                    }
                    $stub = $a = null;
                } elseif ($zval['zval_isref']) {
                    if ($useExt) {
                        $vals[$k] = $hardRefs[$zval['zval_hash']] = new Stub();
                        $vals[$k]->value = $v;
                    } else {
                        $refs[$k] = $vals[$k] = new Stub();
                        $refs[$k]->value = $v;
                        $h = spl_object_hash($refs[$k]);
                        $hardRefs[$h] = &$refs[$k];
                        $values[$h] = $v;
                    }
                    $vals[$k]->handle = ++$refsCounter;
                }
            }

            if ($fromObjCast) {
                $refs = $vals;
                $vals = array();
                $j = -1;
                foreach ($queue[$i] as $k => $v) {
                    foreach (array($k => $v) as $a => $v) {
                    }
                    if ($a !== $k) {
                        $vals = (object) $vals;
                        $vals->{$k} = $refs[++$j];
                        $vals = (array) $vals;
                    } else {
                        $vals[$k] = $refs[++$j];
                    }
                }
            }

            $queue[$i] = $vals;

            if (isset($arrayRefs[$i])) {
                if ($indexed) {
                    $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
                }
                unset($arrayRefs[$i]);
            }
        }

        foreach ($values as $h => $v) {
            $hardRefs[$h] = $v;
        }

        return $queue;
    }

    private static function initHashMask()
    {
        $obj = (object) array();
        self::$hashOffset = 16 - PHP_INT_SIZE;
        self::$hashMask = -1;

        if (defined('HHVM_VERSION')) {
            self::$hashOffset += 16;
        } else {
            // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
            $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
            foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
                if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) {
                    $frame['line'] = 0;
                    break;
                }
            }
            if (!empty($frame['line'])) {
                ob_start();
                debug_zval_dump($obj);
                self::$hashMask = (int) substr(ob_get_clean(), 17);
            }
        }

        self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, PHP_INT_SIZE));
    }
}
<?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\VarDumper\Dumper;

use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\DumperInterface;

/**
 * Abstract mechanism for dumping a Data object.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{
    public static $defaultOutput = 'php://output';

    protected $line = '';
    protected $lineDumper;
    protected $outputStream;
    protected $decimalPoint; // This is locale dependent
    protected $indentPad = '  ';

    private $charset;

    /**
     * @param callable|resource|string|null $output  A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput
     * @param string                        $charset The default character encoding to use for non-UTF8 strings
     */
    public function __construct($output = null, $charset = null)
    {
        $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
        $this->decimalPoint = localeconv();
        $this->decimalPoint = $this->decimalPoint['decimal_point'];
        $this->setOutput($output ?: static::$defaultOutput);
        if (!$output && is_string(static::$defaultOutput)) {
            static::$defaultOutput = $this->outputStream;
        }
    }

    /**
     * Sets the output destination of the dumps.
     *
     * @param callable|resource|string $output A line dumper callable, an opened stream or an output path
     *
     * @return callable|resource|string The previous output destination
     */
    public function setOutput($output)
    {
        $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;

        if (is_callable($output)) {
            $this->outputStream = null;
            $this->lineDumper = $output;
        } else {
            if (is_string($output)) {
                $output = fopen($output, 'wb');
            }
            $this->outputStream = $output;
            $this->lineDumper = array($this, 'echoLine');
        }

        return $prev;
    }

    /**
     * Sets the default character encoding to use for non-UTF8 strings.
     *
     * @param string $charset The default character encoding to use for non-UTF8 strings
     *
     * @return string The previous charset
     */
    public function setCharset($charset)
    {
        $prev = $this->charset;

        $charset = strtoupper($charset);
        $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset;

        $this->charset = $charset;

        return $prev;
    }

    /**
     * Sets the indentation pad string.
     *
     * @param string $pad A string the will be prepended to dumped lines, repeated by nesting level
     *
     * @return string The indent pad
     */
    public function setIndentPad($pad)
    {
        $prev = $this->indentPad;
        $this->indentPad = $pad;

        return $prev;
    }

    /**
     * Dumps a Data object.
     *
     * @param Data                          $data   A Data object
     * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path
     */
    public function dump(Data $data, $output = null)
    {
        $this->decimalPoint = localeconv();
        $this->decimalPoint = $this->decimalPoint['decimal_point'];

        $exception = null;
        if ($output) {
            $prevOutput = $this->setOutput($output);
        }
        try {
            $data->dump($this);
            $this->dumpLine(-1);
        } catch (\Exception $exception) {
            // Re-thrown below
        } catch (\Throwable $exception) {
            // Re-thrown below
        }
        if ($output) {
            $this->setOutput($prevOutput);
        }
        if (null !== $exception) {
            throw $exception;
        }
    }

    /**
     * Dumps the current line.
     *
     * @param int $depth The recursive depth in the dumped structure for the line being dumped,
     *                   or -1 to signal the end-of-dump to the line dumper callable
     */
    protected function dumpLine($depth)
    {
        call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad);
        $this->line = '';
    }

    /**
     * Generic line dumper callback.
     *
     * @param string $line      The line to write
     * @param int    $depth     The recursive depth in the dumped structure
     * @param string $indentPad The line indent pad
     */
    protected function echoLine($line, $depth, $indentPad)
    {
        if (-1 !== $depth) {
            fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n");
        }
    }

    /**
     * Converts a non-UTF-8 string to UTF-8.
     *
     * @param string $s The non-UTF-8 string to convert
     *
     * @return string The string converted to UTF-8
     */
    protected function utf8Encode($s)
    {
        if (!function_exists('iconv')) {
            throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
        }
        if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) {
            return $c;
        }
        if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) {
            return $c;
        }

        return iconv('CP850', 'UTF-8', $s);
    }
}
<?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\VarDumper\Dumper;

use Symfony\Component\VarDumper\Cloner\Cursor;

/**
 * CliDumper dumps variables for command line output.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class CliDumper extends AbstractDumper
{
    public static $defaultColors;
    public static $defaultOutput = 'php://stdout';

    protected $colors;
    protected $maxStringWidth = 0;
    protected $styles = array(
        // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
        'default' => '38;5;208',
        'num' => '1;38;5;38',
        'const' => '1;38;5;208',
        'str' => '1;38;5;113',
        'note' => '38;5;38',
        'ref' => '38;5;247',
        'public' => '',
        'protected' => '',
        'private' => '',
        'meta' => '38;5;170',
        'key' => '38;5;113',
        'index' => '38;5;38',
    );

    protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/';
    protected static $controlCharsMap = array(
        "\t" => '\t',
        "\n" => '\n',
        "\v" => '\v',
        "\f" => '\f',
        "\r" => '\r',
        "\033" => '\e',
    );

    /**
     * {@inheritdoc}
     */
    public function __construct($output = null, $charset = null)
    {
        parent::__construct($output, $charset);

        if ('\\' === DIRECTORY_SEPARATOR && 'ON' !== @getenv('ConEmuANSI') && 'xterm' !== @getenv('TERM')) {
            // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI
            $this->setStyles(array(
                'default' => '31',
                'num' => '1;34',
                'const' => '1;31',
                'str' => '1;32',
                'note' => '34',
                'ref' => '1;30',
                'meta' => '35',
                'key' => '32',
                'index' => '34',
            ));
        }
    }

    /**
     * Enables/disables colored output.
     *
     * @param bool $colors
     */
    public function setColors($colors)
    {
        $this->colors = (bool) $colors;
    }

    /**
     * Sets the maximum number of characters per line for dumped strings.
     *
     * @param int $maxStringWidth
     */
    public function setMaxStringWidth($maxStringWidth)
    {
        $this->maxStringWidth = (int) $maxStringWidth;
    }

    /**
     * Configures styles.
     *
     * @param array $styles A map of style names to style definitions
     */
    public function setStyles(array $styles)
    {
        $this->styles = $styles + $this->styles;
    }

    /**
     * {@inheritdoc}
     */
    public function dumpScalar(Cursor $cursor, $type, $value)
    {
        $this->dumpKey($cursor);

        $style = 'const';
        $attr = array();

        switch ($type) {
            case 'integer':
                $style = 'num';
                break;

            case 'double':
                $style = 'num';

                switch (true) {
                    case INF === $value:  $value = 'INF'; break;
                    case -INF === $value: $value = '-INF'; break;
                    case is_nan($value):  $value = 'NAN'; break;
                    default:
                        $value = (string) $value;
                        if (false === strpos($value, $this->decimalPoint)) {
                            $value .= $this->decimalPoint.'0';
                        }
                        break;
                }
                break;

            case 'NULL':
                $value = 'null';
                break;

            case 'boolean':
                $value = $value ? 'true' : 'false';
                break;

            default:
                $attr['value'] = isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value;
                $value = isset($type[0]) && !preg_match('//u', $type) ? $this->utf8Encode($type) : $type;
                break;
        }

        $this->line .= $this->style($style, $value, $attr);

        $this->dumpLine($cursor->depth, true);
    }

    /**
     * {@inheritdoc}
     */
    public function dumpString(Cursor $cursor, $str, $bin, $cut)
    {
        $this->dumpKey($cursor);

        if ($bin) {
            $str = $this->utf8Encode($str);
        }
        if ('' === $str) {
            $this->line .= '""';
            $this->dumpLine($cursor->depth, true);
        } else {
            $attr = array(
                'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
                'binary' => $bin,
            );
            $str = explode("\n", $str);
            if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
                unset($str[1]);
                $str[0] .= "\n";
            }
            $m = count($str) - 1;
            $i = $lineCut = 0;

            if ($bin) {
                $this->line .= 'b';
            }

            if ($m) {
                $this->line .= '"""';
                $this->dumpLine($cursor->depth);
            } else {
                $this->line .= '"';
            }

            foreach ($str as $str) {
                if ($i < $m) {
                    $str .= "\n";
                }
                if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) {
                    $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8');
                    $lineCut = $len - $this->maxStringWidth;
                }
                if ($m && 0 < $cursor->depth) {
                    $this->line .= $this->indentPad;
                }
                if ('' !== $str) {
                    $this->line .= $this->style('str', $str, $attr);
                }
                if ($i++ == $m) {
                    if ($m) {
                        if ('' !== $str) {
                            $this->dumpLine($cursor->depth);
                            if (0 < $cursor->depth) {
                                $this->line .= $this->indentPad;
                            }
                        }
                        $this->line .= '"""';
                    } else {
                        $this->line .= '"';
                    }
                    if ($cut < 0) {
                        $this->line .= '…';
                        $lineCut = 0;
                    } elseif ($cut) {
                        $lineCut += $cut;
                    }
                }
                if ($lineCut) {
                    $this->line .= '…'.$lineCut;
                    $lineCut = 0;
                }

                $this->dumpLine($cursor->depth, $i > $m);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function enterHash(Cursor $cursor, $type, $class, $hasChild)
    {
        $this->dumpKey($cursor);

        if (!preg_match('//u', $class)) {
            $class = $this->utf8Encode($class);
        }
        if (Cursor::HASH_OBJECT === $type) {
            $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{';
        } elseif (Cursor::HASH_RESOURCE === $type) {
            $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
        } else {
            $prefix = $class ? $this->style('note', 'array:'.$class).' [' : '[';
        }

        if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
            $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), array('count' => $cursor->softRefCount));
        } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) {
            $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, array('count' => $cursor->hardRefCount));
        } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) {
            $prefix = substr($prefix, 0, -1);
        }

        $this->line .= $prefix;

        if ($hasChild) {
            $this->dumpLine($cursor->depth);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
    {
        $this->dumpEllipsis($cursor, $hasChild, $cut);
        $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : ''));
        $this->dumpLine($cursor->depth, true);
    }

    /**
     * Dumps an ellipsis for cut children.
     *
     * @param Cursor $cursor   The Cursor position in the dump
     * @param bool   $hasChild When the dump of the hash has child item
     * @param int    $cut      The number of items the hash has been cut by
     */
    protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
    {
        if ($cut) {
            $this->line .= ' …';
            if (0 < $cut) {
                $this->line .= $cut;
            }
            if ($hasChild) {
                $this->dumpLine($cursor->depth + 1);
            }
        }
    }

    /**
     * Dumps a key in a hash structure.
     *
     * @param Cursor $cursor The Cursor position in the dump
     */
    protected function dumpKey(Cursor $cursor)
    {
        if (null !== $key = $cursor->hashKey) {
            if ($cursor->hashKeyIsBinary) {
                $key = $this->utf8Encode($key);
            }
            $attr = array('binary' => $cursor->hashKeyIsBinary);
            $bin = $cursor->hashKeyIsBinary ? 'b' : '';
            $style = 'key';
            switch ($cursor->hashType) {
                default:
                case Cursor::HASH_INDEXED:
                    $style = 'index';
                    // no break
                case Cursor::HASH_ASSOC:
                    if (is_int($key)) {
                        $this->line .= $this->style($style, $key).' => ';
                    } else {
                        $this->line .= $bin.'"'.$this->style($style, $key).'" => ';
                    }
                    break;

                case Cursor::HASH_RESOURCE:
                    $key = "\0~\0".$key;
                    // no break
                case Cursor::HASH_OBJECT:
                    if (!isset($key[0]) || "\0" !== $key[0]) {
                        $this->line .= '+'.$bin.$this->style('public', $key).': ';
                    } elseif (0 < strpos($key, "\0", 1)) {
                        $key = explode("\0", substr($key, 1), 2);

                        switch ($key[0]) {
                            case '+': // User inserted keys
                                $attr['dynamic'] = true;
                                $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": ';
                                break 2;
                            case '~':
                                $style = 'meta';
                                break;
                            case '*':
                                $style = 'protected';
                                $bin = '#'.$bin;
                                break;
                            default:
                                $attr['class'] = $key[0];
                                $style = 'private';
                                $bin = '-'.$bin;
                                break;
                        }

                        $this->line .= $bin.$this->style($style, $key[1], $attr).': ';
                    } else {
                        // This case should not happen
                        $this->line .= '-'.$bin.'"'.$this->style('private', $key, array('class' => '')).'": ';
                    }
                    break;
            }

            if ($cursor->hardRefTo) {
                $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), array('count' => $cursor->hardRefCount)).' ';
            }
        }
    }

    /**
     * Decorates a value with some style.
     *
     * @param string $style The type of style being applied
     * @param string $value The value being styled
     * @param array  $attr  Optional context information
     *
     * @return string The value with style decoration
     */
    protected function style($style, $value, $attr = array())
    {
        if (null === $this->colors) {
            $this->colors = $this->supportsColors();
        }

        $style = $this->styles[$style];

        $map = static::$controlCharsMap;
        $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
        $endCchr = $this->colors ? "\033[m\033[{$style}m" : '';
        $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
            $s = $startCchr;
            $c = $c[$i = 0];
            do {
                $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i]));
            } while (isset($c[++$i]));

            return $s.$endCchr;
        }, $value, -1, $cchrCount);

        if ($this->colors) {
            if ($cchrCount && "\033" === $value[0]) {
                $value = substr($value, strlen($startCchr));
            } else {
                $value = "\033[{$style}m".$value;
            }
            if ($cchrCount && $endCchr === substr($value, -strlen($endCchr))) {
                $value = substr($value, 0, -strlen($endCchr));
            } else {
                $value .= "\033[{$this->styles['default']}m";
            }
        }

        return $value;
    }

    /**
     * @return bool Tells if the current output stream supports ANSI colors or not
     */
    protected function supportsColors()
    {
        if ($this->outputStream !== static::$defaultOutput) {
            return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream));
        }
        if (null !== static::$defaultColors) {
            return static::$defaultColors;
        }
        if (isset($_SERVER['argv'][1])) {
            $colors = $_SERVER['argv'];
            $i = count($colors);
            while (--$i > 0) {
                if (isset($colors[$i][5])) {
                    switch ($colors[$i]) {
                        case '--ansi':
                        case '--color':
                        case '--color=yes':
                        case '--color=force':
                        case '--color=always':
                            return static::$defaultColors = true;

                        case '--no-ansi':
                        case '--color=no':
                        case '--color=none':
                        case '--color=never':
                            return static::$defaultColors = false;
                    }
                }
            }
        }

        if ('\\' === DIRECTORY_SEPARATOR) {
            static::$defaultColors = @(
                '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM')
            );
        } elseif (function_exists('posix_isatty')) {
            $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null);
            $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream;
            static::$defaultColors = @posix_isatty($h);
        } else {
            static::$defaultColors = false;
        }

        return static::$defaultColors;
    }

    /**
     * {@inheritdoc}
     */
    protected function dumpLine($depth, $endOfValue = false)
    {
        if ($this->colors) {
            $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line);
        }
        parent::dumpLine($depth);
    }
}
<?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\VarDumper\Dumper;

use Symfony\Component\VarDumper\Cloner\Data;

/**
 * DataDumperInterface for dumping Data objects.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface DataDumperInterface
{
    /**
     * Dumps a Data object.
     *
     * @param Data $data A Data object
     */
    public function dump(Data $data);
}
<?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\VarDumper\Dumper;

use Symfony\Component\VarDumper\Cloner\Cursor;
use Symfony\Component\VarDumper\Cloner\Data;

/**
 * HtmlDumper dumps variables as HTML.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class HtmlDumper extends CliDumper
{
    public static $defaultOutput = 'php://output';

    protected $dumpHeader;
    protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">';
    protected $dumpSuffix = '</pre><script>Sfdump("%s")</script>';
    protected $dumpId = 'sf-dump';
    protected $colors = true;
    protected $headerIsDumped = false;
    protected $lastDepth = -1;
    protected $styles = array(
        'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
        'num' => 'font-weight:bold; color:#1299DA',
        'const' => 'font-weight:bold',
        'str' => 'font-weight:bold; color:#56DB3A',
        'note' => 'color:#1299DA',
        'ref' => 'color:#A0A0A0',
        'public' => 'color:#FFFFFF',
        'protected' => 'color:#FFFFFF',
        'private' => 'color:#FFFFFF',
        'meta' => 'color:#B729D9',
        'key' => 'color:#56DB3A',
        'index' => 'color:#1299DA',
    );

    /**
     * {@inheritdoc}
     */
    public function __construct($output = null, $charset = null)
    {
        AbstractDumper::__construct($output, $charset);
        $this->dumpId = 'sf-dump-'.mt_rand();
    }

    /**
     * {@inheritdoc}
     */
    public function setStyles(array $styles)
    {
        $this->headerIsDumped = false;
        $this->styles = $styles + $this->styles;
    }

    /**
     * Sets an HTML header that will be dumped once in the output stream.
     *
     * @param string $header An HTML string
     */
    public function setDumpHeader($header)
    {
        $this->dumpHeader = $header;
    }

    /**
     * Sets an HTML prefix and suffix that will encapse every single dump.
     *
     * @param string $prefix The prepended HTML string
     * @param string $suffix The appended HTML string
     */
    public function setDumpBoundaries($prefix, $suffix)
    {
        $this->dumpPrefix = $prefix;
        $this->dumpSuffix = $suffix;
    }

    /**
     * {@inheritdoc}
     */
    public function dump(Data $data, $output = null)
    {
        parent::dump($data, $output);
        $this->dumpId = 'sf-dump-'.mt_rand();
    }

    /**
     * Dumps the HTML header.
     */
    protected function getDumpHeader()
    {
        $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;

        if (null !== $this->dumpHeader) {
            return $this->dumpHeader;
        }

        $line = <<<'EOHTML'
<script>
Sfdump = window.Sfdump || (function (doc) {

var refStyle = doc.createElement('style'),
    rxEsc = /([.*+?^${}()|\[\]\/\\])/g,
    idRx = /\bsf-dump-\d+-ref[012]\w+\b/,
    keyHint = 0 <= navigator.platform.toUpperCase().indexOf('MAC') ? 'Cmd' : 'Ctrl',
    addEventListener = function (e, n, cb) {
        e.addEventListener(n, cb, false);
    };

(doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle);

if (!doc.addEventListener) {
    addEventListener = function (element, eventName, callback) {
        element.attachEvent('on' + eventName, function (e) {
            e.preventDefault = function () {e.returnValue = false;};
            e.target = e.srcElement;
            callback(e);
        });
    };
}

function toggle(a, recursive) {
    var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass;

    if (/\bsf-dump-compact\b/.test(oldClass)) {
        arrow = '▼';
        newClass = 'sf-dump-expanded';
    } else if (/\bsf-dump-expanded\b/.test(oldClass)) {
        arrow = '▶';
        newClass = 'sf-dump-compact';
    } else {
        return false;
    }

    a.lastChild.innerHTML = arrow;
    s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass);

    if (recursive) {
        try {
            a = s.querySelectorAll('.'+oldClass);
            for (s = 0; s < a.length; ++s) {
                if (-1 == a[s].className.indexOf(newClass)) {
                    a[s].className = newClass;
                    a[s].previousSibling.lastChild.innerHTML = arrow;
                }
            }
        } catch (e) {
        }
    }

    return true;
};

return function (root) {
    root = doc.getElementById(root);

    function a(e, f) {
        addEventListener(root, e, function (e) {
            if ('A' == e.target.tagName) {
                f(e.target, e);
            } else if ('A' == e.target.parentNode.tagName) {
                f(e.target.parentNode, e);
            }
        });
    };
    function isCtrlKey(e) {
        return e.ctrlKey || e.metaKey;
    }
    addEventListener(root, 'mouseover', function (e) {
        if ('' != refStyle.innerHTML) {
            refStyle.innerHTML = '';
        }
    });
    a('mouseover', function (a) {
        if (a = idRx.exec(a.className)) {
            try {
                refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}';
            } catch (e) {
            }
        }
    });
    a('click', function (a, e) {
        if (/\bsf-dump-toggle\b/.test(a.className)) {
            e.preventDefault();
            if (!toggle(a, isCtrlKey(e))) {
                var r = doc.getElementById(a.getAttribute('href').substr(1)),
                    s = r.previousSibling,
                    f = r.parentNode,
                    t = a.parentNode;
                t.replaceChild(r, a);
                f.replaceChild(a, s);
                t.insertBefore(s, r);
                f = f.firstChild.nodeValue.match(indentRx);
                t = t.firstChild.nodeValue.match(indentRx);
                if (f && t && f[0] !== t[0]) {
                    r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]);
                }
                if (/\bsf-dump-compact\b/.test(r.className)) {
                    toggle(s, isCtrlKey(e));
                }
            }

            if (doc.getSelection) {
                try {
                    doc.getSelection().removeAllRanges();
                } catch (e) {
                    doc.getSelection().empty();
                }
            } else {
                doc.selection.empty();
            }
        }
    });

    var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || '  ').replace(rxEsc, '\\$1')+')+', 'm'),
        elt = root.getElementsByTagName('A'),
        len = elt.length,
        i = 0,
        t = [];

    while (i < len) t.push(elt[i++]);

    elt = root.getElementsByTagName('SAMP');
    len = elt.length;
    i = 0;

    while (i < len) t.push(elt[i++]);

    root = t;
    len = t.length;
    i = t = 0;

    while (i < len) {
        elt = root[i];
        if ("SAMP" == elt.tagName) {
            elt.className = "sf-dump-expanded";
            a = elt.previousSibling || {};
            if ('A' != a.tagName) {
                a = doc.createElement('A');
                a.className = 'sf-dump-ref';
                elt.parentNode.insertBefore(a, elt);
            } else {
                a.innerHTML += ' ';
            }
            a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children';
            a.innerHTML += '<span>▼</span>';
            a.className += ' sf-dump-toggle';
            if (!/\bsf-dump\b/.test(elt.parentNode.className)) {
                toggle(a);
            }
        } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) {
            a = a.substr(1);
            elt.className += ' '+a;

            if (/[\[{]$/.test(elt.previousSibling.nodeValue)) {
                a = a != elt.nextSibling.id && doc.getElementById(a);
                try {
                    t = a.nextSibling;
                    elt.appendChild(a);
                    t.parentNode.insertBefore(a, t);
                    if (/^[@#]/.test(elt.innerHTML)) {
                        elt.innerHTML += ' <span>▶</span>';
                    } else {
                        elt.innerHTML = '<span>▶</span>';
                        elt.className = 'sf-dump-ref';
                    }
                    elt.className += ' sf-dump-toggle';
                } catch (e) {
                    if ('&' == elt.innerHTML.charAt(0)) {
                        elt.innerHTML = '…';
                        elt.className = 'sf-dump-ref';
                    }
                }
            }
        }
        ++i;
    }
};

})(document);
</script><style>
pre.sf-dump {
    display: block;
    white-space: pre;
    padding: 5px;
}
pre.sf-dump span {
    display: inline;
}
pre.sf-dump .sf-dump-compact {
    display: none;
}
pre.sf-dump abbr {
    text-decoration: none;
    border: none;
    cursor: help;
}
pre.sf-dump a {
    text-decoration: none;
    cursor: pointer;
    border: 0;
    outline: none;
}
EOHTML;

        foreach ($this->styles as $class => $style) {
            $line .= 'pre.sf-dump'.('default' !== $class ? ' .sf-dump-'.$class : '').'{'.$style.'}';
        }

        return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
    }

    /**
     * {@inheritdoc}
     */
    public function enterHash(Cursor $cursor, $type, $class, $hasChild)
    {
        parent::enterHash($cursor, $type, $class, false);

        if ($hasChild) {
            if ($cursor->refIndex) {
                $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2;
                $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex;

                $this->line .= sprintf('<samp id=%s-ref%s>', $this->dumpId, $r);
            } else {
                $this->line .= '<samp>';
            }
            $this->dumpLine($cursor->depth);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
    {
        $this->dumpEllipsis($cursor, $hasChild, $cut);
        if ($hasChild) {
            $this->line .= '</samp>';
        }
        parent::leaveHash($cursor, $type, $class, $hasChild, 0);
    }

    /**
     * {@inheritdoc}
     */
    protected function style($style, $value, $attr = array())
    {
        if ('' === $value) {
            return '';
        }

        $v = esc($value);

        if ('ref' === $style) {
            if (empty($attr['count'])) {
                return sprintf('<a class=sf-dump-ref>%s</a>', $v);
            }
            $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1);

            return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v);
        }

        if ('const' === $style && isset($attr['value'])) {
            $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
        } elseif ('public' === $style) {
            $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
        } elseif ('str' === $style && 1 < $attr['length']) {
            $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
        } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
            return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1));
        } elseif ('protected' === $style) {
            $style .= ' title="Protected property"';
        } elseif ('private' === $style) {
            $style .= sprintf(' title="Private property defined in class:&#10;`%s`"', esc($attr['class']));
        }

        $map = static::$controlCharsMap;
        $style = "<span class=sf-dump-{$style}>";
        $v = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $style) {
            $s = '</span>';
            $c = $c[$i = 0];
            do {
                $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i]));
            } while (isset($c[++$i]));

            return $s.$style;
        }, $v, -1, $cchrCount);

        if ($cchrCount && '<' === $v[0]) {
            $v = substr($v, 7);
        } else {
            $v = $style.$v;
        }
        if ($cchrCount && '>' === substr($v, -1)) {
            $v = substr($v, 0, -strlen($style));
        } else {
            $v .= '</span>';
        }

        return $v;
    }

    /**
     * {@inheritdoc}
     */
    protected function dumpLine($depth, $endOfValue = false)
    {
        if (-1 === $this->lastDepth) {
            $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
        }
        if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
            $this->line = $this->getDumpHeader().$this->line;
        }

        if (-1 === $depth) {
            $this->line .= sprintf($this->dumpSuffix, $this->dumpId);
        }
        $this->lastDepth = $depth;

        $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');

        if (-1 === $depth) {
            AbstractDumper::dumpLine(0);
        }
        AbstractDumper::dumpLine($depth);
    }
}

function esc($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
<?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\VarDumper\Exception;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ThrowingCasterException extends \Exception
{
    /**
     * @param \Exception $prev The exception thrown from the caster
     */
    public function __construct($prev, \Exception $e = null)
    {
        if (!$prev instanceof \Exception) {
            @trigger_error('Providing $caster as the first argument of the '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Provide directly the $prev exception instead.', E_USER_DEPRECATED);

            $prev = $e;
        }
        parent::__construct('Unexpected '.get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev);
    }
}
<?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\Component\VarDumper\VarDumper;

if (!function_exists('dump')) {
    /**
     * @author Nicolas Grekas <p@tchwork.com>
     */
    function dump($var)
    {
        foreach (func_get_args() as $var) {
            VarDumper::dump($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\VarDumper;

use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

// Load the global dump() function
require_once __DIR__.'/Resources/functions/dump.php';

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
class VarDumper
{
    private static $handler;

    public static function dump($var)
    {
        if (null === self::$handler) {
            $cloner = new VarCloner();
            $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
            self::$handler = function ($var) use ($cloner, $dumper) {
                $dumper->dump($cloner->cloneVar($var));
            };
        }

        return call_user_func(self::$handler, $var);
    }

    public static function setHandler($callable)
    {
        if (null !== $callable && !is_callable($callable, true)) {
            throw new \InvalidArgumentException('Invalid PHP callback.');
        }

        $prevHandler = self::$handler;
        self::$handler = $callable;

        return $prevHandler;
    }
}
<?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;

/**
 * Dumper dumps PHP variables to YAML strings.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Dumper
{
    /**
     * The amount of spaces to use for indentation of nested nodes.
     *
     * @var int
     */
    protected $indentation = 4;

    /**
     * Sets the indentation.
     *
     * @param int $num The amount of spaces to use for indentation of nested nodes
     */
    public function setIndentation($num)
    {
        if ($num < 1) {
            throw new \InvalidArgumentException('The indentation must be greater than zero.');
        }

        $this->indentation = (int) $num;
    }

    /**
     * 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 bool  $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          true if object support is enabled, false otherwise
     *
     * @return string The YAML representation of the PHP value
     */
    public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        $output = '';
        $prefix = $indent ? str_repeat(' ', $indent) : '';

        if ($inline <= 0 || !is_array($input) || empty($input)) {
            $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport);
        } else {
            $isAHash = Inline::isHash($input);

            foreach ($input as $key => $value) {
                $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value);

                $output .= sprintf('%s%s%s%s',
                    $prefix,
                    $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-',
                    $willBeInlined ? ' ' : "\n",
                    $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport)
                ).($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;

/**
 * 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 = array('\\', '\\\\', '\\"', '"',
                                     "\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 = array('\\\\', '\\"', '\\\\', '\\"',
                                     '\\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($value)
    {
        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($value)
    {
        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($value)
    {
        // Determines if a PHP value is entirely composed of a value that would
        // require single quoting in YAML.
        if (in_array(strtolower($value), array('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($value)
    {
        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\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
{
}
<?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($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $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) {
            if (\PHP_VERSION_ID >= 50400) {
                $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
            } else {
                $jsonOptions = 0;
            }
            $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions));
        }

        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\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;

use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Exception\DumpException;

/**
 * Inline implements a YAML parser/dumper for the YAML inline syntax.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Inline
{
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';

    private static $exceptionOnInvalidType = false;
    private static $objectSupport = false;
    private static $objectForMap = false;

    /**
     * Converts a YAML string to a PHP value.
     *
     * @param string $value                  A YAML string
     * @param bool   $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool   $objectSupport          true if object support is enabled, false otherwise
     * @param bool   $objectForMap           true if maps should return a stdClass instead of array()
     * @param array  $references             Mapping of variable names to values
     *
     * @return mixed A PHP value
     *
     * @throws ParseException
     */
    public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
    {
        self::$exceptionOnInvalidType = $exceptionOnInvalidType;
        self::$objectSupport = $objectSupport;
        self::$objectForMap = $objectForMap;

        $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');
        }

        $i = 0;
        switch ($value[0]) {
            case '[':
                $result = self::parseSequence($value, $i, $references);
                ++$i;
                break;
            case '{':
                $result = self::parseMapping($value, $i, $references);
                ++$i;
                break;
            default:
                $result = self::parseScalar($value, null, array('"', "'"), $i, true, $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)));
        }

        if (isset($mbEncoding)) {
            mb_internal_encoding($mbEncoding);
        }

        return $result;
    }

    /**
     * Dumps a given PHP variable to a YAML string.
     *
     * @param mixed $value                  The PHP variable to convert
     * @param bool  $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          true if object support is enabled, false otherwise
     *
     * @return string The YAML string representing the PHP value
     *
     * @throws DumpException When trying to dump PHP resource
     */
    public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        switch (true) {
            case is_resource($value):
                if ($exceptionOnInvalidType) {
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
                }

                return 'null';
            case is_object($value):
                if ($objectSupport) {
                    return '!php/object:'.serialize($value);
                }

                if ($exceptionOnInvalidType) {
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
                }

                return 'null';
            case is_array($value):
                return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
            case null === $value:
                return 'null';
            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 Escaper::requiresDoubleQuoting($value):
                return Escaper::escapeWithDoubleQuotes($value);
            case Escaper::requiresSingleQuoting($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.
     *
     * @internal
     *
     * @param array $value The PHP array to check
     *
     * @return bool true if value is hash array, false otherwise
     */
    public static function isHash(array $value)
    {
        $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 bool  $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          true if object support is enabled, false otherwise
     *
     * @return string The YAML string representing the PHP array
     */
    private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
    {
        // array
        if ($value && !self::isHash($value)) {
            $output = array();
            foreach ($value as $val) {
                $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
            }

            return sprintf('[%s]', implode(', ', $output));
        }

        // hash
        $output = array();
        foreach ($value as $key => $val) {
            $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
        }

        return sprintf('{ %s }', implode(', ', $output));
    }

    /**
     * Parses a YAML scalar.
     *
     * @param string   $scalar
     * @param string[] $delimiters
     * @param string[] $stringDelimiters
     * @param int      &$i
     * @param bool     $evaluate
     * @param array    $references
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     *
     * @internal
     */
    public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
    {
        if (in_array($scalar[$i], $stringDelimiters)) {
            // quoted scalar
            $output = self::parseQuotedScalar($scalar, $i);

            if (null !== $delimiters) {
                $tmp = ltrim(substr($scalar, $i), ' ');
                if (!in_array($tmp[0], $delimiters)) {
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
                }
            }
        } 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);
            } else {
                throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
            }

            // 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])) {
                @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);

                // to be thrown in 3.0
                // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
            }

            if ($evaluate) {
                $output = self::evaluateScalar($output, $references);
            }
        }

        return $output;
    }

    /**
     * Parses a YAML quoted scalar.
     *
     * @param string $scalar
     * @param int    &$i
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseQuotedScalar($scalar, &$i)
    {
        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)));
        }

        $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.
     *
     * @param string $sequence
     * @param int    &$i
     * @param array  $references
     *
     * @return array
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseSequence($sequence, &$i = 0, $references = array())
    {
        $output = array();
        $len = strlen($sequence);
        ++$i;

        // [foo, bar, ...]
        while ($i < $len) {
            switch ($sequence[$i]) {
                case '[':
                    // nested sequence
                    $output[] = self::parseSequence($sequence, $i, $references);
                    break;
                case '{':
                    // nested mapping
                    $output[] = self::parseMapping($sequence, $i, $references);
                    break;
                case ']':
                    return $output;
                case ',':
                case ' ':
                    break;
                default:
                    $isQuoted = in_array($sequence[$i], array('"', "'"));
                    $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);

                    // the value can be an array if a reference has been resolved to an array var
                    if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
                        // embedded mapping?
                        try {
                            $pos = 0;
                            $value = self::parseMapping('{'.$value.'}', $pos, $references);
                        } catch (\InvalidArgumentException $e) {
                            // no, it's not
                        }
                    }

                    $output[] = $value;

                    --$i;
            }

            ++$i;
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
    }

    /**
     * Parses a YAML mapping.
     *
     * @param string $mapping
     * @param int    &$i
     * @param array  $references
     *
     * @return array|\stdClass
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseMapping($mapping, &$i = 0, $references = array())
    {
        $output = array();
        $len = strlen($mapping);
        ++$i;

        // {foo: bar, bar:foo, ...}
        while ($i < $len) {
            switch ($mapping[$i]) {
                case ' ':
                case ',':
                    ++$i;
                    continue 2;
                case '}':
                    if (self::$objectForMap) {
                        return (object) $output;
                    }

                    return $output;
            }

            // key
            $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);

            // value
            $done = false;

            while ($i < $len) {
                switch ($mapping[$i]) {
                    case '[':
                        // nested sequence
                        $value = self::parseSequence($mapping, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        if (!isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        break;
                    case '{':
                        // nested mapping
                        $value = self::parseMapping($mapping, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        if (!isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        break;
                    case ':':
                    case ' ':
                        break;
                    default:
                        $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        if (!isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        --$i;
                }

                ++$i;

                if ($done) {
                    continue 2;
                }
            }
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
    }

    /**
     * Evaluates scalars and replaces magic values.
     *
     * @param string $scalar
     * @param array  $references
     *
     * @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($scalar, $references = array())
    {
        $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.');
            }

            if (!array_key_exists($value, $references)) {
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
            }

            return $references[$value];
        }

        switch (true) {
            case 'null' === $scalarLower:
            case '' === $scalar:
            case '~' === $scalar:
                return;
            case 'true' === $scalarLower:
                return true;
            case 'false' === $scalarLower:
                return false;
            // Optimise for returning strings.
            case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
                switch (true) {
                    case 0 === strpos($scalar, '!str'):
                        return (string) substr($scalar, 5);
                    case 0 === strpos($scalar, '! '):
                        return (int) self::parseScalar(substr($scalar, 2));
                    case 0 === strpos($scalar, '!php/object:'):
                        if (self::$objectSupport) {
                            return unserialize(substr($scalar, 12));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
                        }

                        return;
                    case 0 === strpos($scalar, '!!php/object:'):
                        if (self::$objectSupport) {
                            return unserialize(substr($scalar, 13));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
                        }

                        return;
                    case 0 === strpos($scalar, '!!float '):
                        return (float) substr($scalar, 8);
                    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($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
                    case is_numeric($scalar):
                    case Parser::preg_match(self::getHexRegex(), $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]+)?$/', $scalar):
                        return (float) str_replace(',', '', $scalar);
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
                        $timeZone = date_default_timezone_get();
                        date_default_timezone_set('UTC');
                        $time = strtotime($scalar);
                        date_default_timezone_set($timeZone);

                        return $time;
                }
                // no break
            default:
                return (string) $scalar;
        }
    }

    /**
     * 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()
    {
        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.
     *
     * @return string
     */
    private static function getHexRegex()
    {
        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;

/**
 * Parser parses YAML strings to convert them to PHP arrays.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Parser
{
    const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
    // BC - wrongly named
    const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;

    private $offset = 0;
    private $totalNumberOfLines;
    private $lines = array();
    private $currentLineNb = -1;
    private $currentLine = '';
    private $refs = array();
    private $skippedLineNumbers = array();
    private $locallySkippedLineNumbers = array();

    /**
     * @param int      $offset             The offset of YAML document (used for line numbers in error messages)
     * @param int|null $totalNumberOfLines The overall number of lines being parsed
     * @param int[]    $skippedLineNumbers Number of comment lines that have been skipped by the parser
     */
    public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
    {
        $this->offset = $offset;
        $this->totalNumberOfLines = $totalNumberOfLines;
        $this->skippedLineNumbers = $skippedLineNumbers;
    }

    /**
     * Parses a YAML string to a PHP value.
     *
     * @param string $value                  A YAML string
     * @param bool   $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool   $objectSupport          true if object support is enabled, false otherwise
     * @param bool   $objectForMap           true if maps should return a stdClass instead of array()
     *
     * @return mixed A PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        if (false === preg_match('//u', $value)) {
            throw new ParseException('The YAML value does not appear to be valid UTF-8.');
        }

        $this->refs = array();

        $mbEncoding = null;
        $e = null;
        $data = 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, $exceptionOnInvalidType, $objectSupport, $objectForMap);
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }

        if (null !== $mbEncoding) {
            mb_internal_encoding($mbEncoding);
        }

        $this->lines = array();
        $this->currentLine = '';
        $this->refs = array();
        $this->skippedLineNumbers = array();
        $this->locallySkippedLineNumbers = array();

        if (null !== $e) {
            throw $e;
        }

        return $data;
    }

    private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        $this->currentLineNb = -1;
        $this->currentLine = '';
        $value = $this->cleanup($value);
        $this->lines = explode("\n", $value);
        $this->locallySkippedLineNumbers = array();

        if (null === $this->totalNumberOfLines) {
            $this->totalNumberOfLines = count($this->lines);
        }

        $data = array();
        $context = null;
        $allowOverwrite = false;

        while ($this->moveToNextLine()) {
            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);
            }

            $isRef = $mergeNode = false;
            if (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);
                }
                $context = 'sequence';

                if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $values['value'] = $matches['value'];
                }

                // array
                if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
                    $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
                } else {
                    if (isset($values['leadspaces'])
                        && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($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, $exceptionOnInvalidType, $objectSupport, $objectForMap);
                    } else {
                        $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = end($data);
                }
            } elseif (
                self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
                && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
            ) {
                if ($context && 'sequence' == $context) {
                    throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
                }
                $context = 'mapping';

                // force correct settings
                Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
                try {
                    $key = Inline::parseScalar($values['key']);
                } catch (ParseException $e) {
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                    $e->setSnippet($this->currentLine);

                    throw $e;
                }

                // 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']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
                    $mergeNode = true;
                    $allowOverwrite = true;
                    if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
                        $refName = substr($values['value'], 1);
                        if (!array_key_exists($refName, $this->refs)) {
                            throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        $refValue = $this->refs[$refName];

                        if (!is_array($refValue)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        $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, $exceptionOnInvalidType, $objectSupport, $objectForMap);

                        if (!is_array($parsed)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        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 (!is_array($parsedItem)) {
                                    throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
                                }

                                $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']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $values['value'] = $matches['value'];
                }

                if ($mergeNode) {
                    // Merge keys
                } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $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])) {
                            $data[$key] = null;
                        }
                    } else {
                        $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);

                        if ('<<' === $key) {
                            $this->refs[$refMatches['ref']] = $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.
                            $data[$key] = $value;
                        }
                    }
                } else {
                    $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $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;
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = $data[$key];
                }
            } else {
                // multiple documents are not supported
                if ('---' === $this->currentLine) {
                    throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 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], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
                    } catch (ParseException $e) {
                        $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                        $e->setSnippet($this->currentLine);

                        throw $e;
                    }

                    return $value;
                }

                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        }

        if ($objectForMap && !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($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
    {
        $skippedLineNumbers = $this->skippedLineNumbers;

        foreach ($this->locallySkippedLineNumbers as $lineNumber) {
            if ($lineNumber < $offset) {
                continue;
            }

            $skippedLineNumbers[] = $lineNumber;
        }

        $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
        $parser->refs = &$this->refs;

        return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
    }

    /**
     * Returns the current line number (takes the offset into account).
     *
     * @return int The current line number
     */
    private function getRealCurrentLineNb()
    {
        $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()
    {
        return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
    }

    /**
     * Returns the next embed block of YAML.
     *
     * @param int  $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($indentation = null, $inSequence = false)
    {
        $oldLineIndentation = $this->getCurrentLineIndentation();
        $blockScalarIndentations = array();

        if ($this->isBlockScalarHeader()) {
            $blockScalarIndentations[] = $this->getCurrentLineIndentation();
        }

        if (!$this->moveToNextLine()) {
            return;
        }

        if (null === $indentation) {
            $newIndent = $this->getCurrentLineIndentation();

            $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();

            if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        } else {
            $newIndent = $indentation;
        }

        $data = array();
        if ($this->getCurrentLineIndentation() >= $newIndent) {
            $data[] = substr($this->currentLine, $newIndent);
        } 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();

        if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
            $blockScalarIndentations[] = $this->getCurrentLineIndentation();
        }

        $previousLineIndentation = $this->getCurrentLineIndentation();

        while ($this->moveToNextLine()) {
            $indent = $this->getCurrentLineIndentation();

            // terminate all block scalars that are more indented than the current line
            if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
                foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
                    if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
                        unset($blockScalarIndentations[$key]);
                    }
                }
            }

            if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
                $blockScalarIndentations[] = $this->getCurrentLineIndentation();
            }

            $previousLineIndentation = $indent;

            if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
                $this->moveToPreviousLine();
                break;
            }

            if ($this->isCurrentLineBlank()) {
                $data[] = substr($this->currentLine, $newIndent);
                continue;
            }

            // we ignore "comment" lines only when we are not inside a scalar block
            if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
                // remember ignored comment lines (they are used later in nested
                // parser calls to determine real line numbers)
                //
                // CAUTION: beware to not populate the global property here as it
                // will otherwise influence the getRealCurrentLineNb() call here
                // for consecutive comment lines and subsequent embedded blocks
                $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();

                continue;
            }

            if ($indent >= $newIndent) {
                $data[] = substr($this->currentLine, $newIndent);
            } elseif (0 == $indent) {
                $this->moveToPreviousLine();

                break;
            } else {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        }

        return implode("\n", $data);
    }

    /**
     * Moves the parser to the next line.
     *
     * @return bool
     */
    private function moveToNextLine()
    {
        if ($this->currentLineNb >= count($this->lines) - 1) {
            return false;
        }

        $this->currentLine = $this->lines[++$this->currentLineNb];

        return true;
    }

    /**
     * Moves the parser to the previous line.
     *
     * @return bool
     */
    private function moveToPreviousLine()
    {
        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 bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           true if maps should return a stdClass instead of array()
     * @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($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $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)) {
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
            }

            return $this->refs[$value];
        }

        if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
            $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';

            return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
        }

        try {
            $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);

            if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
                @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);

                // to be thrown in 3.0
                // throw new ParseException('A colon cannot be used in an unquoted mapping value.');
            }

            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
     *
     * @return string The text value
     */
    private function parseBlockScalar($style, $chomping = '', $indentation = 0)
    {
        $notEOF = $this->moveToNextLine();
        if (!$notEOF) {
            return '';
        }

        $isCurrentLineBlank = $this->isCurrentLineBlank();
        $blockLines = array();

        // 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) {
            if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
                $indentation = strlen($matches[0]);
            }
        }

        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()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $EOF = !$this->moveToNextLine();

        while (!$EOF && $this->isCurrentLineEmpty()) {
            $EOF = !$this->moveToNextLine();
        }

        if ($EOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() > $currentIndentation;

        $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()
    {
        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()
    {
        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()
    {
        //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()
    {
        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($value)
    {
        $value = str_replace(array("\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()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $notEOF = $this->moveToNextLine();

        while ($notEOF && $this->isCurrentLineEmpty()) {
            $notEOF = $this->moveToNextLine();
        }

        if (false === $notEOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();

        $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()
    {
        return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
    }

    /**
     * Tests whether or not the current line is the header of a block scalar.
     *
     * @return bool
     */
    private function isBlockScalarHeader()
    {
        return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $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($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
    {
        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;
    }
}
<?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;

/**
 * Unescaper encapsulates unescaping rules for single and double-quoted
 * YAML strings.
 *
 * @author Matthew Lewinski <matthew@lewinski.org>
 *
 * @internal
 */
class Unescaper
{
    /**
     * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters
     * must be converted to that encoding.
     *
     * @deprecated since version 2.5, to be removed in 3.0
     *
     * @internal
     */
    const ENCODING = 'UTF-8';

    /**
     * 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($value)
    {
        return str_replace('\'\'', '\'', $value);
    }

    /**
     * Unescapes a double quoted string.
     *
     * @param string $value A double quoted string
     *
     * @return string The unescaped string
     */
    public function unescapeDoubleQuotedString($value)
    {
        $self = $this;
        $callback = function ($match) use ($self) {
            return $self->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
     *
     * @internal This method is public to be usable as callback. It should not
     *           be used in user code. Should be changed in 3.0.
     */
    public function unescapeCharacter($value)
    {
        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:
                @trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED);

                return $value;
        }
    }

    /**
     * Get the UTF-8 character for the given code point.
     *
     * @param int $c The unicode code point
     *
     * @return string The corresponding UTF-8 character
     */
    private static function utf8chr($c)
    {
        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\Exception\ParseException;

/**
 * Yaml offers convenience methods to load and dump YAML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Yaml
{
    /**
     * Parses YAML into a PHP value.
     *
     *  Usage:
     *  <code>
     *   $array = Yaml::parse(file_get_contents('config.yml'));
     *   print_r($array);
     *  </code>
     *
     * As this method accepts both plain strings and file names as an input,
     * you must validate the input before calling this method. Passing a file
     * as an input is a deprecated feature and will be removed in 3.0.
     *
     * Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.
     *
     * @param string $input                  Path to a YAML file or a string containing YAML
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
     *
     * @return mixed The YAML converted to a PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        // if input is a file, process it
        $file = '';
        if (false === strpos($input, "\n") && is_file($input)) {
            @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED);

            if (false === is_readable($input)) {
                throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input));
            }

            $file = $input;
            $input = file_get_contents($file);
        }

        $yaml = new Parser();

        try {
            return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap);
        } catch (ParseException $e) {
            if ($file) {
                $e->setParsedFile($file);
            }

            throw $e;
        }
    }

    /**
     * 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 bool  $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          true if object support is enabled, false otherwise
     *
     * @return string A YAML string representing the original PHP value
     */
    public static function dump($input, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        if ($indent < 1) {
            throw new \InvalidArgumentException('The indentation must be greater than zero.');
        }

        $yaml = new Dumper();
        $yaml->setIndentation($indent);

        return $yaml->dump($input, $inline, 0, $exceptionOnInvalidType, $objectSupport);
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'JakubOnderka\\PhpConsoleHighlighter' => array($vendorDir . '/jakub-onderka/php-console-highlighter/src'),
    'JakubOnderka\\PhpConsoleColor' => array($vendorDir . '/jakub-onderka/php-console-color/src'),
    'Drush' => array($baseDir . '/lib'),
    'Consolidation' => array($baseDir . '/lib'),
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),
    'Webmozart\\PathUtil\\' => array($vendorDir . '/webmozart/path-util/src'),
    'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
    'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
    'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Psy\\' => array($vendorDir . '/psy/psysh/src/Psy'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
    'Consolidation\\OutputFormatters\\' => array($vendorDir . '/consolidation/output-formatters/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(
    'Console_Table' => $vendorDir . '/pear/console_table/Table.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\\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\\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\\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\\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\\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\\FormatterInterface' => $vendorDir . '/consolidation/output-formatters/src/Formatters/FormatterInterface.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\\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\\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\\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\\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\\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\\TableDataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/TableDataInterface.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\\TableTransformation' => $vendorDir . '/consolidation/output-formatters/src/Transformations/TableTransformation.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',
    'Drush\\Boot\\BaseBoot' => $baseDir . '/lib/Drush/Boot/BaseBoot.php',
    'Drush\\Boot\\Boot' => $baseDir . '/lib/Drush/Boot/Boot.php',
    'Drush\\Boot\\DrupalBoot' => $baseDir . '/lib/Drush/Boot/DrupalBoot.php',
    'Drush\\Boot\\DrupalBoot6' => $baseDir . '/lib/Drush/Boot/DrupalBoot6.php',
    'Drush\\Boot\\DrupalBoot7' => $baseDir . '/lib/Drush/Boot/DrupalBoot7.php',
    'Drush\\Boot\\DrupalBoot8' => $baseDir . '/lib/Drush/Boot/DrupalBoot8.php',
    'Drush\\Boot\\EmptyBoot' => $baseDir . '/lib/Drush/Boot/EmptyBoot.php',
    'Drush\\Cache\\CacheInterface' => $baseDir . '/lib/Drush/Cache/CacheInterface.php',
    'Drush\\Cache\\FileCache' => $baseDir . '/lib/Drush/Cache/FileCache.php',
    'Drush\\Cache\\JSONCache' => $baseDir . '/lib/Drush/Cache/JSONCache.php',
    'Drush\\CommandFiles\\Core\\BrowseCommands' => $baseDir . '/lib/Drush/CommandFiles/core/browseCommands.php',
    'Drush\\CommandFiles\\ExampleCommandFile' => $baseDir . '/lib/Drush/CommandFiles/ExampleCommandFile.php',
    'Drush\\CommandFiles\\core\\DrupliconCommands' => $baseDir . '/lib/Drush/CommandFiles/core/DrupliconCommands.php',
    'Drush\\Command\\Commandfiles' => $baseDir . '/lib/Drush/Command/Commandfiles.php',
    'Drush\\Command\\CommandfilesInterface' => $baseDir . '/lib/Drush/Command/CommandfilesInterface.php',
    'Drush\\Command\\DrushInputAdapter' => $baseDir . '/lib/Drush/Command/DrushInputAdapter.php',
    'Drush\\Command\\DrushOutputAdapter' => $baseDir . '/lib/Drush/Command/DrushOutputAdapter.php',
    'Drush\\Command\\ServiceCommandlist' => $baseDir . '/lib/Drush/Command/ServiceCommandlist.php',
    'Drush\\Commands\\core\\SanitizeCommands' => $baseDir . '/lib/Drush/Commands/core/SanitizeCommands.php',
    'Drush\\Commands\\core\\StatusCommands' => $baseDir . '/lib/Drush/Commands/core/StatusCommands.php',
    'Drush\\Drupal\\DrupalKernel' => $baseDir . '/lib/Drush/Drupal/DrupalKernel.php',
    'Drush\\Drupal\\DrushServiceModifier' => $baseDir . '/lib/Drush/Drupal/DrushServiceModifier.php',
    'Drush\\Drupal\\ExtensionDiscovery' => $baseDir . '/lib/Drush/Drupal/ExtensionDiscovery.php',
    'Drush\\Drupal\\FindCommandsCompilerPass' => $baseDir . '/lib/Drush/Drupal/FindCommandsCompilerPass.php',
    'Drush\\Log\\DrushLog' => $baseDir . '/lib/Drush/Log/DrushLog.php',
    'Drush\\Log\\LogLevel' => $baseDir . '/lib/Drush/Log/LogLevel.php',
    'Drush\\Log\\Logger' => $baseDir . '/lib/Drush/Log/Logger.php',
    'Drush\\Make\\Parser\\ParserIni' => $baseDir . '/lib/Drush/Make/Parser/ParserIni.php',
    'Drush\\Make\\Parser\\ParserInterface' => $baseDir . '/lib/Drush/Make/Parser/ParserInterface.php',
    'Drush\\Make\\Parser\\ParserYaml' => $baseDir . '/lib/Drush/Make/Parser/ParserYaml.php',
    'Drush\\Psysh\\Caster' => $baseDir . '/lib/Drush/Psysh/Caster.php',
    'Drush\\Psysh\\DrushCommand' => $baseDir . '/lib/Drush/Psysh/DrushCommand.php',
    'Drush\\Psysh\\DrushHelpCommand' => $baseDir . '/lib/Drush/Psysh/DrushHelpCommand.php',
    'Drush\\Psysh\\Shell' => $baseDir . '/lib/Drush/Psysh/Shell.php',
    'Drush\\Queue\\Queue6' => $baseDir . '/lib/Drush/Queue/Queue6.php',
    'Drush\\Queue\\Queue7' => $baseDir . '/lib/Drush/Queue/Queue7.php',
    'Drush\\Queue\\Queue8' => $baseDir . '/lib/Drush/Queue/Queue8.php',
    'Drush\\Queue\\QueueBase' => $baseDir . '/lib/Drush/Queue/QueueBase.php',
    'Drush\\Queue\\QueueException' => $baseDir . '/lib/Drush/Queue/QueueException.php',
    'Drush\\Queue\\QueueInterface' => $baseDir . '/lib/Drush/Queue/QueueInterface.php',
    'Drush\\Role\\Role6' => $baseDir . '/lib/Drush/Role/Role6.php',
    'Drush\\Role\\Role7' => $baseDir . '/lib/Drush/Role/Role7.php',
    'Drush\\Role\\Role8' => $baseDir . '/lib/Drush/Role/Role8.php',
    'Drush\\Role\\RoleBase' => $baseDir . '/lib/Drush/Role/RoleBase.php',
    'Drush\\Role\\RoleException' => $baseDir . '/lib/Drush/Role/RoleException.php',
    'Drush\\Sql\\Sql6' => $baseDir . '/lib/Drush/Sql/Sql6.php',
    'Drush\\Sql\\Sql7' => $baseDir . '/lib/Drush/Sql/Sql7.php',
    'Drush\\Sql\\Sql8' => $baseDir . '/lib/Drush/Sql/Sql8.php',
    'Drush\\Sql\\SqlBase' => $baseDir . '/lib/Drush/Sql/SqlBase.php',
    'Drush\\Sql\\SqlException' => $baseDir . '/lib/Drush/Sql/SqlException.php',
    'Drush\\Sql\\SqlVersion' => $baseDir . '/lib/Drush/Sql/SqlVersion.php',
    'Drush\\Sql\\Sqlmysql' => $baseDir . '/lib/Drush/Sql/Sqlmysql.php',
    'Drush\\Sql\\Sqloracle' => $baseDir . '/lib/Drush/Sql/Sqloracle.php',
    'Drush\\Sql\\Sqlpgsql' => $baseDir . '/lib/Drush/Sql/Sqlpgsql.php',
    'Drush\\Sql\\Sqlsqlite' => $baseDir . '/lib/Drush/Sql/Sqlsqlite.php',
    'Drush\\Sql\\Sqlsqlsrv' => $baseDir . '/lib/Drush/Sql/Sqlsqlsrv.php',
    'Drush\\UpdateService\\Project' => $baseDir . '/lib/Drush/UpdateService/Project.php',
    'Drush\\UpdateService\\ReleaseInfo' => $baseDir . '/lib/Drush/UpdateService/ReleaseInfo.php',
    'Drush\\UpdateService\\StatusInfoDrupal6' => $baseDir . '/lib/Drush/UpdateService/StatusInfoDrupal6.php',
    'Drush\\UpdateService\\StatusInfoDrupal7' => $baseDir . '/lib/Drush/UpdateService/StatusInfoDrupal7.php',
    'Drush\\UpdateService\\StatusInfoDrupal8' => $baseDir . '/lib/Drush/UpdateService/StatusInfoDrupal8.php',
    'Drush\\UpdateService\\StatusInfoDrush' => $baseDir . '/lib/Drush/UpdateService/StatusInfoDrush.php',
    'Drush\\UpdateService\\StatusInfoInterface' => $baseDir . '/lib/Drush/UpdateService/StatusInfoInterface.php',
    'Drush\\User\\User6' => $baseDir . '/lib/Drush/User/User6.php',
    'Drush\\User\\User7' => $baseDir . '/lib/Drush/User/User7.php',
    'Drush\\User\\User8' => $baseDir . '/lib/Drush/User/User8.php',
    'Drush\\User\\UserList' => $baseDir . '/lib/Drush/User/UserList.php',
    'Drush\\User\\UserListException' => $baseDir . '/lib/Drush/User/UserListException.php',
    'Drush\\User\\UserSingle6' => $baseDir . '/lib/Drush/User/UserSingle6.php',
    'Drush\\User\\UserSingle7' => $baseDir . '/lib/Drush/User/UserSingle7.php',
    'Drush\\User\\UserSingle8' => $baseDir . '/lib/Drush/User/UserSingle8.php',
    'Drush\\User\\UserSingleBase' => $baseDir . '/lib/Drush/User/UserSingleBase.php',
    'Drush\\User\\UserVersion' => $baseDir . '/lib/Drush/User/UserVersion.php',
    'JakubOnderka\\PhpConsoleColor\\ConsoleColor' => $vendorDir . '/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/ConsoleColor.php',
    'JakubOnderka\\PhpConsoleColor\\InvalidStyleException' => $vendorDir . '/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/InvalidStyleException.php',
    'JakubOnderka\\PhpConsoleHighlighter\\Highlighter' => $vendorDir . '/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php',
    'PhpParser\\Autoloader' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Autoloader.php',
    'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php',
    'PhpParser\\BuilderAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderAbstract.php',
    'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
    'PhpParser\\Builder\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php',
    'PhpParser\\Builder\\Declaration' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php',
    'PhpParser\\Builder\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php',
    'PhpParser\\Builder\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php',
    'PhpParser\\Builder\\Interface_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php',
    'PhpParser\\Builder\\Method' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Method.php',
    'PhpParser\\Builder\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php',
    'PhpParser\\Builder\\Param' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Param.php',
    'PhpParser\\Builder\\Property' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Property.php',
    'PhpParser\\Builder\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Trait_.php',
    'PhpParser\\Builder\\Use_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Use_.php',
    'PhpParser\\Comment' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Comment.php',
    'PhpParser\\Comment\\Doc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Comment/Doc.php',
    'PhpParser\\Error' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Error.php',
    'PhpParser\\Lexer' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer.php',
    'PhpParser\\Lexer\\Emulative' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php',
    'PhpParser\\Node' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node.php',
    'PhpParser\\NodeAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeAbstract.php',
    'PhpParser\\NodeDumper' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeDumper.php',
    'PhpParser\\NodeTraverser' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeTraverser.php',
    'PhpParser\\NodeTraverserInterface' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php',
    'PhpParser\\NodeVisitor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor.php',
    'PhpParser\\NodeVisitorAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php',
    'PhpParser\\NodeVisitor\\NameResolver' => $vendorDir . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php',
    'PhpParser\\Node\\Arg' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Arg.php',
    'PhpParser\\Node\\Const_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Const_.php',
    'PhpParser\\Node\\Expr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr.php',
    'PhpParser\\Node\\Expr\\ArrayDimFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php',
    'PhpParser\\Node\\Expr\\ArrayItem' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php',
    'PhpParser\\Node\\Expr\\Array_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php',
    'PhpParser\\Node\\Expr\\Assign' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php',
    'PhpParser\\Node\\Expr\\AssignOp' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php',
    'PhpParser\\Node\\Expr\\AssignOp\\BitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php',
    'PhpParser\\Node\\Expr\\AssignOp\\BitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php',
    'PhpParser\\Node\\Expr\\AssignOp\\BitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Concat' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Div' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Minus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Mod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Mul' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Plus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php',
    'PhpParser\\Node\\Expr\\AssignOp\\Pow' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php',
    'PhpParser\\Node\\Expr\\AssignOp\\ShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php',
    'PhpParser\\Node\\Expr\\AssignOp\\ShiftRight' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php',
    'PhpParser\\Node\\Expr\\AssignRef' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php',
    'PhpParser\\Node\\Expr\\BinaryOp' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\BooleanAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Coalesce' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Concat' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Div' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Equal' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Greater' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\GreaterOrEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Identical' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Minus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Mod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Mul' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\NotEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\NotIdentical' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Plus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Pow' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\ShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\ShiftRight' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Smaller' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\SmallerOrEqual' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php',
    'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php',
    'PhpParser\\Node\\Expr\\BitwiseNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php',
    'PhpParser\\Node\\Expr\\BooleanNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php',
    'PhpParser\\Node\\Expr\\Cast' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php',
    'PhpParser\\Node\\Expr\\Cast\\Array_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php',
    'PhpParser\\Node\\Expr\\Cast\\Bool_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php',
    'PhpParser\\Node\\Expr\\Cast\\Double' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php',
    'PhpParser\\Node\\Expr\\Cast\\Int_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php',
    'PhpParser\\Node\\Expr\\Cast\\Object_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php',
    'PhpParser\\Node\\Expr\\Cast\\String_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php',
    'PhpParser\\Node\\Expr\\Cast\\Unset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php',
    'PhpParser\\Node\\Expr\\ClassConstFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php',
    'PhpParser\\Node\\Expr\\Clone_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php',
    'PhpParser\\Node\\Expr\\Closure' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php',
    'PhpParser\\Node\\Expr\\ClosureUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php',
    'PhpParser\\Node\\Expr\\ConstFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php',
    'PhpParser\\Node\\Expr\\Empty_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php',
    'PhpParser\\Node\\Expr\\ErrorSuppress' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php',
    'PhpParser\\Node\\Expr\\Eval_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php',
    'PhpParser\\Node\\Expr\\Exit_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php',
    'PhpParser\\Node\\Expr\\FuncCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php',
    'PhpParser\\Node\\Expr\\Include_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php',
    'PhpParser\\Node\\Expr\\Instanceof_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php',
    'PhpParser\\Node\\Expr\\Isset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php',
    'PhpParser\\Node\\Expr\\List_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php',
    'PhpParser\\Node\\Expr\\MethodCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php',
    'PhpParser\\Node\\Expr\\New_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php',
    'PhpParser\\Node\\Expr\\PostDec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php',
    'PhpParser\\Node\\Expr\\PostInc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php',
    'PhpParser\\Node\\Expr\\PreDec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php',
    'PhpParser\\Node\\Expr\\PreInc' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php',
    'PhpParser\\Node\\Expr\\Print_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php',
    'PhpParser\\Node\\Expr\\PropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php',
    'PhpParser\\Node\\Expr\\ShellExec' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php',
    'PhpParser\\Node\\Expr\\StaticCall' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php',
    'PhpParser\\Node\\Expr\\StaticPropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php',
    'PhpParser\\Node\\Expr\\Ternary' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php',
    'PhpParser\\Node\\Expr\\UnaryMinus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php',
    'PhpParser\\Node\\Expr\\UnaryPlus' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php',
    'PhpParser\\Node\\Expr\\Variable' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php',
    'PhpParser\\Node\\Expr\\YieldFrom' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php',
    'PhpParser\\Node\\Expr\\Yield_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php',
    'PhpParser\\Node\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php',
    'PhpParser\\Node\\Name' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name.php',
    'PhpParser\\Node\\Name\\FullyQualified' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php',
    'PhpParser\\Node\\Name\\Relative' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php',
    'PhpParser\\Node\\Param' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Param.php',
    'PhpParser\\Node\\Scalar' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar.php',
    'PhpParser\\Node\\Scalar\\DNumber' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php',
    'PhpParser\\Node\\Scalar\\Encapsed' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php',
    'PhpParser\\Node\\Scalar\\EncapsedStringPart' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php',
    'PhpParser\\Node\\Scalar\\LNumber' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php',
    'PhpParser\\Node\\Scalar\\MagicConst' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Dir' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\File' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Line' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Method' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php',
    'PhpParser\\Node\\Scalar\\MagicConst\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php',
    'PhpParser\\Node\\Scalar\\String_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php',
    'PhpParser\\Node\\Stmt' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt.php',
    'PhpParser\\Node\\Stmt\\Break_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php',
    'PhpParser\\Node\\Stmt\\Case_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php',
    'PhpParser\\Node\\Stmt\\Catch_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php',
    'PhpParser\\Node\\Stmt\\ClassConst' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php',
    'PhpParser\\Node\\Stmt\\ClassLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php',
    'PhpParser\\Node\\Stmt\\ClassMethod' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php',
    'PhpParser\\Node\\Stmt\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php',
    'PhpParser\\Node\\Stmt\\Const_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php',
    'PhpParser\\Node\\Stmt\\Continue_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php',
    'PhpParser\\Node\\Stmt\\DeclareDeclare' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php',
    'PhpParser\\Node\\Stmt\\Declare_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php',
    'PhpParser\\Node\\Stmt\\Do_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php',
    'PhpParser\\Node\\Stmt\\Echo_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php',
    'PhpParser\\Node\\Stmt\\ElseIf_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php',
    'PhpParser\\Node\\Stmt\\Else_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php',
    'PhpParser\\Node\\Stmt\\For_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php',
    'PhpParser\\Node\\Stmt\\Foreach_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php',
    'PhpParser\\Node\\Stmt\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php',
    'PhpParser\\Node\\Stmt\\Global_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php',
    'PhpParser\\Node\\Stmt\\Goto_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php',
    'PhpParser\\Node\\Stmt\\GroupUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php',
    'PhpParser\\Node\\Stmt\\HaltCompiler' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php',
    'PhpParser\\Node\\Stmt\\If_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php',
    'PhpParser\\Node\\Stmt\\InlineHTML' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php',
    'PhpParser\\Node\\Stmt\\Interface_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php',
    'PhpParser\\Node\\Stmt\\Label' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php',
    'PhpParser\\Node\\Stmt\\Namespace_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php',
    'PhpParser\\Node\\Stmt\\Nop' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php',
    'PhpParser\\Node\\Stmt\\Property' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php',
    'PhpParser\\Node\\Stmt\\PropertyProperty' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php',
    'PhpParser\\Node\\Stmt\\Return_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php',
    'PhpParser\\Node\\Stmt\\StaticVar' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php',
    'PhpParser\\Node\\Stmt\\Static_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php',
    'PhpParser\\Node\\Stmt\\Switch_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php',
    'PhpParser\\Node\\Stmt\\Throw_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php',
    'PhpParser\\Node\\Stmt\\TraitUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php',
    'PhpParser\\Node\\Stmt\\TraitUseAdaptation' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php',
    'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Alias' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php',
    'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Precedence' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php',
    'PhpParser\\Node\\Stmt\\Trait_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php',
    'PhpParser\\Node\\Stmt\\TryCatch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php',
    'PhpParser\\Node\\Stmt\\Unset_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php',
    'PhpParser\\Node\\Stmt\\UseUse' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php',
    'PhpParser\\Node\\Stmt\\Use_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php',
    'PhpParser\\Node\\Stmt\\While_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php',
    'PhpParser\\Parser' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser.php',
    'PhpParser\\ParserAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php',
    'PhpParser\\ParserFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserFactory.php',
    'PhpParser\\Parser\\Multiple' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Multiple.php',
    'PhpParser\\Parser\\Php5' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Php5.php',
    'PhpParser\\Parser\\Php7' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Php7.php',
    'PhpParser\\Parser\\Tokens' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php',
    'PhpParser\\PrettyPrinterAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php',
    'PhpParser\\PrettyPrinter\\Standard' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
    'PhpParser\\Serializer' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Serializer.php',
    'PhpParser\\Serializer\\XML' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Serializer/XML.php',
    'PhpParser\\Unserializer' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Unserializer.php',
    'PhpParser\\Unserializer\\XML' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Unserializer/XML.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',
    'Psy\\Autoloader' => $vendorDir . '/psy/psysh/src/Psy/Autoloader.php',
    'Psy\\CodeCleaner' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner.php',
    'Psy\\CodeCleaner\\AbstractClassPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php',
    'Psy\\CodeCleaner\\AssignThisVariablePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.php',
    'Psy\\CodeCleaner\\CallTimePassByReferencePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php',
    'Psy\\CodeCleaner\\CalledClassPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.php',
    'Psy\\CodeCleaner\\CodeCleanerPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php',
    'Psy\\CodeCleaner\\ExitPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/ExitPass.php',
    'Psy\\CodeCleaner\\FinalClassPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/FinalClassPass.php',
    'Psy\\CodeCleaner\\FunctionContextPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/FunctionContextPass.php',
    'Psy\\CodeCleaner\\FunctionReturnInWriteContextPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/FunctionReturnInWriteContextPass.php',
    'Psy\\CodeCleaner\\ImplicitReturnPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php',
    'Psy\\CodeCleaner\\InstanceOfPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php',
    'Psy\\CodeCleaner\\LeavePsyshAlonePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php',
    'Psy\\CodeCleaner\\LegacyEmptyPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php',
    'Psy\\CodeCleaner\\LoopContextPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/LoopContextPass.php',
    'Psy\\CodeCleaner\\MagicConstantsPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php',
    'Psy\\CodeCleaner\\NamespaceAwarePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.php',
    'Psy\\CodeCleaner\\NamespacePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php',
    'Psy\\CodeCleaner\\NoReturnValue' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/NoReturnValue.php',
    'Psy\\CodeCleaner\\PassableByReferencePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/PassableByReferencePass.php',
    'Psy\\CodeCleaner\\RequirePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/RequirePass.php',
    'Psy\\CodeCleaner\\StaticConstructorPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php',
    'Psy\\CodeCleaner\\StrictTypesPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.php',
    'Psy\\CodeCleaner\\UseStatementPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php',
    'Psy\\CodeCleaner\\ValidClassNamePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php',
    'Psy\\CodeCleaner\\ValidConstantPass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php',
    'Psy\\CodeCleaner\\ValidFunctionNamePass' => $vendorDir . '/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php',
    'Psy\\Command\\BufferCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/BufferCommand.php',
    'Psy\\Command\\ClearCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ClearCommand.php',
    'Psy\\Command\\Command' => $vendorDir . '/psy/psysh/src/Psy/Command/Command.php',
    'Psy\\Command\\DocCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/DocCommand.php',
    'Psy\\Command\\DumpCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/DumpCommand.php',
    'Psy\\Command\\EditCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/EditCommand.php',
    'Psy\\Command\\ExitCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ExitCommand.php',
    'Psy\\Command\\HelpCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/HelpCommand.php',
    'Psy\\Command\\HistoryCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/HistoryCommand.php',
    'Psy\\Command\\ListCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand.php',
    'Psy\\Command\\ListCommand\\ClassConstantEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.php',
    'Psy\\Command\\ListCommand\\ClassEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php',
    'Psy\\Command\\ListCommand\\ConstantEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php',
    'Psy\\Command\\ListCommand\\Enumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php',
    'Psy\\Command\\ListCommand\\FunctionEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php',
    'Psy\\Command\\ListCommand\\GlobalVariableEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php',
    'Psy\\Command\\ListCommand\\InterfaceEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.php',
    'Psy\\Command\\ListCommand\\MethodEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.php',
    'Psy\\Command\\ListCommand\\PropertyEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.php',
    'Psy\\Command\\ListCommand\\TraitEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php',
    'Psy\\Command\\ListCommand\\VariableEnumerator' => $vendorDir . '/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php',
    'Psy\\Command\\ParseCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ParseCommand.php',
    'Psy\\Command\\PsyVersionCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/PsyVersionCommand.php',
    'Psy\\Command\\ReflectingCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ReflectingCommand.php',
    'Psy\\Command\\ShowCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ShowCommand.php',
    'Psy\\Command\\SudoCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/SudoCommand.php',
    'Psy\\Command\\ThrowUpCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/ThrowUpCommand.php',
    'Psy\\Command\\TraceCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/TraceCommand.php',
    'Psy\\Command\\WhereamiCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/WhereamiCommand.php',
    'Psy\\Command\\WtfCommand' => $vendorDir . '/psy/psysh/src/Psy/Command/WtfCommand.php',
    'Psy\\Compiler' => $vendorDir . '/psy/psysh/src/Psy/Compiler.php',
    'Psy\\ConfigPaths' => $vendorDir . '/psy/psysh/src/Psy/ConfigPaths.php',
    'Psy\\Configuration' => $vendorDir . '/psy/psysh/src/Psy/Configuration.php',
    'Psy\\ConsoleColorFactory' => $vendorDir . '/psy/psysh/src/Psy/ConsoleColorFactory.php',
    'Psy\\Context' => $vendorDir . '/psy/psysh/src/Psy/Context.php',
    'Psy\\ContextAware' => $vendorDir . '/psy/psysh/src/Psy/ContextAware.php',
    'Psy\\Exception\\BreakException' => $vendorDir . '/psy/psysh/src/Psy/Exception/BreakException.php',
    'Psy\\Exception\\DeprecatedException' => $vendorDir . '/psy/psysh/src/Psy/Exception/DeprecatedException.php',
    'Psy\\Exception\\ErrorException' => $vendorDir . '/psy/psysh/src/Psy/Exception/ErrorException.php',
    'Psy\\Exception\\Exception' => $vendorDir . '/psy/psysh/src/Psy/Exception/Exception.php',
    'Psy\\Exception\\FatalErrorException' => $vendorDir . '/psy/psysh/src/Psy/Exception/FatalErrorException.php',
    'Psy\\Exception\\ParseErrorException' => $vendorDir . '/psy/psysh/src/Psy/Exception/ParseErrorException.php',
    'Psy\\Exception\\RuntimeException' => $vendorDir . '/psy/psysh/src/Psy/Exception/RuntimeException.php',
    'Psy\\Exception\\ThrowUpException' => $vendorDir . '/psy/psysh/src/Psy/Exception/ThrowUpException.php',
    'Psy\\Exception\\TypeErrorException' => $vendorDir . '/psy/psysh/src/Psy/Exception/TypeErrorException.php',
    'Psy\\ExecutionLoop\\ForkingLoop' => $vendorDir . '/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php',
    'Psy\\ExecutionLoop\\Loop' => $vendorDir . '/psy/psysh/src/Psy/ExecutionLoop/Loop.php',
    'Psy\\Formatter\\CodeFormatter' => $vendorDir . '/psy/psysh/src/Psy/Formatter/CodeFormatter.php',
    'Psy\\Formatter\\DocblockFormatter' => $vendorDir . '/psy/psysh/src/Psy/Formatter/DocblockFormatter.php',
    'Psy\\Formatter\\Formatter' => $vendorDir . '/psy/psysh/src/Psy/Formatter/Formatter.php',
    'Psy\\Formatter\\SignatureFormatter' => $vendorDir . '/psy/psysh/src/Psy/Formatter/SignatureFormatter.php',
    'Psy\\Input\\CodeArgument' => $vendorDir . '/psy/psysh/src/Psy/Input/CodeArgument.php',
    'Psy\\Input\\FilterOptions' => $vendorDir . '/psy/psysh/src/Psy/Input/FilterOptions.php',
    'Psy\\Input\\ShellInput' => $vendorDir . '/psy/psysh/src/Psy/Input/ShellInput.php',
    'Psy\\Input\\SilentInput' => $vendorDir . '/psy/psysh/src/Psy/Input/SilentInput.php',
    'Psy\\Output\\OutputPager' => $vendorDir . '/psy/psysh/src/Psy/Output/OutputPager.php',
    'Psy\\Output\\PassthruPager' => $vendorDir . '/psy/psysh/src/Psy/Output/PassthruPager.php',
    'Psy\\Output\\ProcOutputPager' => $vendorDir . '/psy/psysh/src/Psy/Output/ProcOutputPager.php',
    'Psy\\Output\\ShellOutput' => $vendorDir . '/psy/psysh/src/Psy/Output/ShellOutput.php',
    'Psy\\ParserFactory' => $vendorDir . '/psy/psysh/src/Psy/ParserFactory.php',
    'Psy\\Readline\\GNUReadline' => $vendorDir . '/psy/psysh/src/Psy/Readline/GNUReadline.php',
    'Psy\\Readline\\HoaConsole' => $vendorDir . '/psy/psysh/src/Psy/Readline/HoaConsole.php',
    'Psy\\Readline\\Libedit' => $vendorDir . '/psy/psysh/src/Psy/Readline/Libedit.php',
    'Psy\\Readline\\Readline' => $vendorDir . '/psy/psysh/src/Psy/Readline/Readline.php',
    'Psy\\Readline\\Transient' => $vendorDir . '/psy/psysh/src/Psy/Readline/Transient.php',
    'Psy\\Reflection\\ReflectionConstant' => $vendorDir . '/psy/psysh/src/Psy/Reflection/ReflectionConstant.php',
    'Psy\\Reflection\\ReflectionLanguageConstruct' => $vendorDir . '/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstruct.php',
    'Psy\\Reflection\\ReflectionLanguageConstructParameter' => $vendorDir . '/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstructParameter.php',
    'Psy\\Shell' => $vendorDir . '/psy/psysh/src/Psy/Shell.php',
    'Psy\\Sudo' => $vendorDir . '/psy/psysh/src/Psy/Sudo.php',
    'Psy\\Sudo\\SudoVisitor' => $vendorDir . '/psy/psysh/src/Psy/Sudo/SudoVisitor.php',
    'Psy\\TabCompletion\\AutoCompleter' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php',
    'Psy\\TabCompletion\\Matcher\\AbstractContextAwareMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php',
    'Psy\\TabCompletion\\Matcher\\AbstractDefaultParametersMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php',
    'Psy\\TabCompletion\\Matcher\\AbstractMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ClassAttributesMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ClassMethodDefaultParametersMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ClassMethodsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ClassNamesMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php',
    'Psy\\TabCompletion\\Matcher\\CommandsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ConstantsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\FunctionDefaultParametersMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php',
    'Psy\\TabCompletion\\Matcher\\FunctionsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\KeywordsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\MongoClientMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.php',
    'Psy\\TabCompletion\\Matcher\\MongoDatabaseMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ObjectAttributesMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ObjectMethodDefaultParametersMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php',
    'Psy\\TabCompletion\\Matcher\\ObjectMethodsMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.php',
    'Psy\\TabCompletion\\Matcher\\VariablesMatcher' => $vendorDir . '/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php',
    'Psy\\Util\\Docblock' => $vendorDir . '/psy/psysh/src/Psy/Util/Docblock.php',
    'Psy\\Util\\Json' => $vendorDir . '/psy/psysh/src/Psy/Util/Json.php',
    'Psy\\Util\\Mirror' => $vendorDir . '/psy/psysh/src/Psy/Util/Mirror.php',
    'Psy\\Util\\Str' => $vendorDir . '/psy/psysh/src/Psy/Util/Str.php',
    'Psy\\VarDumper\\Cloner' => $vendorDir . '/psy/psysh/src/Psy/VarDumper/Cloner.php',
    'Psy\\VarDumper\\Dumper' => $vendorDir . '/psy/psysh/src/Psy/VarDumper/Dumper.php',
    'Psy\\VarDumper\\Presenter' => $vendorDir . '/psy/psysh/src/Psy/VarDumper/Presenter.php',
    'Psy\\VarDumper\\PresenterAware' => $vendorDir . '/psy/psysh/src/Psy/VarDumper/PresenterAware.php',
    'Psy\\VersionUpdater\\Checker' => $vendorDir . '/psy/psysh/src/Psy/VersionUpdater/Checker.php',
    'Psy\\VersionUpdater\\GitHubChecker' => $vendorDir . '/psy/psysh/src/Psy/VersionUpdater/GitHubChecker.php',
    'Psy\\VersionUpdater\\IntervalChecker' => $vendorDir . '/psy/psysh/src/Psy/VersionUpdater/IntervalChecker.php',
    'Psy\\VersionUpdater\\NoopChecker' => $vendorDir . '/psy/psysh/src/Psy/VersionUpdater/NoopChecker.php',
    'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.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\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.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\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.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\\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\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php',
    'Symfony\\Component\\Console\\Helper\\DialogHelper' => $vendorDir . '/symfony/console/Helper/DialogHelper.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\\ProgressHelper' => $vendorDir . '/symfony/console/Helper/ProgressHelper.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\\TableHelper' => $vendorDir . '/symfony/console/Helper/TableHelper.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\\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\\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\\Shell' => $vendorDir . '/symfony/console/Shell.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\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
    'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
    'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php',
    'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php',
    'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php',
    'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/ErrorHandler.php',
    'Symfony\\Component\\Debug\\ErrorHandlerCanary' => $vendorDir . '/symfony/debug/ErrorHandler.php',
    'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/ExceptionHandler.php',
    'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/debug/Exception/ClassNotFoundException.php',
    'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => $vendorDir . '/symfony/debug/Exception/ContextErrorException.php',
    'Symfony\\Component\\Debug\\Exception\\DummyException' => $vendorDir . '/symfony/debug/Exception/DummyException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Exception/FatalErrorException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => $vendorDir . '/symfony/debug/Exception/FatalThrowableError.php',
    'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Exception/FlattenException.php',
    'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => $vendorDir . '/symfony/debug/Exception/OutOfMemoryException.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => $vendorDir . '/symfony/debug/Exception/UndefinedFunctionException.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => $vendorDir . '/symfony/debug/Exception/UndefinedMethodException.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => $vendorDir . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
    'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.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\\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\\Finder\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractAdapter.php',
    'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractFindAdapter.php',
    'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/finder/Adapter/AdapterInterface.php',
    'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => $vendorDir . '/symfony/finder/Adapter/BsdFindAdapter.php',
    'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => $vendorDir . '/symfony/finder/Adapter/GnuFindAdapter.php',
    'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => $vendorDir . '/symfony/finder/Adapter/PhpAdapter.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\\AdapterFailureException' => $vendorDir . '/symfony/finder/Exception/AdapterFailureException.php',
    'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => $vendorDir . '/symfony/finder/Exception/OperationNotPermitedException.php',
    'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => $vendorDir . '/symfony/finder/Exception/ShellCommandFailureException.php',
    'Symfony\\Component\\Finder\\Expression\\Expression' => $vendorDir . '/symfony/finder/Expression/Expression.php',
    'Symfony\\Component\\Finder\\Expression\\Glob' => $vendorDir . '/symfony/finder/Expression/Glob.php',
    'Symfony\\Component\\Finder\\Expression\\Regex' => $vendorDir . '/symfony/finder/Expression/Regex.php',
    'Symfony\\Component\\Finder\\Expression\\ValueInterface' => $vendorDir . '/symfony/finder/Expression/ValueInterface.php',
    'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.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\\FilePathsIterator' => $vendorDir . '/symfony/finder/Iterator/FilePathsIterator.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\\FilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilterIterator.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\\Shell\\Command' => $vendorDir . '/symfony/finder/Shell/Command.php',
    'Symfony\\Component\\Finder\\Shell\\Shell' => $vendorDir . '/symfony/finder/Shell/Shell.php',
    'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php',
    'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => $vendorDir . '/symfony/var-dumper/Caster/AmqpCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\Caster' => $vendorDir . '/symfony/var-dumper/Caster/Caster.php',
    'Symfony\\Component\\VarDumper\\Caster\\ConstStub' => $vendorDir . '/symfony/var-dumper/Caster/ConstStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => $vendorDir . '/symfony/var-dumper/Caster/CutArrayStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\CutStub' => $vendorDir . '/symfony/var-dumper/Caster/CutStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => $vendorDir . '/symfony/var-dumper/Caster/DOMCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => $vendorDir . '/symfony/var-dumper/Caster/DoctrineCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => $vendorDir . '/symfony/var-dumper/Caster/MongoCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => $vendorDir . '/symfony/var-dumper/Caster/PdoCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => $vendorDir . '/symfony/var-dumper/Caster/PgSqlCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => $vendorDir . '/symfony/var-dumper/Caster/SplCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php',
    'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php',
    'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php',
    'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => $vendorDir . '/symfony/var-dumper/Cloner/AbstractCloner.php',
    'Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => $vendorDir . '/symfony/var-dumper/Cloner/ClonerInterface.php',
    'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => $vendorDir . '/symfony/var-dumper/Cloner/Cursor.php',
    'Symfony\\Component\\VarDumper\\Cloner\\Data' => $vendorDir . '/symfony/var-dumper/Cloner/Data.php',
    'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php',
    'Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php',
    'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php',
    'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => $vendorDir . '/symfony/var-dumper/Dumper/AbstractDumper.php',
    'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => $vendorDir . '/symfony/var-dumper/Dumper/CliDumper.php',
    'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => $vendorDir . '/symfony/var-dumper/Dumper/DataDumperInterface.php',
    'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => $vendorDir . '/symfony/var-dumper/Dumper/HtmlDumper.php',
    'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php',
    'Symfony\\Component\\VarDumper\\Test\\VarDumperTestCase' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestCase.php',
    'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestTrait.php',
    'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.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\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php',
    'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php',
    'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
    'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php',
    'Webmozart\\PathUtil\\Path' => $vendorDir . '/webmozart/path-util/src/Path.php',
    'Webmozart\\PathUtil\\Url' => $vendorDir . '/webmozart/path-util/src/Url.php',
    'XdgBaseDir\\Xdg' => $vendorDir . '/dnoegel/php-xdg-base-dir/src/Xdg.php',
);
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
    'e7223560d890eab89cda23685e711e2c' => $vendorDir . '/psy/psysh/src/Psy/functions.php',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18
{
    public static $files = array (
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
        'e7223560d890eab89cda23685e711e2c' => __DIR__ . '/..' . '/psy/psysh/src/Psy/functions.php',
    );

    public static $prefixLengthsPsr4 = array (
        'X' => 
        array (
            'XdgBaseDir\\' => 11,
        ),
        'W' => 
        array (
            'Webmozart\\PathUtil\\' => 19,
            'Webmozart\\Assert\\' => 17,
        ),
        'S' => 
        array (
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Component\\Yaml\\' => 23,
            'Symfony\\Component\\VarDumper\\' => 28,
            'Symfony\\Component\\Finder\\' => 25,
            'Symfony\\Component\\EventDispatcher\\' => 34,
            'Symfony\\Component\\Debug\\' => 24,
            'Symfony\\Component\\Console\\' => 26,
        ),
        'P' => 
        array (
            'Psy\\' => 4,
            'Psr\\Log\\' => 8,
            'PhpParser\\' => 10,
        ),
        'C' => 
        array (
            'Consolidation\\OutputFormatters\\' => 31,
            'Consolidation\\AnnotatedCommand\\' => 31,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'XdgBaseDir\\' => 
        array (
            0 => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src',
        ),
        'Webmozart\\PathUtil\\' => 
        array (
            0 => __DIR__ . '/..' . '/webmozart/path-util/src',
        ),
        'Webmozart\\Assert\\' => 
        array (
            0 => __DIR__ . '/..' . '/webmozart/assert/src',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Component\\Yaml\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/yaml',
        ),
        'Symfony\\Component\\VarDumper\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/var-dumper',
        ),
        'Symfony\\Component\\Finder\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/finder',
        ),
        'Symfony\\Component\\EventDispatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
        ),
        'Symfony\\Component\\Debug\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/debug',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Psy\\' => 
        array (
            0 => __DIR__ . '/..' . '/psy/psysh/src/Psy',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'PhpParser\\' => 
        array (
            0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
        ),
        'Consolidation\\OutputFormatters\\' => 
        array (
            0 => __DIR__ . '/..' . '/consolidation/output-formatters/src',
        ),
        'Consolidation\\AnnotatedCommand\\' => 
        array (
            0 => __DIR__ . '/..' . '/consolidation/annotated-command/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'J' => 
        array (
            'JakubOnderka\\PhpConsoleHighlighter' => 
            array (
                0 => __DIR__ . '/..' . '/jakub-onderka/php-console-highlighter/src',
            ),
            'JakubOnderka\\PhpConsoleColor' => 
            array (
                0 => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src',
            ),
        ),
        'D' => 
        array (
            'Drush' => 
            array (
                0 => __DIR__ . '/../..' . '/lib',
            ),
        ),
        'C' => 
        array (
            'Consolidation' => 
            array (
                0 => __DIR__ . '/../..' . '/lib',
            ),
        ),
    );

    public static $classMap = array (
        'Console_Table' => __DIR__ . '/..' . '/pear/console_table/Table.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\\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\\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\\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\\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\\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\\FormatterInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/FormatterInterface.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\\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\\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\\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\\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\\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\\TableDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/TableDataInterface.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\\TableTransformation' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/TableTransformation.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',
        'Drush\\Boot\\BaseBoot' => __DIR__ . '/../..' . '/lib/Drush/Boot/BaseBoot.php',
        'Drush\\Boot\\Boot' => __DIR__ . '/../..' . '/lib/Drush/Boot/Boot.php',
        'Drush\\Boot\\DrupalBoot' => __DIR__ . '/../..' . '/lib/Drush/Boot/DrupalBoot.php',
        'Drush\\Boot\\DrupalBoot6' => __DIR__ . '/../..' . '/lib/Drush/Boot/DrupalBoot6.php',
        'Drush\\Boot\\DrupalBoot7' => __DIR__ . '/../..' . '/lib/Drush/Boot/DrupalBoot7.php',
        'Drush\\Boot\\DrupalBoot8' => __DIR__ . '/../..' . '/lib/Drush/Boot/DrupalBoot8.php',
        'Drush\\Boot\\EmptyBoot' => __DIR__ . '/../..' . '/lib/Drush/Boot/EmptyBoot.php',
        'Drush\\Cache\\CacheInterface' => __DIR__ . '/../..' . '/lib/Drush/Cache/CacheInterface.php',
        'Drush\\Cache\\FileCache' => __DIR__ . '/../..' . '/lib/Drush/Cache/FileCache.php',
        'Drush\\Cache\\JSONCache' => __DIR__ . '/../..' . '/lib/Drush/Cache/JSONCache.php',
        'Drush\\CommandFiles\\Core\\BrowseCommands' => __DIR__ . '/../..' . '/lib/Drush/CommandFiles/core/browseCommands.php',
        'Drush\\CommandFiles\\ExampleCommandFile' => __DIR__ . '/../..' . '/lib/Drush/CommandFiles/ExampleCommandFile.php',
        'Drush\\CommandFiles\\core\\DrupliconCommands' => __DIR__ . '/../..' . '/lib/Drush/CommandFiles/core/DrupliconCommands.php',
        'Drush\\Command\\Commandfiles' => __DIR__ . '/../..' . '/lib/Drush/Command/Commandfiles.php',
        'Drush\\Command\\CommandfilesInterface' => __DIR__ . '/../..' . '/lib/Drush/Command/CommandfilesInterface.php',
        'Drush\\Command\\DrushInputAdapter' => __DIR__ . '/../..' . '/lib/Drush/Command/DrushInputAdapter.php',
        'Drush\\Command\\DrushOutputAdapter' => __DIR__ . '/../..' . '/lib/Drush/Command/DrushOutputAdapter.php',
        'Drush\\Command\\ServiceCommandlist' => __DIR__ . '/../..' . '/lib/Drush/Command/ServiceCommandlist.php',
        'Drush\\Commands\\core\\SanitizeCommands' => __DIR__ . '/../..' . '/lib/Drush/Commands/core/SanitizeCommands.php',
        'Drush\\Commands\\core\\StatusCommands' => __DIR__ . '/../..' . '/lib/Drush/Commands/core/StatusCommands.php',
        'Drush\\Drupal\\DrupalKernel' => __DIR__ . '/../..' . '/lib/Drush/Drupal/DrupalKernel.php',
        'Drush\\Drupal\\DrushServiceModifier' => __DIR__ . '/../..' . '/lib/Drush/Drupal/DrushServiceModifier.php',
        'Drush\\Drupal\\ExtensionDiscovery' => __DIR__ . '/../..' . '/lib/Drush/Drupal/ExtensionDiscovery.php',
        'Drush\\Drupal\\FindCommandsCompilerPass' => __DIR__ . '/../..' . '/lib/Drush/Drupal/FindCommandsCompilerPass.php',
        'Drush\\Log\\DrushLog' => __DIR__ . '/../..' . '/lib/Drush/Log/DrushLog.php',
        'Drush\\Log\\LogLevel' => __DIR__ . '/../..' . '/lib/Drush/Log/LogLevel.php',
        'Drush\\Log\\Logger' => __DIR__ . '/../..' . '/lib/Drush/Log/Logger.php',
        'Drush\\Make\\Parser\\ParserIni' => __DIR__ . '/../..' . '/lib/Drush/Make/Parser/ParserIni.php',
        'Drush\\Make\\Parser\\ParserInterface' => __DIR__ . '/../..' . '/lib/Drush/Make/Parser/ParserInterface.php',
        'Drush\\Make\\Parser\\ParserYaml' => __DIR__ . '/../..' . '/lib/Drush/Make/Parser/ParserYaml.php',
        'Drush\\Psysh\\Caster' => __DIR__ . '/../..' . '/lib/Drush/Psysh/Caster.php',
        'Drush\\Psysh\\DrushCommand' => __DIR__ . '/../..' . '/lib/Drush/Psysh/DrushCommand.php',
        'Drush\\Psysh\\DrushHelpCommand' => __DIR__ . '/../..' . '/lib/Drush/Psysh/DrushHelpCommand.php',
        'Drush\\Psysh\\Shell' => __DIR__ . '/../..' . '/lib/Drush/Psysh/Shell.php',
        'Drush\\Queue\\Queue6' => __DIR__ . '/../..' . '/lib/Drush/Queue/Queue6.php',
        'Drush\\Queue\\Queue7' => __DIR__ . '/../..' . '/lib/Drush/Queue/Queue7.php',
        'Drush\\Queue\\Queue8' => __DIR__ . '/../..' . '/lib/Drush/Queue/Queue8.php',
        'Drush\\Queue\\QueueBase' => __DIR__ . '/../..' . '/lib/Drush/Queue/QueueBase.php',
        'Drush\\Queue\\QueueException' => __DIR__ . '/../..' . '/lib/Drush/Queue/QueueException.php',
        'Drush\\Queue\\QueueInterface' => __DIR__ . '/../..' . '/lib/Drush/Queue/QueueInterface.php',
        'Drush\\Role\\Role6' => __DIR__ . '/../..' . '/lib/Drush/Role/Role6.php',
        'Drush\\Role\\Role7' => __DIR__ . '/../..' . '/lib/Drush/Role/Role7.php',
        'Drush\\Role\\Role8' => __DIR__ . '/../..' . '/lib/Drush/Role/Role8.php',
        'Drush\\Role\\RoleBase' => __DIR__ . '/../..' . '/lib/Drush/Role/RoleBase.php',
        'Drush\\Role\\RoleException' => __DIR__ . '/../..' . '/lib/Drush/Role/RoleException.php',
        'Drush\\Sql\\Sql6' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sql6.php',
        'Drush\\Sql\\Sql7' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sql7.php',
        'Drush\\Sql\\Sql8' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sql8.php',
        'Drush\\Sql\\SqlBase' => __DIR__ . '/../..' . '/lib/Drush/Sql/SqlBase.php',
        'Drush\\Sql\\SqlException' => __DIR__ . '/../..' . '/lib/Drush/Sql/SqlException.php',
        'Drush\\Sql\\SqlVersion' => __DIR__ . '/../..' . '/lib/Drush/Sql/SqlVersion.php',
        'Drush\\Sql\\Sqlmysql' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sqlmysql.php',
        'Drush\\Sql\\Sqloracle' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sqloracle.php',
        'Drush\\Sql\\Sqlpgsql' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sqlpgsql.php',
        'Drush\\Sql\\Sqlsqlite' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sqlsqlite.php',
        'Drush\\Sql\\Sqlsqlsrv' => __DIR__ . '/../..' . '/lib/Drush/Sql/Sqlsqlsrv.php',
        'Drush\\UpdateService\\Project' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/Project.php',
        'Drush\\UpdateService\\ReleaseInfo' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/ReleaseInfo.php',
        'Drush\\UpdateService\\StatusInfoDrupal6' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/StatusInfoDrupal6.php',
        'Drush\\UpdateService\\StatusInfoDrupal7' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/StatusInfoDrupal7.php',
        'Drush\\UpdateService\\StatusInfoDrupal8' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/StatusInfoDrupal8.php',
        'Drush\\UpdateService\\StatusInfoDrush' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/StatusInfoDrush.php',
        'Drush\\UpdateService\\StatusInfoInterface' => __DIR__ . '/../..' . '/lib/Drush/UpdateService/StatusInfoInterface.php',
        'Drush\\User\\User6' => __DIR__ . '/../..' . '/lib/Drush/User/User6.php',
        'Drush\\User\\User7' => __DIR__ . '/../..' . '/lib/Drush/User/User7.php',
        'Drush\\User\\User8' => __DIR__ . '/../..' . '/lib/Drush/User/User8.php',
        'Drush\\User\\UserList' => __DIR__ . '/../..' . '/lib/Drush/User/UserList.php',
        'Drush\\User\\UserListException' => __DIR__ . '/../..' . '/lib/Drush/User/UserListException.php',
        'Drush\\User\\UserSingle6' => __DIR__ . '/../..' . '/lib/Drush/User/UserSingle6.php',
        'Drush\\User\\UserSingle7' => __DIR__ . '/../..' . '/lib/Drush/User/UserSingle7.php',
        'Drush\\User\\UserSingle8' => __DIR__ . '/../..' . '/lib/Drush/User/UserSingle8.php',
        'Drush\\User\\UserSingleBase' => __DIR__ . '/../..' . '/lib/Drush/User/UserSingleBase.php',
        'Drush\\User\\UserVersion' => __DIR__ . '/../..' . '/lib/Drush/User/UserVersion.php',
        'JakubOnderka\\PhpConsoleColor\\ConsoleColor' => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/ConsoleColor.php',
        'JakubOnderka\\PhpConsoleColor\\InvalidStyleException' => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/InvalidStyleException.php',
        'JakubOnderka\\PhpConsoleHighlighter\\Highlighter' => __DIR__ . '/..' . '/jakub-onderka/php-console-highlighter/src/JakubOnderka/PhpConsoleHighlighter/Highlighter.php',
        'PhpParser\\Autoloader' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Autoloader.php',
        'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php',
        'PhpParser\\BuilderAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderAbstract.php',
        'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
        'PhpParser\\Builder\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php',
        'PhpParser\\Builder\\Declaration' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php',
        'PhpParser\\Builder\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php',
        'PhpParser\\Builder\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php',
        'PhpParser\\Builder\\Interface_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php',
        'PhpParser\\Builder\\Method' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Method.php',
        'PhpParser\\Builder\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php',
        'PhpParser\\Builder\\Param' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Param.php',
        'PhpParser\\Builder\\Property' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Property.php',
        'PhpParser\\Builder\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Trait_.php',
        'PhpParser\\Builder\\Use_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Use_.php',
        'PhpParser\\Comment' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Comment.php',
        'PhpParser\\Comment\\Doc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Comment/Doc.php',
        'PhpParser\\Error' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Error.php',
        'PhpParser\\Lexer' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer.php',
        'PhpParser\\Lexer\\Emulative' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php',
        'PhpParser\\Node' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node.php',
        'PhpParser\\NodeAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeAbstract.php',
        'PhpParser\\NodeDumper' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeDumper.php',
        'PhpParser\\NodeTraverser' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeTraverser.php',
        'PhpParser\\NodeTraverserInterface' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php',
        'PhpParser\\NodeVisitor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor.php',
        'PhpParser\\NodeVisitorAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php',
        'PhpParser\\NodeVisitor\\NameResolver' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php',
        'PhpParser\\Node\\Arg' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Arg.php',
        'PhpParser\\Node\\Const_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Const_.php',
        'PhpParser\\Node\\Expr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr.php',
        'PhpParser\\Node\\Expr\\ArrayDimFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php',
        'PhpParser\\Node\\Expr\\ArrayItem' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php',
        'PhpParser\\Node\\Expr\\Array_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php',
        'PhpParser\\Node\\Expr\\Assign' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php',
        'PhpParser\\Node\\Expr\\AssignOp' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php',
        'PhpParser\\Node\\Expr\\AssignOp\\BitwiseAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php',
        'PhpParser\\Node\\Expr\\AssignOp\\BitwiseOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php',
        'PhpParser\\Node\\Expr\\AssignOp\\BitwiseXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Concat' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Div' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Minus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Mod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Mul' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Plus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php',
        'PhpParser\\Node\\Expr\\AssignOp\\Pow' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php',
        'PhpParser\\Node\\Expr\\AssignOp\\ShiftLeft' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php',
        'PhpParser\\Node\\Expr\\AssignOp\\ShiftRight' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php',
        'PhpParser\\Node\\Expr\\AssignRef' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php',
        'PhpParser\\Node\\Expr\\BinaryOp' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\BitwiseXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\BooleanAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\BooleanOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Coalesce' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Concat' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Div' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Equal' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Greater' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\GreaterOrEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Identical' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalAnd' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalOr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\LogicalXor' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Minus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Mod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Mul' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\NotEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\NotIdentical' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Plus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Pow' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\ShiftLeft' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\ShiftRight' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Smaller' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\SmallerOrEqual' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php',
        'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php',
        'PhpParser\\Node\\Expr\\BitwiseNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php',
        'PhpParser\\Node\\Expr\\BooleanNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php',
        'PhpParser\\Node\\Expr\\Cast' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php',
        'PhpParser\\Node\\Expr\\Cast\\Array_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php',
        'PhpParser\\Node\\Expr\\Cast\\Bool_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php',
        'PhpParser\\Node\\Expr\\Cast\\Double' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php',
        'PhpParser\\Node\\Expr\\Cast\\Int_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php',
        'PhpParser\\Node\\Expr\\Cast\\Object_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php',
        'PhpParser\\Node\\Expr\\Cast\\String_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php',
        'PhpParser\\Node\\Expr\\Cast\\Unset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php',
        'PhpParser\\Node\\Expr\\ClassConstFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php',
        'PhpParser\\Node\\Expr\\Clone_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php',
        'PhpParser\\Node\\Expr\\Closure' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php',
        'PhpParser\\Node\\Expr\\ClosureUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php',
        'PhpParser\\Node\\Expr\\ConstFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php',
        'PhpParser\\Node\\Expr\\Empty_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php',
        'PhpParser\\Node\\Expr\\ErrorSuppress' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php',
        'PhpParser\\Node\\Expr\\Eval_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php',
        'PhpParser\\Node\\Expr\\Exit_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php',
        'PhpParser\\Node\\Expr\\FuncCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php',
        'PhpParser\\Node\\Expr\\Include_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php',
        'PhpParser\\Node\\Expr\\Instanceof_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php',
        'PhpParser\\Node\\Expr\\Isset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php',
        'PhpParser\\Node\\Expr\\List_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php',
        'PhpParser\\Node\\Expr\\MethodCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php',
        'PhpParser\\Node\\Expr\\New_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php',
        'PhpParser\\Node\\Expr\\PostDec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php',
        'PhpParser\\Node\\Expr\\PostInc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php',
        'PhpParser\\Node\\Expr\\PreDec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php',
        'PhpParser\\Node\\Expr\\PreInc' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php',
        'PhpParser\\Node\\Expr\\Print_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php',
        'PhpParser\\Node\\Expr\\PropertyFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php',
        'PhpParser\\Node\\Expr\\ShellExec' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php',
        'PhpParser\\Node\\Expr\\StaticCall' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php',
        'PhpParser\\Node\\Expr\\StaticPropertyFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php',
        'PhpParser\\Node\\Expr\\Ternary' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php',
        'PhpParser\\Node\\Expr\\UnaryMinus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php',
        'PhpParser\\Node\\Expr\\UnaryPlus' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php',
        'PhpParser\\Node\\Expr\\Variable' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php',
        'PhpParser\\Node\\Expr\\YieldFrom' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php',
        'PhpParser\\Node\\Expr\\Yield_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php',
        'PhpParser\\Node\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php',
        'PhpParser\\Node\\Name' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name.php',
        'PhpParser\\Node\\Name\\FullyQualified' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php',
        'PhpParser\\Node\\Name\\Relative' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php',
        'PhpParser\\Node\\Param' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Param.php',
        'PhpParser\\Node\\Scalar' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar.php',
        'PhpParser\\Node\\Scalar\\DNumber' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php',
        'PhpParser\\Node\\Scalar\\Encapsed' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php',
        'PhpParser\\Node\\Scalar\\EncapsedStringPart' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php',
        'PhpParser\\Node\\Scalar\\LNumber' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php',
        'PhpParser\\Node\\Scalar\\MagicConst' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Dir' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\File' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Line' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Method' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php',
        'PhpParser\\Node\\Scalar\\MagicConst\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php',
        'PhpParser\\Node\\Scalar\\String_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php',
        'PhpParser\\Node\\Stmt' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt.php',
        'PhpParser\\Node\\Stmt\\Break_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php',
        'PhpParser\\Node\\Stmt\\Case_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php',
        'PhpParser\\Node\\Stmt\\Catch_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php',
        'PhpParser\\Node\\Stmt\\ClassConst' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php',
        'PhpParser\\Node\\Stmt\\ClassLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php',
        'PhpParser\\Node\\Stmt\\ClassMethod' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php',
        'PhpParser\\Node\\Stmt\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php',
        'PhpParser\\Node\\Stmt\\Const_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php',
        'PhpParser\\Node\\Stmt\\Continue_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php',
        'PhpParser\\Node\\Stmt\\DeclareDeclare' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php',
        'PhpParser\\Node\\Stmt\\Declare_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php',
        'PhpParser\\Node\\Stmt\\Do_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php',
        'PhpParser\\Node\\Stmt\\Echo_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php',
        'PhpParser\\Node\\Stmt\\ElseIf_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php',
        'PhpParser\\Node\\Stmt\\Else_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php',
        'PhpParser\\Node\\Stmt\\For_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php',
        'PhpParser\\Node\\Stmt\\Foreach_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php',
        'PhpParser\\Node\\Stmt\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php',
        'PhpParser\\Node\\Stmt\\Global_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php',
        'PhpParser\\Node\\Stmt\\Goto_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php',
        'PhpParser\\Node\\Stmt\\GroupUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php',
        'PhpParser\\Node\\Stmt\\HaltCompiler' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php',
        'PhpParser\\Node\\Stmt\\If_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php',
        'PhpParser\\Node\\Stmt\\InlineHTML' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php',
        'PhpParser\\Node\\Stmt\\Interface_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php',
        'PhpParser\\Node\\Stmt\\Label' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php',
        'PhpParser\\Node\\Stmt\\Namespace_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php',
        'PhpParser\\Node\\Stmt\\Nop' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php',
        'PhpParser\\Node\\Stmt\\Property' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php',
        'PhpParser\\Node\\Stmt\\PropertyProperty' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php',
        'PhpParser\\Node\\Stmt\\Return_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php',
        'PhpParser\\Node\\Stmt\\StaticVar' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php',
        'PhpParser\\Node\\Stmt\\Static_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php',
        'PhpParser\\Node\\Stmt\\Switch_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php',
        'PhpParser\\Node\\Stmt\\Throw_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php',
        'PhpParser\\Node\\Stmt\\TraitUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php',
        'PhpParser\\Node\\Stmt\\TraitUseAdaptation' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php',
        'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Alias' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php',
        'PhpParser\\Node\\Stmt\\TraitUseAdaptation\\Precedence' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php',
        'PhpParser\\Node\\Stmt\\Trait_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php',
        'PhpParser\\Node\\Stmt\\TryCatch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php',
        'PhpParser\\Node\\Stmt\\Unset_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php',
        'PhpParser\\Node\\Stmt\\UseUse' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php',
        'PhpParser\\Node\\Stmt\\Use_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php',
        'PhpParser\\Node\\Stmt\\While_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php',
        'PhpParser\\Parser' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser.php',
        'PhpParser\\ParserAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php',
        'PhpParser\\ParserFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserFactory.php',
        'PhpParser\\Parser\\Multiple' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Multiple.php',
        'PhpParser\\Parser\\Php5' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Php5.php',
        'PhpParser\\Parser\\Php7' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Php7.php',
        'PhpParser\\Parser\\Tokens' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php',
        'PhpParser\\PrettyPrinterAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php',
        'PhpParser\\PrettyPrinter\\Standard' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php',
        'PhpParser\\Serializer' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Serializer.php',
        'PhpParser\\Serializer\\XML' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Serializer/XML.php',
        'PhpParser\\Unserializer' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Unserializer.php',
        'PhpParser\\Unserializer\\XML' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Unserializer/XML.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',
        'Psy\\Autoloader' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Autoloader.php',
        'Psy\\CodeCleaner' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner.php',
        'Psy\\CodeCleaner\\AbstractClassPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/AbstractClassPass.php',
        'Psy\\CodeCleaner\\AssignThisVariablePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/AssignThisVariablePass.php',
        'Psy\\CodeCleaner\\CallTimePassByReferencePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/CallTimePassByReferencePass.php',
        'Psy\\CodeCleaner\\CalledClassPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/CalledClassPass.php',
        'Psy\\CodeCleaner\\CodeCleanerPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/CodeCleanerPass.php',
        'Psy\\CodeCleaner\\ExitPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/ExitPass.php',
        'Psy\\CodeCleaner\\FinalClassPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/FinalClassPass.php',
        'Psy\\CodeCleaner\\FunctionContextPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/FunctionContextPass.php',
        'Psy\\CodeCleaner\\FunctionReturnInWriteContextPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/FunctionReturnInWriteContextPass.php',
        'Psy\\CodeCleaner\\ImplicitReturnPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/ImplicitReturnPass.php',
        'Psy\\CodeCleaner\\InstanceOfPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/InstanceOfPass.php',
        'Psy\\CodeCleaner\\LeavePsyshAlonePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/LeavePsyshAlonePass.php',
        'Psy\\CodeCleaner\\LegacyEmptyPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/LegacyEmptyPass.php',
        'Psy\\CodeCleaner\\LoopContextPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/LoopContextPass.php',
        'Psy\\CodeCleaner\\MagicConstantsPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/MagicConstantsPass.php',
        'Psy\\CodeCleaner\\NamespaceAwarePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/NamespaceAwarePass.php',
        'Psy\\CodeCleaner\\NamespacePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/NamespacePass.php',
        'Psy\\CodeCleaner\\NoReturnValue' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/NoReturnValue.php',
        'Psy\\CodeCleaner\\PassableByReferencePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/PassableByReferencePass.php',
        'Psy\\CodeCleaner\\RequirePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/RequirePass.php',
        'Psy\\CodeCleaner\\StaticConstructorPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/StaticConstructorPass.php',
        'Psy\\CodeCleaner\\StrictTypesPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/StrictTypesPass.php',
        'Psy\\CodeCleaner\\UseStatementPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/UseStatementPass.php',
        'Psy\\CodeCleaner\\ValidClassNamePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/ValidClassNamePass.php',
        'Psy\\CodeCleaner\\ValidConstantPass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/ValidConstantPass.php',
        'Psy\\CodeCleaner\\ValidFunctionNamePass' => __DIR__ . '/..' . '/psy/psysh/src/Psy/CodeCleaner/ValidFunctionNamePass.php',
        'Psy\\Command\\BufferCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/BufferCommand.php',
        'Psy\\Command\\ClearCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ClearCommand.php',
        'Psy\\Command\\Command' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/Command.php',
        'Psy\\Command\\DocCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/DocCommand.php',
        'Psy\\Command\\DumpCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/DumpCommand.php',
        'Psy\\Command\\EditCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/EditCommand.php',
        'Psy\\Command\\ExitCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ExitCommand.php',
        'Psy\\Command\\HelpCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/HelpCommand.php',
        'Psy\\Command\\HistoryCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/HistoryCommand.php',
        'Psy\\Command\\ListCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand.php',
        'Psy\\Command\\ListCommand\\ClassConstantEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/ClassConstantEnumerator.php',
        'Psy\\Command\\ListCommand\\ClassEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/ClassEnumerator.php',
        'Psy\\Command\\ListCommand\\ConstantEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/ConstantEnumerator.php',
        'Psy\\Command\\ListCommand\\Enumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/Enumerator.php',
        'Psy\\Command\\ListCommand\\FunctionEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/FunctionEnumerator.php',
        'Psy\\Command\\ListCommand\\GlobalVariableEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/GlobalVariableEnumerator.php',
        'Psy\\Command\\ListCommand\\InterfaceEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/InterfaceEnumerator.php',
        'Psy\\Command\\ListCommand\\MethodEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/MethodEnumerator.php',
        'Psy\\Command\\ListCommand\\PropertyEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/PropertyEnumerator.php',
        'Psy\\Command\\ListCommand\\TraitEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/TraitEnumerator.php',
        'Psy\\Command\\ListCommand\\VariableEnumerator' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ListCommand/VariableEnumerator.php',
        'Psy\\Command\\ParseCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ParseCommand.php',
        'Psy\\Command\\PsyVersionCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/PsyVersionCommand.php',
        'Psy\\Command\\ReflectingCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ReflectingCommand.php',
        'Psy\\Command\\ShowCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ShowCommand.php',
        'Psy\\Command\\SudoCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/SudoCommand.php',
        'Psy\\Command\\ThrowUpCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/ThrowUpCommand.php',
        'Psy\\Command\\TraceCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/TraceCommand.php',
        'Psy\\Command\\WhereamiCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/WhereamiCommand.php',
        'Psy\\Command\\WtfCommand' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Command/WtfCommand.php',
        'Psy\\Compiler' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Compiler.php',
        'Psy\\ConfigPaths' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ConfigPaths.php',
        'Psy\\Configuration' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Configuration.php',
        'Psy\\ConsoleColorFactory' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ConsoleColorFactory.php',
        'Psy\\Context' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Context.php',
        'Psy\\ContextAware' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ContextAware.php',
        'Psy\\Exception\\BreakException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/BreakException.php',
        'Psy\\Exception\\DeprecatedException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/DeprecatedException.php',
        'Psy\\Exception\\ErrorException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/ErrorException.php',
        'Psy\\Exception\\Exception' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/Exception.php',
        'Psy\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/FatalErrorException.php',
        'Psy\\Exception\\ParseErrorException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/ParseErrorException.php',
        'Psy\\Exception\\RuntimeException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/RuntimeException.php',
        'Psy\\Exception\\ThrowUpException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/ThrowUpException.php',
        'Psy\\Exception\\TypeErrorException' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Exception/TypeErrorException.php',
        'Psy\\ExecutionLoop\\ForkingLoop' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php',
        'Psy\\ExecutionLoop\\Loop' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ExecutionLoop/Loop.php',
        'Psy\\Formatter\\CodeFormatter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Formatter/CodeFormatter.php',
        'Psy\\Formatter\\DocblockFormatter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Formatter/DocblockFormatter.php',
        'Psy\\Formatter\\Formatter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Formatter/Formatter.php',
        'Psy\\Formatter\\SignatureFormatter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Formatter/SignatureFormatter.php',
        'Psy\\Input\\CodeArgument' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Input/CodeArgument.php',
        'Psy\\Input\\FilterOptions' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Input/FilterOptions.php',
        'Psy\\Input\\ShellInput' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Input/ShellInput.php',
        'Psy\\Input\\SilentInput' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Input/SilentInput.php',
        'Psy\\Output\\OutputPager' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Output/OutputPager.php',
        'Psy\\Output\\PassthruPager' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Output/PassthruPager.php',
        'Psy\\Output\\ProcOutputPager' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Output/ProcOutputPager.php',
        'Psy\\Output\\ShellOutput' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Output/ShellOutput.php',
        'Psy\\ParserFactory' => __DIR__ . '/..' . '/psy/psysh/src/Psy/ParserFactory.php',
        'Psy\\Readline\\GNUReadline' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Readline/GNUReadline.php',
        'Psy\\Readline\\HoaConsole' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Readline/HoaConsole.php',
        'Psy\\Readline\\Libedit' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Readline/Libedit.php',
        'Psy\\Readline\\Readline' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Readline/Readline.php',
        'Psy\\Readline\\Transient' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Readline/Transient.php',
        'Psy\\Reflection\\ReflectionConstant' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Reflection/ReflectionConstant.php',
        'Psy\\Reflection\\ReflectionLanguageConstruct' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstruct.php',
        'Psy\\Reflection\\ReflectionLanguageConstructParameter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Reflection/ReflectionLanguageConstructParameter.php',
        'Psy\\Shell' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Shell.php',
        'Psy\\Sudo' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Sudo.php',
        'Psy\\Sudo\\SudoVisitor' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Sudo/SudoVisitor.php',
        'Psy\\TabCompletion\\AutoCompleter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/AutoCompleter.php',
        'Psy\\TabCompletion\\Matcher\\AbstractContextAwareMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractContextAwareMatcher.php',
        'Psy\\TabCompletion\\Matcher\\AbstractDefaultParametersMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php',
        'Psy\\TabCompletion\\Matcher\\AbstractMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/AbstractMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ClassAttributesMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassAttributesMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ClassMethodDefaultParametersMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ClassMethodsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassMethodsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ClassNamesMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ClassNamesMatcher.php',
        'Psy\\TabCompletion\\Matcher\\CommandsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/CommandsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ConstantsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ConstantsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\FunctionDefaultParametersMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php',
        'Psy\\TabCompletion\\Matcher\\FunctionsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/FunctionsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\KeywordsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/KeywordsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\MongoClientMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/MongoClientMatcher.php',
        'Psy\\TabCompletion\\Matcher\\MongoDatabaseMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/MongoDatabaseMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ObjectAttributesMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectAttributesMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ObjectMethodDefaultParametersMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php',
        'Psy\\TabCompletion\\Matcher\\ObjectMethodsMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/ObjectMethodsMatcher.php',
        'Psy\\TabCompletion\\Matcher\\VariablesMatcher' => __DIR__ . '/..' . '/psy/psysh/src/Psy/TabCompletion/Matcher/VariablesMatcher.php',
        'Psy\\Util\\Docblock' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Util/Docblock.php',
        'Psy\\Util\\Json' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Util/Json.php',
        'Psy\\Util\\Mirror' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Util/Mirror.php',
        'Psy\\Util\\Str' => __DIR__ . '/..' . '/psy/psysh/src/Psy/Util/Str.php',
        'Psy\\VarDumper\\Cloner' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VarDumper/Cloner.php',
        'Psy\\VarDumper\\Dumper' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VarDumper/Dumper.php',
        'Psy\\VarDumper\\Presenter' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VarDumper/Presenter.php',
        'Psy\\VarDumper\\PresenterAware' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VarDumper/PresenterAware.php',
        'Psy\\VersionUpdater\\Checker' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VersionUpdater/Checker.php',
        'Psy\\VersionUpdater\\GitHubChecker' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VersionUpdater/GitHubChecker.php',
        'Psy\\VersionUpdater\\IntervalChecker' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VersionUpdater/IntervalChecker.php',
        'Psy\\VersionUpdater\\NoopChecker' => __DIR__ . '/..' . '/psy/psysh/src/Psy/VersionUpdater/NoopChecker.php',
        'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.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\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.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\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleExceptionEvent.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\\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\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php',
        'Symfony\\Component\\Console\\Helper\\DialogHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DialogHelper.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\\ProgressHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressHelper.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\\TableHelper' => __DIR__ . '/..' . '/symfony/console/Helper/TableHelper.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\\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\\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\\Shell' => __DIR__ . '/..' . '/symfony/console/Shell.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\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
        'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
        'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php',
        'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php',
        'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php',
        'Symfony\\Component\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php',
        'Symfony\\Component\\Debug\\ErrorHandlerCanary' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php',
        'Symfony\\Component\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/debug/ExceptionHandler.php',
        'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/debug/Exception/ClassNotFoundException.php',
        'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/ContextErrorException.php',
        'Symfony\\Component\\Debug\\Exception\\DummyException' => __DIR__ . '/..' . '/symfony/debug/Exception/DummyException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalErrorException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalThrowableError.php',
        'Symfony\\Component\\Debug\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/debug/Exception/FlattenException.php',
        'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => __DIR__ . '/..' . '/symfony/debug/Exception/OutOfMemoryException.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedFunctionException.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedMethodException.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
        'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.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\\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\\Finder\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/AbstractAdapter.php',
        'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/AbstractFindAdapter.php',
        'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/finder/Adapter/AdapterInterface.php',
        'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/BsdFindAdapter.php',
        'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/GnuFindAdapter.php',
        'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/PhpAdapter.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\\AdapterFailureException' => __DIR__ . '/..' . '/symfony/finder/Exception/AdapterFailureException.php',
        'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/finder/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => __DIR__ . '/..' . '/symfony/finder/Exception/OperationNotPermitedException.php',
        'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => __DIR__ . '/..' . '/symfony/finder/Exception/ShellCommandFailureException.php',
        'Symfony\\Component\\Finder\\Expression\\Expression' => __DIR__ . '/..' . '/symfony/finder/Expression/Expression.php',
        'Symfony\\Component\\Finder\\Expression\\Glob' => __DIR__ . '/..' . '/symfony/finder/Expression/Glob.php',
        'Symfony\\Component\\Finder\\Expression\\Regex' => __DIR__ . '/..' . '/symfony/finder/Expression/Regex.php',
        'Symfony\\Component\\Finder\\Expression\\ValueInterface' => __DIR__ . '/..' . '/symfony/finder/Expression/ValueInterface.php',
        'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.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\\FilePathsIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilePathsIterator.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\\FilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilterIterator.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\\Shell\\Command' => __DIR__ . '/..' . '/symfony/finder/Shell/Command.php',
        'Symfony\\Component\\Finder\\Shell\\Shell' => __DIR__ . '/..' . '/symfony/finder/Shell/Shell.php',
        'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php',
        'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/AmqpCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\Caster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/Caster.php',
        'Symfony\\Component\\VarDumper\\Caster\\ConstStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ConstStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutArrayStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\CutStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DOMCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DoctrineCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MongoCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PdoCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PgSqlCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SplCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php',
        'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php',
        'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php',
        'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/AbstractCloner.php',
        'Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/ClonerInterface.php',
        'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Cursor.php',
        'Symfony\\Component\\VarDumper\\Cloner\\Data' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Data.php',
        'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php',
        'Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php',
        'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php',
        'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/AbstractDumper.php',
        'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/CliDumper.php',
        'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/DataDumperInterface.php',
        'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/HtmlDumper.php',
        'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php',
        'Symfony\\Component\\VarDumper\\Test\\VarDumperTestCase' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestCase.php',
        'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestTrait.php',
        'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.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\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php',
        'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php',
        'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php',
        'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php',
        'Webmozart\\PathUtil\\Path' => __DIR__ . '/..' . '/webmozart/path-util/src/Path.php',
        'Webmozart\\PathUtil\\Url' => __DIR__ . '/..' . '/webmozart/path-util/src/Url.php',
        'XdgBaseDir\\Xdg' => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src/Xdg.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::$prefixesPsr0;
            $loader->classMap = ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::$classMap;

        }, null, ClassLoader::class);
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInita10aa57c4ae20a31073bf4a2d3443e18
{
    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('ComposerAutoloaderInita10aa57c4ae20a31073bf4a2d3443e18', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInita10aa57c4ae20a31073bf4a2d3443e18', 'loadClassLoader'));

        $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\ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::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\ComposerStaticInita10aa57c4ae20a31073bf4a2d3443e18::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequirea10aa57c4ae20a31073bf4a2d3443e18($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequirea10aa57c4ae20a31073bf4a2d3443e18($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?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') && ini_get('apc.enabled') ? $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

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

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?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;

/**
 * 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;

/**
 * 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;

/**
 * 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
     */
    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
     */
    abstract public function log($level, $message, array $context = array());
}
<?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
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
<?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 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;

use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\Exception\InvalidFormatException;
use Consolidation\OutputFormatters\Exception\UnknownFormatException;
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
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;

/**
 * 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 = [
            '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',
        ];
        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);
        }

        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);
            $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)
    {
        $formatter = $this->getFormatter((string)$format);
        if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) {
            $validFormats = $this->validFormats($structuredOutput);
            throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats);
        }
        // Give the formatter a chance to override the options
        $options = $this->overrideOptions($formatter, $structuredOutput, $options);
        $structuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
        $formatter->write($output, $structuredOutput, $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;
    }

    /**
     * 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)
    {
        if ($formatter instanceof OverrideOptionsInterface) {
            return $formatter->overrideOptions($structuredOutput, $options);
        }
        return $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 => ',',
        ];
    }

    /**
     * @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);
        }
    }

    protected function writeOneLine(OutputInterface $output, $data, $options)
    {
        $defaults = $this->getDefaultFormatterOptions();
        $delimiter = $options->get(FormatterOptions::DELIMITER, $defaults);

        $output->write($this->csvEscape($data, $delimiter));
    }

    protected function csvEscape($data, $delimiter = ',')
    {
        $buffer = fopen('php://temp', 'r+');
        fputcsv($buffer, $data, $delimiter);
        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;

interface FormatterInterface
{
    /**
     * Given structured data, apply appropriate
     * formatting, and return a printable string.
     *
     * @param mixed $data Structured data to format
     *
     * @return string
     */
    public function write(OutputInterface $output, $data, FormatterOptions $options);
}
<?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 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;

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\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 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;

/**
 * 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\Validate\ValidationInterface;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;

/**
 * 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
{
    /**
     * All data types are acceptable.
     */
    public function isValidDataType(\ReflectionClass $dataType)
    {
        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 as a TSV value.
     *
     * @param OutputInterface $output
     * @param mixed $data
     * @param FormatterOptions $options
     */
    protected function reduceToSigleFieldAndWrite(OutputInterface $output, $data, FormatterOptions $options)
    {
        $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 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;

/**
 * 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
{
    use ValidDataTypesTrait;
    use RenderTableDataTrait;

    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\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;

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\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 (!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 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\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_STRING_FIELD = 'default-string-field';
    const DELIMITER = 'delimiter';
    const LIST_DELIMITER = 'list-delimiter';
    const TERMINAL_WIDTH = 'width';

    /**
     * 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 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);
    }

    /**
     * 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\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\StructuredData;

use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
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 ListDataFromKeys 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 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, $defaults);
        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 => [],
            FormatterOptions::ROW_LABELS => [],
            FormatterOptions::DEFAULT_FIELDS => [],
        ];
    }
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;

/**
 * Old name for PropertyList class.
 *
 * @deprecated
 */
class AssociativeList extends PropertyList
{

}
<?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\StructuredData\Xml\DomDataInterface;

class HelpDocument implements DomDataInterface
{
    /**
     * Convert data into a \DomDocument.
     *
     * @return \DomDocument
     */
    public function getDomData()
    {
    }
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;

use Consolidation\OutputFormatters\Options\FormatterOptions;

/**
 * Represents aribtrary array data (structured or unstructured) where the
 * data to display in --list format comes from the array keys.
 */
class ListDataFromKeys extends \ArrayObject implements ListDataInterface
{
    public function __construct($data)
    {
        parent::__construct($data);
    }

    public function getListData(FormatterOptions $options)
    {
        return array_keys($this->getArrayCopy());
    }
}
<?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;

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\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
{
    /**
     * 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;

interface RenderCellCollectionInterface extends RenderCellInterface
{
    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;

trait RenderCellCollectionTrait
{

    /** @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) {
            $cellData = $renderer->renderCell($key, $cellData, $options, $rowData);
            if (is_string($cellData)) {
                return $cellData;
            }
        }
        return $cellData;
    }
}
<?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;

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\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
{
    /**
     * 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;

interface TableDataInterface
{
    /**
     * Return the original data for this table.  Used by any
     * formatter that is -not- a table.
     */
    public function getOriginalData();

    /**
     * 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\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\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\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\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;

class PropertyListTableTransformation extends TableTransformation
{
    public function getOriginalData()
    {
        $data = $this->getArrayCopy();
        return $data[0];
    }
}
<?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;

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 (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;

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\Transformations;

use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;

class TableTransformation extends \ArrayObject implements TableDataInterface, OriginalDataInterface
{
    protected $headers;
    protected $rowLabels;
    protected $layout;

    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;
    }

    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()
    {
        return $this->getArrayCopy();
    }

    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\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\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\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\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[]
     */
    public abstract 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 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

use Consolidation\OutputFormatters\Transformations\WordWrapper;

include 'vendor/autoload.php';

$wrapper = new WordWrapper(78);

$data = [
    'name' => ['Name', ':', 'Rex', ],
    'species' => ['Species', ':', 'dog', ],
    'food' => ['Food', ':', 'kibble', ],
    'legs' => ['Legs', ':', '4', ],
    'description' => ['Description', ':', 'Rex is a very good dog, Brett. He likes kibble, and has four legs.', ],
];

$result = $wrapper->wrap($data);

var_export($result);

<?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;

/**
 * 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 $usesInputInterface;
    protected $usesOutputInterface;
    protected $returnType;

    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)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));
        $commandXML->setAttribute('id', $this->getName());
        $commandXML->setAttribute('name', $this->getName());

        // Get the original <command> element and its top-level elements.
        $originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command');
        $originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages');
        $originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description');
        $originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help');
        $originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments');
        $originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options');

        // Keep only the first of the <usage> elements
        $newUsagesXML = $dom->createElement('usages');
        $firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage');
        $newUsagesXML->appendChild($firstUsageXML);

        // Create our own <example> elements
        $newExamplesXML = $dom->createElement('examples');
        foreach ($this->examples 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 ($this->getAliases() as $alias) {
            $newAliasesXML->appendChild($dom->createElement('alias', $alias));
        }

        // Create our own <topic> elements
        $newTopicsXML = $dom->createElement('topics');
        foreach ($this->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 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;
    }

    protected function setCommandArguments($commandInfo)
    {
        $this->setUsesInputInterface($commandInfo);
        $this->setUsesOutputInterface($commandInfo);
        $this->setCommandArgumentsFromParameters($commandInfo);
        return $this;
    }

    /**
     * Check whether the first parameter is an InputInterface.
     */
    protected function checkUsesInputInterface($params)
    {
        /** @var \ReflectionParameter $firstParam */
        $firstParam = reset($params);
        return $firstParam && $firstParam->getClass() && $firstParam->getClass()->implementsInterface(
            '\\Symfony\\Component\\Console\\Input\\InputInterface'
        );
    }

    /**
     * Determine whether this command wants to get its inputs
     * via an InputInterface or via its command parameters
     */
    protected function setUsesInputInterface($commandInfo)
    {
        $params = $commandInfo->getParameters();
        $this->usesInputInterface = $this->checkUsesInputInterface($params);
        return $this;
    }

    /**
     * Determine whether this command wants to send its output directly
     * to the provided OutputInterface, or whether it will returned
     * structured output to be processed by the command processor.
     */
    protected function setUsesOutputInterface($commandInfo)
    {
        $params = $commandInfo->getParameters();
        $index = $this->checkUsesInputInterface($params) ? 1 : 0;
        $this->usesOutputInterface =
            (count($params) > $index) &&
            $params[$index]->getClass() &&
            $params[$index]->getClass()->implementsInterface(
                '\\Symfony\\Component\\Console\\Output\\OutputInterface'
            )
        ;
        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
        );

        $commandData->setUseIOInterfaces(
            $this->usesInputInterface,
            $this->usesOutputInterface
        );

        // 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;

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 = [];
        $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;

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());
    }
}
<?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;

/**
 * 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\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;

/**
 * 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;

/**
 * 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;

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 $usesInputInterface;
    /** var boolean */
    protected $usesOutputInterface;
    /** var boolean */
    protected $includeOptionsInArgs;
    /** var array */
    protected $specialDefaults = [];

    public function __construct(
        AnnotationData $annotationData,
        InputInterface $input,
        OutputInterface $output,
        $usesInputInterface = false,
        $usesOutputInterface = false
    ) {
        $this->annotationData = $annotationData;
        $this->input = $input;
        $this->output = $output;
        $this->usesInputInterface = false;
        $this->usesOutputInterface = false;
        $this->includeOptionsInArgs = true;
    }

    /**
     * For internal use only; indicates that the function to be called
     * should be passed an InputInterface &/or an OutputInterface.
     * @param booean $usesInputInterface
     * @param boolean $usesOutputInterface
     * @return self
     */
    public function setUseIOInterfaces($usesInputInterface, $usesOutputInterface)
    {
        $this->usesInputInterface = $usesInputInterface;
        $this->usesOutputInterface = $usesOutputInterface;
        return $this;
    }

    /**
     * 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 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.
        array_shift($args);

        if ($this->usesOutputInterface) {
            array_unshift($args, $this->output());
        }

        if ($this->usesInputInterface) {
            array_unshift($args, $this->input());
        }

        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;

/**
 * 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 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');
 */
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;

    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;
    }

    /**
     * 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 $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;

use Consolidation\AnnotatedCommand\Parser\CommandInfo;

interface CommandInfoAltererInterface
{
    public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
}
<?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 callable */
    protected $displayErrorFunction;
    /** var PrepareFormatterOptions[] */
    protected $prepareOptionsList = [];
    /** var boolean */
    protected $passExceptions;

    public function __construct(HookManager $hookManager)
    {
        $this->hookManager = $hookManager;
    }

    /**
     * Return the hook manager
     * @return HookManager
     */
    public function hookManager()
    {
        return $this->hookManager;
    }

    public function addPrepareFormatter(PrepareFormatter $preparer)
    {
        $this->prepareOptionsList[] = $preparer;
    }

    public function setFormatterManager(FormatterManager $formatterManager)
    {
        $this->formatterManager = $formatterManager;
        return $this;
    }

    public function setDisplayErrorFunction(callable $fn)
    {
        $this->displayErrorFunction = $fn;
        return $this;
    }

    /**
     * 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;
        }

        $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);
    }

    /**
     * Handle the result output and status code calculation.
     */
    public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
    {
        $statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
        $status = $statusCodeDispatcher->determineStatusCode($result);
        // 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
        $extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
        $structuredOutput = $extractDispatcher->extractOutput($result);
        $output = $this->chooseOutputStream($output, $status);
        if ($status != 0) {
            return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
        }
        if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
            return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
        }
        return $this->writeCommandOutput($output, $structuredOutput);
    }

    protected function dataCanBeFormatted($structuredOutput)
    {
        if (!isset($this->formatterManager)) {
            return false;
        }
        return
            is_object($structuredOutput) ||
            is_array($structuredOutput);
    }

    /**
     * Run the main command callback
     */
    protected function runCommandCallback($commandCallback, CommandData $commandData)
    {
        $result = false;
        try {
            $args = $commandData->getArgsAndOptions();
            $result = call_user_func_array($commandCallback, $args);
        } catch (\Exception $e) {
            $result = $this->commandErrorForException($e);
        }
        return $result;
    }

    /**
     * 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)
    {
        $formatterOptions = $this->createFormatterOptions($commandData);
        $format = $this->getFormat($formatterOptions);
        $this->formatterManager->write(
            $output,
            $format,
            $structuredOutput,
            $formatterOptions
        );
        return 0;
    }

    /**
     * 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);
        }
        return $formatterOptions;
    }

    /**
     * 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
    ) {
        // If there is no formatter, we will print strings,
        // but can do no more than that.
        if (is_string($structuredOutput)) {
            $output->writeln($structuredOutput);
        }
        return 0;
    }

    /**
     * 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\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\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;

/**
 * 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\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 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\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\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 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 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\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\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;

/**
 * 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;

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;

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\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\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;

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;

/**
 * A StatusDeterminer maps from a result to a status exit code.
 *
 * @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;

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\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;

/**
 * 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\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 = 3;

    /**
     * @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;

    /**
     * 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 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);
        }
        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;

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'])
            ;

        $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' => [],
            '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;

/**
 * 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),
        ];

        // 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());
        }

        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;

/**
 * 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\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' => 'processArgumentTag',
        '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 @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);
        $set->add($variableName, $description);
    }

    /**
     * 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;

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\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 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('#\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;

/**
 * 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
$loader = require_once __DIR__ . '/vendor/autoload.php';

$consoleColor = new JakubOnderka\PhpConsoleColor\ConsoleColor();

echo "Colors are supported: " . ($consoleColor->isSupported() ? 'Yes' : 'No') . "\n";
echo "256 colors are supported: " . ($consoleColor->are256ColorsSupported() ? 'Yes' : 'No') . "\n\n";

if ($consoleColor->isSupported()) {
    foreach ($consoleColor->getPossibleStyles() as $style) {
        echo $consoleColor->apply($style, $style) . "\n";
    }
}

echo "\n";

if ($consoleColor->are256ColorsSupported()) {
    echo "Foreground colors:\n";
    for ($i = 1; $i <= 255; $i++) {
        echo $consoleColor->apply("color_$i", str_pad($i, 6, ' ', STR_PAD_BOTH));

        if ($i % 15 === 0) {
            echo "\n";
        }
    }

    echo "\nBackground colors:\n";

    for ($i = 1; $i <= 255; $i++) {
        echo $consoleColor->apply("bg_color_$i", str_pad($i, 6, ' ', STR_PAD_BOTH));

        if ($i % 15 === 0) {
            echo "\n";
        }
    }

    echo "\n";
}
<?php
namespace JakubOnderka\PhpConsoleColor;

class ConsoleColor
{
    const FOREGROUND = 38,
        BACKGROUND = 48;

    const COLOR256_REGEXP = '~^(bg_)?color_([0-9]{1,3})$~';

    const RESET_STYLE = 0;

    /** @var bool */
    private $isSupported;

    /** @var bool */
    private $forceStyle = false;

    /** @var array */
    private $styles = array(
        'none' => null,
        'bold' => '1',
        'dark' => '2',
        'italic' => '3',
        'underline' => '4',
        'blink' => '5',
        'reverse' => '7',
        'concealed' => '8',

        'default' => '39',
        'black' => '30',
        'red' => '31',
        'green' => '32',
        'yellow' => '33',
        'blue' => '34',
        'magenta' => '35',
        'cyan' => '36',
        'light_gray' => '37',

        'dark_gray' => '90',
        'light_red' => '91',
        'light_green' => '92',
        'light_yellow' => '93',
        'light_blue' => '94',
        'light_magenta' => '95',
        'light_cyan' => '96',
        'white' => '97',

        'bg_default' => '49',
        'bg_black' => '40',
        'bg_red' => '41',
        'bg_green' => '42',
        'bg_yellow' => '43',
        'bg_blue' => '44',
        'bg_magenta' => '45',
        'bg_cyan' => '46',
        'bg_light_gray' => '47',

        'bg_dark_gray' => '100',
        'bg_light_red' => '101',
        'bg_light_green' => '102',
        'bg_light_yellow' => '103',
        'bg_light_blue' => '104',
        'bg_light_magenta' => '105',
        'bg_light_cyan' => '106',
        'bg_white' => '107',
    );

    /** @var array */
    private $themes = array();

    public function __construct()
    {
        $this->isSupported = $this->isSupported();
    }

    /**
     * @param string|array $style
     * @param string $text
     * @return string
     * @throws InvalidStyleException
     * @throws \InvalidArgumentException
     */
    public function apply($style, $text)
    {
        if (!$this->isStyleForced() && !$this->isSupported()) {
            return $text;
        }

        if (is_string($style)) {
            $style = array($style);
        }
        if (!is_array($style)) {
            throw new \InvalidArgumentException("Style must be string or array.");
        }

        $sequences = array();

        foreach ($style as $s) {
            if (isset($this->themes[$s])) {
                $sequences = array_merge($sequences, $this->themeSequence($s));
            } else if ($this->isValidStyle($s)) {
                $sequences[] = $this->styleSequence($s);
            } else {
                throw new InvalidStyleException($s);
            }
        }

        $sequences = array_filter($sequences, function ($val) {
            return $val !== null;
        });

        if (empty($sequences)) {
            return $text;
        }

        return $this->escSequence(implode(';', $sequences)) . $text . $this->escSequence(self::RESET_STYLE);
    }

    /**
     * @param bool $forceStyle
     */
    public function setForceStyle($forceStyle)
    {
        $this->forceStyle = (bool) $forceStyle;
    }

    /**
     * @return bool
     */
    public function isStyleForced()
    {
        return $this->forceStyle;
    }

    /**
     * @param array $themes
     * @throws InvalidStyleException
     * @throws \InvalidArgumentException
     */
    public function setThemes(array $themes)
    {
        $this->themes = array();
        foreach ($themes as $name => $styles) {
            $this->addTheme($name, $styles);
        }
    }

    /**
     * @param string $name
     * @param array|string $styles
     * @throws \InvalidArgumentException
     * @throws InvalidStyleException
     */
    public function addTheme($name, $styles)
    {
        if (is_string($styles)) {
            $styles = array($styles);
        }
        if (!is_array($styles)) {
            throw new \InvalidArgumentException("Style must be string or array.");
        }

        foreach ($styles as $style) {
            if (!$this->isValidStyle($style)) {
                throw new InvalidStyleException($style);
            }
        }

        $this->themes[$name] = $styles;
    }

    /**
     * @return array
     */
    public function getThemes()
    {
        return $this->themes;
    }

    /**
     * @param string $name
     * @return bool
     */
    public function hasTheme($name)
    {
        return isset($this->themes[$name]);
    }

    /**
     * @param string $name
     */
    public function removeTheme($name)
    {
        unset($this->themes[$name]);
    }

    /**
     * @return bool
     */
    public function isSupported()
    {
        if (DIRECTORY_SEPARATOR === '\\') {
            return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON';
        }

        return function_exists('posix_isatty') && @posix_isatty(STDOUT);
    }

    /**
     * @return bool
     */
    public function are256ColorsSupported()
    {
        return DIRECTORY_SEPARATOR === '/' && strpos(getenv('TERM'), '256color') !== false;
    }

    /**
     * @return array
     */
    public function getPossibleStyles()
    {
        return array_keys($this->styles);
    }

    /**
     * @param string $name
     * @return string
     * @throws InvalidStyleException
     */
    private function themeSequence($name)
    {
        $sequences = array();
        foreach ($this->themes[$name] as $style) {
            $sequences[] = $this->styleSequence($style);
        }
        return $sequences;
    }

    /**
     * @param string $style
     * @return string
     * @throws InvalidStyleException
     */
    private function styleSequence($style)
    {
        if (array_key_exists($style, $this->styles)) {
            return $this->styles[$style];
        }

        if (!$this->are256ColorsSupported()) {
            return null;
        }

        preg_match(self::COLOR256_REGEXP, $style, $matches);

        $type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
        $value = $matches[2];

        return "$type;5;$value";
    }

    /**
     * @param string $style
     * @return bool
     */
    private function isValidStyle($style)
    {
        return array_key_exists($style, $this->styles) || preg_match(self::COLOR256_REGEXP, $style);
    }

    /**
     * @param string|int $value
     * @return string
     */
    private function escSequence($value)
    {
        return "\033[{$value}m";
    }
}<?php
namespace JakubOnderka\PhpConsoleColor;

class InvalidStyleException extends \Exception
{
    public function __construct($styleName)
    {
        parent::__construct("Invalid style $styleName.");
    }
}<?php
use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;

require __DIR__ . '/../vendor/autoload.php';

$highlighter = new Highlighter(new ConsoleColor());

$fileContent = file_get_contents(__FILE__);
echo $highlighter->getCodeSnippet($fileContent, 3);<?php
use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;

require __DIR__ . '/../vendor/autoload.php';

$highlighter = new Highlighter(new ConsoleColor());

$fileContent = file_get_contents(__FILE__);
echo $highlighter->getWholeFile($fileContent);<?php
use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;

require __DIR__ . '/../vendor/autoload.php';

$highlighter = new Highlighter(new ConsoleColor());

$fileContent = file_get_contents(__FILE__);
echo $highlighter->getWholeFileWithLineNumbers($fileContent);<?php
namespace JakubOnderka\PhpConsoleHighlighter;

use JakubOnderka\PhpConsoleColor\ConsoleColor;

class Highlighter
{
    const TOKEN_DEFAULT = 'token_default',
        TOKEN_COMMENT = 'token_comment',
        TOKEN_STRING = 'token_string',
        TOKEN_HTML = 'token_html',
        TOKEN_KEYWORD = 'token_keyword';

    const ACTUAL_LINE_MARK = 'actual_line_mark',
        LINE_NUMBER = 'line_number';

    /** @var ConsoleColor */
    private $color;

    /** @var array */
    private $defaultTheme = array(
        self::TOKEN_STRING => 'red',
        self::TOKEN_COMMENT => 'yellow',
        self::TOKEN_KEYWORD => 'green',
        self::TOKEN_DEFAULT => 'default',
        self::TOKEN_HTML => 'cyan',

        self::ACTUAL_LINE_MARK  => 'red',
        self::LINE_NUMBER => 'dark_gray',
    );

    /**
     * @param ConsoleColor $color
     */
    public function __construct(ConsoleColor $color)
    {
        $this->color = $color;

        foreach ($this->defaultTheme as $name => $styles) {
            if (!$this->color->hasTheme($name)) {
                $this->color->addTheme($name, $styles);
            }
        }
    }

    /**
     * @param string $source
     * @param int $lineNumber
     * @param int $linesBefore
     * @param int $linesAfter
     * @return string
     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
     * @throws \InvalidArgumentException
     */
    public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2)
    {
        $tokenLines = $this->getHighlightedLines($source);

        $offset = $lineNumber - $linesBefore - 1;
        $offset = max($offset, 0);
        $length = $linesAfter + $linesBefore + 1;
        $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true);

        $lines = $this->colorLines($tokenLines);

        return $this->lineNumbers($lines, $lineNumber);
    }

    /**
     * @param string $source
     * @return string
     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
     * @throws \InvalidArgumentException
     */
    public function getWholeFile($source)
    {
        $tokenLines = $this->getHighlightedLines($source);
        $lines = $this->colorLines($tokenLines);
        return implode(PHP_EOL, $lines);
    }

    /**
     * @param string $source
     * @return string
     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
     * @throws \InvalidArgumentException
     */
    public function getWholeFileWithLineNumbers($source)
    {
        $tokenLines = $this->getHighlightedLines($source);
        $lines = $this->colorLines($tokenLines);
        return $this->lineNumbers($lines);
    }

    /**
     * @param string $source
     * @return array
     */
    private function getHighlightedLines($source)
    {
        $source = str_replace(array("\r\n", "\r"), "\n", $source);
        $tokens = $this->tokenize($source);
        return $this->splitToLines($tokens);
    }

    /**
     * @param string $source
     * @return array
     */
    private function tokenize($source)
    {
        $tokens = token_get_all($source);

        $output = array();
        $currentType = null;
        $buffer = '';

        foreach ($tokens as $token) {
            if (is_array($token)) {
                switch ($token[0]) {
                    case T_INLINE_HTML:
                        $newType = self::TOKEN_HTML;
                        break;

                    case T_COMMENT:
                    case T_DOC_COMMENT:
                        $newType = self::TOKEN_COMMENT;
                        break;

                    case T_ENCAPSED_AND_WHITESPACE:
                    case T_CONSTANT_ENCAPSED_STRING:
                        $newType = self::TOKEN_STRING;
                        break;

                    case T_WHITESPACE:
                        break;

                    case T_OPEN_TAG:
                    case T_OPEN_TAG_WITH_ECHO:
                    case T_CLOSE_TAG:
                    case T_STRING:
                    case T_VARIABLE:

                    // Constants
                    case T_DIR:
                    case T_FILE:
                    case T_METHOD_C:
                    case T_DNUMBER:
                    case T_LNUMBER:
                    case T_NS_C:
                    case T_LINE:
                    case T_CLASS_C:
                    case T_FUNC_C:
                    //case T_TRAIT_C:
                        $newType = self::TOKEN_DEFAULT;
                        break;

                    default:
                        // Compatibility with PHP 5.3
                        if (defined('T_TRAIT_C') && $token[0] === T_TRAIT_C) {
                            $newType = self::TOKEN_DEFAULT;
                        } else {
                            $newType = self::TOKEN_KEYWORD;
                        }
                }
            } else {
                $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
            }

            if ($currentType === null) {
                $currentType = $newType;
            }

            if ($currentType != $newType) {
                $output[] = array($currentType, $buffer);
                $buffer = '';
                $currentType = $newType;
            }

            $buffer .= is_array($token) ? $token[1] : $token;
        }

        if (isset($newType)) {
            $output[] = array($newType, $buffer);
        }

        return $output;
    }

    /**
     * @param array $tokens
     * @return array
     */
    private function splitToLines(array $tokens)
    {
        $lines = array();

        $line = array();
        foreach ($tokens as $token) {
            foreach (explode("\n", $token[1]) as $count => $tokenLine) {
                if ($count > 0) {
                    $lines[] = $line;
                    $line = array();
                }

                if ($tokenLine === '') {
                    continue;
                }

                $line[] = array($token[0], $tokenLine);
            }
        }

        $lines[] = $line;

        return $lines;
    }

    /**
     * @param array $tokenLines
     * @return array
     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
     * @throws \InvalidArgumentException
     */
    private function colorLines(array $tokenLines)
    {
        $lines = array();
        foreach ($tokenLines as $lineCount => $tokenLine) {
            $line = '';
            foreach ($tokenLine as $token) {
                list($tokenType, $tokenValue) = $token;
                if ($this->color->hasTheme($tokenType)) {
                    $line .= $this->color->apply($tokenType, $tokenValue);
                } else {
                    $line .= $tokenValue;
                }
            }
            $lines[$lineCount] = $line;
        }

        return $lines;
    }

    /**
     * @param array $lines
     * @param null|int $markLine
     * @return string
     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
     */
    private function lineNumbers(array $lines, $markLine = null)
    {
        end($lines);
        $lineStrlen = strlen(key($lines) + 1);

        $snippet = '';
        foreach ($lines as $i => $line) {
            if ($markLine !== null) {
                $snippet .= ($markLine === $i + 1 ? $this->color->apply(self::ACTUAL_LINE_MARK, '  > ') : '    ');
            }

            $snippet .= $this->color->apply(self::LINE_NUMBER, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ');
            $snippet .= $line . PHP_EOL;
        }

        return $snippet;
    }
}<?php
/**
 * Utility for printing tables from commandline scripts.
 *
 * PHP versions 5 and 7
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * o 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.
 * o The names of the authors may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * 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  Console
 * @package   Console_Table
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2002-2005 Richard Heyes
 * @copyright 2006-2008 Jan Schneider
 * @license   http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_Table
 */

define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
define('CONSOLE_TABLE_ALIGN_LEFT', -1);
define('CONSOLE_TABLE_ALIGN_CENTER', 0);
define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
define('CONSOLE_TABLE_BORDER_ASCII', -1);

/**
 * The main class.
 *
 * @category Console
 * @package  Console_Table
 * @author   Jan Schneider <jan@horde.org>
 * @license  http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
 * @link     http://pear.php.net/package/Console_Table
 */
class Console_Table
{
    /**
     * The table headers.
     *
     * @var array
     */
    var $_headers = array();

    /**
     * The data of the table.
     *
     * @var array
     */
    var $_data = array();

    /**
     * The maximum number of columns in a row.
     *
     * @var integer
     */
    var $_max_cols = 0;

    /**
     * The maximum number of rows in the table.
     *
     * @var integer
     */
    var $_max_rows = 0;

    /**
     * Lengths of the columns, calculated when rows are added to the table.
     *
     * @var array
     */
    var $_cell_lengths = array();

    /**
     * Heights of the rows.
     *
     * @var array
     */
    var $_row_heights = array();

    /**
     * How many spaces to use to pad the table.
     *
     * @var integer
     */
    var $_padding = 1;

    /**
     * Column filters.
     *
     * @var array
     */
    var $_filters = array();

    /**
     * Columns to calculate totals for.
     *
     * @var array
     */
    var $_calculateTotals;

    /**
     * Alignment of the columns.
     *
     * @var array
     */
    var $_col_align = array();

    /**
     * Default alignment of columns.
     *
     * @var integer
     */
    var $_defaultAlign;

    /**
     * Character set of the data.
     *
     * @var string
     */
    var $_charset = 'utf-8';

    /**
     * Border characters.
     * Allowed keys:
     * - intersection - intersection ("+")
     * - horizontal - horizontal rule character ("-")
     * - vertical - vertical rule character ("|")
     *
     * @var array
     */
    var $_border = array(
        'intersection' => '+',
        'horizontal' => '-',
        'vertical' => '|',
    );

    /**
     * If borders are shown or not
     * Allowed keys: top, right, bottom, left, inner: true and false
     *
     * @var array
     */
    var $_borderVisibility = array(
        'top'    => true,
        'right'  => true,
        'bottom' => true,
        'left'   => true,
        'inner'  => true
    );

    /**
     * Whether the data has ANSI colors.
     *
     * @var Console_Color2
     */
    var $_ansiColor = false;

    /**
     * Constructor.
     *
     * @param integer $align   Default alignment. One of
     *                         CONSOLE_TABLE_ALIGN_LEFT,
     *                         CONSOLE_TABLE_ALIGN_CENTER or
     *                         CONSOLE_TABLE_ALIGN_RIGHT.
     * @param string  $border  The character used for table borders or
     *                         CONSOLE_TABLE_BORDER_ASCII.
     * @param integer $padding How many spaces to use to pad the table.
     * @param string  $charset A charset supported by the mbstring PHP
     *                         extension.
     * @param boolean $color   Whether the data contains ansi color codes.
     */
    function __construct($align = CONSOLE_TABLE_ALIGN_LEFT,
                         $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1,
                         $charset = null, $color = false)
    {
        $this->_defaultAlign = $align;
        $this->setBorder($border);
        $this->_padding      = $padding;
        if ($color) {
            if (!class_exists('Console_Color2')) {
                include_once 'Console/Color2.php';
            }
            $this->_ansiColor = new Console_Color2();
        }
        if (!empty($charset)) {
            $this->setCharset($charset);
        }
    }

    /**
     * Converts an array to a table.
     *
     * @param array   $headers      Headers for the table.
     * @param array   $data         A two dimensional array with the table
     *                              data.
     * @param boolean $returnObject Whether to return the Console_Table object
     *                              instead of the rendered table.
     *
     * @static
     *
     * @return Console_Table|string  A Console_Table object or the generated
     *                               table.
     */
    function fromArray($headers, $data, $returnObject = false)
    {
        if (!is_array($headers) || !is_array($data)) {
            return false;
        }

        $table = new Console_Table();
        $table->setHeaders($headers);

        foreach ($data as $row) {
            $table->addRow($row);
        }

        return $returnObject ? $table : $table->getTable();
    }

    /**
     * Adds a filter to a column.
     *
     * Filters are standard PHP callbacks which are run on the data before
     * table generation is performed. Filters are applied in the order they
     * are added. The callback function must accept a single argument, which
     * is a single table cell.
     *
     * @param integer $col       Column to apply filter to.
     * @param mixed   &$callback PHP callback to apply.
     *
     * @return void
     */
    function addFilter($col, &$callback)
    {
        $this->_filters[] = array($col, &$callback);
    }

    /**
     * Sets the charset of the provided table data.
     *
     * @param string $charset A charset supported by the mbstring PHP
     *                        extension.
     *
     * @return void
     */
    function setCharset($charset)
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'en_US');
        $this->_charset = strtolower($charset);
        setlocale(LC_CTYPE, $locale);
    }

    /**
     * Set the table border settings
     *
     * Border definition modes:
     * - CONSOLE_TABLE_BORDER_ASCII: Default border with +, - and |
     * - array with keys "intersection", "horizontal" and "vertical"
     * - single character string that sets all three of the array keys
     *
     * @param mixed $border Border definition
     *
     * @return void
     * @see $_border
     */
    function setBorder($border)
    {
        if ($border === CONSOLE_TABLE_BORDER_ASCII) {
            $intersection = '+';
            $horizontal = '-';
            $vertical = '|';
        } else if (is_string($border)) {
            $intersection = $horizontal = $vertical = $border;
        } else if ($border == '') {
            $intersection = $horizontal = $vertical = '';
        } else {
            extract($border);
        }

        $this->_border = array(
            'intersection' => $intersection,
            'horizontal' => $horizontal,
            'vertical' => $vertical,
        );
    }

    /**
     * Set which borders shall be shown.
     *
     * @param array $visibility Visibility settings.
     *                          Allowed keys: left, right, top, bottom, inner
     *
     * @return void
     * @see    $_borderVisibility
     */
    function setBorderVisibility($visibility)
    {
        $this->_borderVisibility = array_merge(
            $this->_borderVisibility,
            array_intersect_key(
                $visibility,
                $this->_borderVisibility
            )
        );
    }

    /**
     * Sets the alignment for the columns.
     *
     * @param integer $col_id The column number.
     * @param integer $align  Alignment to set for this column. One of
     *                        CONSOLE_TABLE_ALIGN_LEFT
     *                        CONSOLE_TABLE_ALIGN_CENTER
     *                        CONSOLE_TABLE_ALIGN_RIGHT.
     *
     * @return void
     */
    function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
    {
        switch ($align) {
        case CONSOLE_TABLE_ALIGN_CENTER:
            $pad = STR_PAD_BOTH;
            break;
        case CONSOLE_TABLE_ALIGN_RIGHT:
            $pad = STR_PAD_LEFT;
            break;
        default:
            $pad = STR_PAD_RIGHT;
            break;
        }
        $this->_col_align[$col_id] = $pad;
    }

    /**
     * Specifies which columns are to have totals calculated for them and
     * added as a new row at the bottom.
     *
     * @param array $cols Array of column numbers (starting with 0).
     *
     * @return void
     */
    function calculateTotalsFor($cols)
    {
        $this->_calculateTotals = $cols;
    }

    /**
     * Sets the headers for the columns.
     *
     * @param array $headers The column headers.
     *
     * @return void
     */
    function setHeaders($headers)
    {
        $this->_headers = array(array_values($headers));
        $this->_updateRowsCols($headers);
    }

    /**
     * Adds a row to the table.
     *
     * @param array   $row    The row data to add.
     * @param boolean $append Whether to append or prepend the row.
     *
     * @return void
     */
    function addRow($row, $append = true)
    {
        if ($append) {
            $this->_data[] = array_values($row);
        } else {
            array_unshift($this->_data, array_values($row));
        }

        $this->_updateRowsCols($row);
    }

    /**
     * Inserts a row after a given row number in the table.
     *
     * If $row_id is not given it will prepend the row.
     *
     * @param array   $row    The data to insert.
     * @param integer $row_id Row number to insert before.
     *
     * @return void
     */
    function insertRow($row, $row_id = 0)
    {
        array_splice($this->_data, $row_id, 0, array($row));

        $this->_updateRowsCols($row);
    }

    /**
     * Adds a column to the table.
     *
     * @param array   $col_data The data of the column.
     * @param integer $col_id   The column index to populate.
     * @param integer $row_id   If starting row is not zero, specify it here.
     *
     * @return void
     */
    function addCol($col_data, $col_id = 0, $row_id = 0)
    {
        foreach ($col_data as $col_cell) {
            $this->_data[$row_id++][$col_id] = $col_cell;
        }

        $this->_updateRowsCols();
        $this->_max_cols = max($this->_max_cols, $col_id + 1);
    }

    /**
     * Adds data to the table.
     *
     * @param array   $data   A two dimensional array with the table data.
     * @param integer $col_id Starting column number.
     * @param integer $row_id Starting row number.
     *
     * @return void
     */
    function addData($data, $col_id = 0, $row_id = 0)
    {
        foreach ($data as $row) {
            if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
                $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE;
                $row_id++;
                continue;
            }
            $starting_col = $col_id;
            foreach ($row as $cell) {
                $this->_data[$row_id][$starting_col++] = $cell;
            }
            $this->_updateRowsCols();
            $this->_max_cols = max($this->_max_cols, $starting_col);
            $row_id++;
        }
    }

    /**
     * Adds a horizontal seperator to the table.
     *
     * @return void
     */
    function addSeparator()
    {
        $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
    }

    /**
     * Returns the generated table.
     *
     * @return string  The generated table.
     */
    function getTable()
    {
        $this->_applyFilters();
        $this->_calculateTotals();
        $this->_validateTable();

        return $this->_buildTable();
    }

    /**
     * Calculates totals for columns.
     *
     * @return void
     */
    function _calculateTotals()
    {
        if (empty($this->_calculateTotals)) {
            return;
        }

        $this->addSeparator();

        $totals = array();
        foreach ($this->_data as $row) {
            if (is_array($row)) {
                foreach ($this->_calculateTotals as $columnID) {
                    $totals[$columnID] += $row[$columnID];
                }
            }
        }

        $this->_data[] = $totals;
        $this->_updateRowsCols();
    }

    /**
     * Applies any column filters to the data.
     *
     * @return void
     */
    function _applyFilters()
    {
        if (empty($this->_filters)) {
            return;
        }

        foreach ($this->_filters as $filter) {
            $column   = $filter[0];
            $callback = $filter[1];

            foreach ($this->_data as $row_id => $row_data) {
                if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) {
                    $this->_data[$row_id][$column] =
                        call_user_func($callback, $row_data[$column]);
                }
            }
        }
    }

    /**
     * Ensures that column and row counts are correct.
     *
     * @return void
     */
    function _validateTable()
    {
        if (!empty($this->_headers)) {
            $this->_calculateRowHeight(-1, $this->_headers[0]);
        }

        for ($i = 0; $i < $this->_max_rows; $i++) {
            for ($j = 0; $j < $this->_max_cols; $j++) {
                if (!isset($this->_data[$i][$j]) &&
                    (!isset($this->_data[$i]) ||
                     $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) {
                    $this->_data[$i][$j] = '';
                }

            }
            $this->_calculateRowHeight($i, $this->_data[$i]);

            if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
                 ksort($this->_data[$i]);
            }

        }

        $this->_splitMultilineRows();

        // Update cell lengths.
        for ($i = 0; $i < count($this->_headers); $i++) {
            $this->_calculateCellLengths($this->_headers[$i]);
        }
        for ($i = 0; $i < $this->_max_rows; $i++) {
            $this->_calculateCellLengths($this->_data[$i]);
        }

        ksort($this->_data);
    }

    /**
     * Splits multiline rows into many smaller one-line rows.
     *
     * @return void
     */
    function _splitMultilineRows()
    {
        ksort($this->_data);
        $sections          = array(&$this->_headers, &$this->_data);
        $max_rows          = array(count($this->_headers), $this->_max_rows);
        $row_height_offset = array(-1, 0);

        for ($s = 0; $s <= 1; $s++) {
            $inserted = 0;
            $new_data = $sections[$s];

            for ($i = 0; $i < $max_rows[$s]; $i++) {
                // Process only rows that have many lines.
                $height = $this->_row_heights[$i + $row_height_offset[$s]];
                if ($height > 1) {
                    // Split column data into one-liners.
                    $split = array();
                    for ($j = 0; $j < $this->_max_cols; $j++) {
                        $split[$j] = preg_split('/\r?\n|\r/',
                                                $sections[$s][$i][$j]);
                    }

                    $new_rows = array();
                    // Construct new 'virtual' rows - insert empty strings for
                    // columns that have less lines that the highest one.
                    for ($i2 = 0; $i2 < $height; $i2++) {
                        for ($j = 0; $j < $this->_max_cols; $j++) {
                            $new_rows[$i2][$j] = !isset($split[$j][$i2])
                                ? ''
                                : $split[$j][$i2];
                        }
                    }

                    // Replace current row with smaller rows.  $inserted is
                    // used to take account of bigger array because of already
                    // inserted rows.
                    array_splice($new_data, $i + $inserted, 1, $new_rows);
                    $inserted += count($new_rows) - 1;
                }
            }

            // Has the data been modified?
            if ($inserted > 0) {
                $sections[$s] = $new_data;
                $this->_updateRowsCols();
            }
        }
    }

    /**
     * Builds the table.
     *
     * @return string  The generated table string.
     */
    function _buildTable()
    {
        if (!count($this->_data)) {
            return '';
        }

        $vertical = $this->_border['vertical'];
        $separator = $this->_getSeparator();

        $return = array();
        for ($i = 0; $i < count($this->_data); $i++) {
            if (is_array($this->_data[$i])) {
                for ($j = 0; $j < count($this->_data[$i]); $j++) {
                    if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE &&
                        $this->_strlen($this->_data[$i][$j]) <
                        $this->_cell_lengths[$j]) {
                        $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j],
                                                              $this->_cell_lengths[$j],
                                                              ' ',
                                                              $this->_col_align[$j]);
                    }
                }
            }

            if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
                $row_begin = $this->_borderVisibility['left']
                    ? $vertical . str_repeat(' ', $this->_padding)
                    : '';
                $row_end = $this->_borderVisibility['right']
                    ? str_repeat(' ', $this->_padding) . $vertical
                    : '';
                $implode_char = str_repeat(' ', $this->_padding) . $vertical
                    . str_repeat(' ', $this->_padding);
                $return[]     = $row_begin
                    . implode($implode_char, $this->_data[$i]) . $row_end;
            } elseif (!empty($separator)) {
                $return[] = $separator;
            }

        }

        $return = implode(PHP_EOL, $return);
        if (!empty($separator)) {
            if ($this->_borderVisibility['inner']) {
                $return = $separator . PHP_EOL . $return;
            }
            if ($this->_borderVisibility['bottom']) {
                $return .= PHP_EOL . $separator;
            }
        }
        $return .= PHP_EOL;

        if (!empty($this->_headers)) {
            $return = $this->_getHeaderLine() .  PHP_EOL . $return;
        }

        return $return;
    }

    /**
     * Creates a horizontal separator for header separation and table
     * start/end etc.
     *
     * @return string  The horizontal separator.
     */
    function _getSeparator()
    {
        if (!$this->_border) {
            return;
        }

        $horizontal = $this->_border['horizontal'];
        $intersection = $this->_border['intersection'];

        $return = array();
        foreach ($this->_cell_lengths as $cl) {
            $return[] = str_repeat($horizontal, $cl);
        }

        $row_begin = $this->_borderVisibility['left']
            ? $intersection . str_repeat($horizontal, $this->_padding)
            : '';
        $row_end = $this->_borderVisibility['right']
            ? str_repeat($horizontal, $this->_padding) . $intersection
            : '';
        $implode_char = str_repeat($horizontal, $this->_padding) . $intersection
            . str_repeat($horizontal, $this->_padding);

        return $row_begin . implode($implode_char, $return) . $row_end;
    }

    /**
     * Returns the header line for the table.
     *
     * @return string  The header line of the table.
     */
    function _getHeaderLine()
    {
        // Make sure column count is correct
        for ($j = 0; $j < count($this->_headers); $j++) {
            for ($i = 0; $i < $this->_max_cols; $i++) {
                if (!isset($this->_headers[$j][$i])) {
                    $this->_headers[$j][$i] = '';
                }
            }
        }

        for ($j = 0; $j < count($this->_headers); $j++) {
            for ($i = 0; $i < count($this->_headers[$j]); $i++) {
                if ($this->_strlen($this->_headers[$j][$i]) <
                    $this->_cell_lengths[$i]) {
                    $this->_headers[$j][$i] =
                        $this->_strpad($this->_headers[$j][$i],
                                       $this->_cell_lengths[$i],
                                       ' ',
                                       $this->_col_align[$i]);
                }
            }
        }

        $vertical = $this->_border['vertical'];
        $row_begin = $this->_borderVisibility['left']
            ? $vertical . str_repeat(' ', $this->_padding)
            : '';
        $row_end = $this->_borderVisibility['right']
            ? str_repeat(' ', $this->_padding) . $vertical
            : '';
        $implode_char = str_repeat(' ', $this->_padding) . $vertical
            . str_repeat(' ', $this->_padding);

        $separator = $this->_getSeparator();
        if (!empty($separator) && $this->_borderVisibility['top']) {
            $return[] = $separator;
        }
        for ($j = 0; $j < count($this->_headers); $j++) {
            $return[] = $row_begin
                . implode($implode_char, $this->_headers[$j]) . $row_end;
        }

        return implode(PHP_EOL, $return);
    }

    /**
     * Updates values for maximum columns and rows.
     *
     * @param array $rowdata Data array of a single row.
     *
     * @return void
     */
    function _updateRowsCols($rowdata = null)
    {
        // Update maximum columns.
        $this->_max_cols = max($this->_max_cols, is_array($rowdata) ? count($rowdata) : 0);

        // Update maximum rows.
        ksort($this->_data);
        $keys            = array_keys($this->_data);
        $this->_max_rows = end($keys) + 1;

        switch ($this->_defaultAlign) {
        case CONSOLE_TABLE_ALIGN_CENTER:
            $pad = STR_PAD_BOTH;
            break;
        case CONSOLE_TABLE_ALIGN_RIGHT:
            $pad = STR_PAD_LEFT;
            break;
        default:
            $pad = STR_PAD_RIGHT;
            break;
        }

        // Set default column alignments
        for ($i = 0; $i < $this->_max_cols; $i++) {
            if (!isset($this->_col_align[$i])) {
                $this->_col_align[$i] = $pad;
            }
        }
    }

    /**
     * Calculates the maximum length for each column of a row.
     *
     * @param array $row The row data.
     *
     * @return void
     */
    function _calculateCellLengths($row)
    {
        if (is_array($row)) {
            for ($i = 0; $i < count($row); $i++) {
               if (!isset($this->_cell_lengths[$i])) {
                    $this->_cell_lengths[$i] = 0;
                }
                $this->_cell_lengths[$i] = max($this->_cell_lengths[$i],
                                               $this->_strlen($row[$i]));
            }
        }
    }

    /**
     * Calculates the maximum height for all columns of a row.
     *
     * @param integer $row_number The row number.
     * @param array   $row        The row data.
     *
     * @return void
     */
    function _calculateRowHeight($row_number, $row)
    {
        if (!isset($this->_row_heights[$row_number])) {
            $this->_row_heights[$row_number] = 1;
        }

        // Do not process horizontal rule rows.
        if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
            return;
        }

        for ($i = 0, $c = count($row); $i < $c; ++$i) {
            $lines                           = preg_split('/\r?\n|\r/', $row[$i]);
            $this->_row_heights[$row_number] = max($this->_row_heights[$row_number],
                                                   count($lines));
        }
    }

    /**
     * Returns the character length of a string.
     *
     * @param string $str A multibyte or singlebyte string.
     *
     * @return integer  The string length.
     */
    function _strlen($str)
    {
        static $mbstring;

        // Strip ANSI color codes if requested.
        if ($this->_ansiColor) {
            $str = $this->_ansiColor->strip($str);
        }

        // Cache expensive function_exists() calls.
        if (!isset($mbstring)) {
            $mbstring = function_exists('mb_strwidth');
        }

        if ($mbstring) {
            return mb_strwidth($str, $this->_charset);
        }

        return strlen($str);
    }

    /**
     * Returns part of a string.
     *
     * @param string  $string The string to be converted.
     * @param integer $start  The part's start position, zero based.
     * @param integer $length The part's length.
     *
     * @return string  The string's part.
     */
    function _substr($string, $start, $length = null)
    {
        static $mbstring;

        // Cache expensive function_exists() calls.
        if (!isset($mbstring)) {
            $mbstring = function_exists('mb_substr');
        }

        if (is_null($length)) {
            $length = $this->_strlen($string);
        }
        if ($mbstring) {
            $ret = @mb_substr($string, $start, $length, $this->_charset);
            if (!empty($ret)) {
                return $ret;
            }
        }
        return substr($string, $start, $length);
    }

    /**
     * Returns a string padded to a certain length with another string.
     *
     * This method behaves exactly like str_pad but is multibyte safe.
     *
     * @param string  $input  The string to be padded.
     * @param integer $length The length of the resulting string.
     * @param string  $pad    The string to pad the input string with. Must
     *                        be in the same charset like the input string.
     * @param const   $type   The padding type. One of STR_PAD_LEFT,
     *                        STR_PAD_RIGHT, or STR_PAD_BOTH.
     *
     * @return string  The padded string.
     */
    function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT)
    {
        $mb_length  = $this->_strlen($input);
        $sb_length  = strlen($input);
        $pad_length = $this->_strlen($pad);

        /* Return if we already have the length. */
        if ($mb_length >= $length) {
            return $input;
        }

        /* Shortcut for single byte strings. */
        if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
            return str_pad($input, $length, $pad, $type);
        }

        switch ($type) {
        case STR_PAD_LEFT:
            $left   = $length - $mb_length;
            $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
                                     0, $left, $this->_charset) . $input;
            break;
        case STR_PAD_BOTH:
            $left   = floor(($length - $mb_length) / 2);
            $right  = ceil(($length - $mb_length) / 2);
            $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
                                     0, $left, $this->_charset) .
                $input .
                $this->_substr(str_repeat($pad, ceil($right / $pad_length)),
                               0, $right, $this->_charset);
            break;
        case STR_PAD_RIGHT:
            $right  = $length - $mb_length;
            $output = $input .
                $this->_substr(str_repeat($pad, ceil($right / $pad_length)),
                               0, $right, $this->_charset);
            break;
        }

        return $output;
    }

}
<?php

const GRAMMAR_FILE = './php5.y';

const LIB = '(?(DEFINE)
    (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    (?<string>(?&singleQuotedString)|(?&doubleQuotedString))
    (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';

const RULE_BLOCK = '(?<name>[a-z_]++):(?<rules>[^\'"/{};]*+(?:(?:(?&string)|(?&comment)|(?&code)|/|})[^\'"/{};]*+)*+);';

$usedTerminals = array_flip(array(
    'T_VARIABLE', 'T_STRING', 'T_INLINE_HTML', 'T_ENCAPSED_AND_WHITESPACE',
    'T_LNUMBER', 'T_DNUMBER', 'T_CONSTANT_ENCAPSED_STRING', 'T_STRING_VARNAME', 'T_NUM_STRING'
));
$unusedNonterminals = array_flip(array(
    'case_separator', 'optional_comma'
));

function regex($regex) {
    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
}

function magicSplit($regex, $string) {
    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);

    foreach ($pieces as &$piece) {
        $piece = trim($piece);
    }

    return array_filter($pieces);
}

echo '<pre>';

////////////////////
////////////////////
////////////////////

list($defs, $ruleBlocks) = magicSplit('%%', file_get_contents(GRAMMAR_FILE));

if ('' !== trim(preg_replace(regex(RULE_BLOCK), '', $ruleBlocks))) {
    die('Not all rule blocks were properly recognized!');
}

preg_match_all(regex(RULE_BLOCK), $ruleBlocks, $ruleBlocksMatches, PREG_SET_ORDER);
foreach ($ruleBlocksMatches as $match) {
    $ruleBlockName = $match['name'];
    $rules = magicSplit('\|', $match['rules']);

    foreach ($rules as &$rule) {
        $parts = magicSplit('\s+', $rule);
        $usedParts = array();

        foreach ($parts as $part) {
            if ('{' === $part[0]) {
                preg_match_all('~\$([0-9]+)~', $part, $backReferencesMatches, PREG_SET_ORDER);
                foreach ($backReferencesMatches as $match) {
                    $usedParts[$match[1]] = true;
                }
            }
        }

        $i = 1;
        foreach ($parts as &$part) {
            if ('/' === $part[0]) {
                continue;
            }

            if (isset($usedParts[$i])) {
                if ('\'' === $part[0] || '{' === $part[0]
                    || (ctype_upper($part[0]) && !isset($usedTerminals[$part]))
                    || (ctype_lower($part[0]) && isset($unusedNonterminals[$part]))
                ) {
                    $part = '<span style="background-color: red; color: white;">' . $part . '</span>';
                } else {
                    $part = '<strong><em>' . $part . '</em></strong>';
                }
            } elseif ((ctype_upper($part[0]) && isset($usedTerminals[$part]))
                      || (ctype_lower($part[0]) && !isset($unusedNonterminals[$part]))

            ) {
                $part = '<span style="background-color: blue; color: white;">' . $part . '</span>';
            }

            ++$i;
        }

        $rule = implode(' ', $parts);
    }

    echo $ruleBlockName, ':', "\n", '      ', implode("\n" . '    | ', $rules), "\n", ';', "\n\n";
}
<?php

$grammarFileToName = [
    __DIR__ . '/php5.y' => 'Php5',
    __DIR__ . '/php7.y' => 'Php7',
];

$tokensFile     = __DIR__ . '/tokens.y';
$tokensTemplate = __DIR__ . '/tokens.template';
$skeletonFile   = __DIR__ . '/parser.template';
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
$tmpResultFile  = __DIR__ . '/tmp_parser.php';
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
$tokensResultsFile = $resultDir . '/Tokens.php';

// check for kmyacc.exe binary in this directory, otherwise fall back to global name
$kmyacc = __DIR__ . '/kmyacc.exe';
if (!file_exists($kmyacc)) {
    $kmyacc = 'kmyacc';
}

$options = array_flip($argv);
$optionDebug = isset($options['--debug']);
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);

///////////////////////////////
/// Utility regex constants ///
///////////////////////////////

const LIB = '(?(DEFINE)
    (?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    (?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    (?<string>(?&singleQuotedString)|(?&doubleQuotedString))
    (?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    (?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';

const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
const ARGS   = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';

///////////////////
/// Main script ///
///////////////////

$tokens = file_get_contents($tokensFile);

foreach ($grammarFileToName as $grammarFile => $name) {
    echo "Building temporary $name grammar file.\n";

    $grammarCode = file_get_contents($grammarFile);
    $grammarCode = str_replace('%tokens', $tokens, $grammarCode);

    $grammarCode = resolveNodes($grammarCode);
    $grammarCode = resolveMacros($grammarCode);
    $grammarCode = resolveStackAccess($grammarCode);

    file_put_contents($tmpGrammarFile, $grammarCode);

    $additionalArgs = $optionDebug ? '-t -v' : '';

    echo "Building $name parser.\n";
    $output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile -p $name $tmpGrammarFile 2>&1"));
    echo "Output: \"$output\"\n";

    $resultCode = file_get_contents($tmpResultFile);
    $resultCode = removeTrailingWhitespace($resultCode);

    ensureDirExists($resultDir);
    file_put_contents("$resultDir/$name.php", $resultCode);
    unlink($tmpResultFile);

    echo "Building token definition.\n";
    $output = trim(shell_exec("$kmyacc -l -m $tokensTemplate $tmpGrammarFile 2>&1"));
    assert($output === '');
    rename($tmpResultFile, $tokensResultsFile);

    if (!$optionKeepTmpGrammar) {
        unlink($tmpGrammarFile);
    }
}

///////////////////////////////
/// Preprocessing functions ///
///////////////////////////////

function resolveNodes($code) {
    return preg_replace_callback(
        '~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
        function($matches) {
            // recurse
            $matches['params'] = resolveNodes($matches['params']);

            $params = magicSplit(
                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
                $matches['params']
            );

            $paramCode = '';
            foreach ($params as $param) {
                $paramCode .= $param . ', ';
            }

            return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
        },
        $code
    );
}

function resolveMacros($code) {
    return preg_replace_callback(
        '~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
        function($matches) {
            // recurse
            $matches['args'] = resolveMacros($matches['args']);

            $name = $matches['name'];
            $args = magicSplit(
                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
                $matches['args']
            );

            if ('attributes' == $name) {
                assertArgs(0, $args, $name);
                return '$this->startAttributeStack[#1] + $this->endAttributes';
            }

            if ('init' == $name) {
                return '$$ = array(' . implode(', ', $args) . ')';
            }

            if ('push' == $name) {
                assertArgs(2, $args, $name);

                return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
            }

            if ('pushNormalizing' == $name) {
                assertArgs(2, $args, $name);

                return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
                     . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
            }

            if ('toArray' == $name) {
                assertArgs(1, $args, $name);

                return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
            }

            if ('parseVar' == $name) {
                assertArgs(1, $args, $name);

                return 'substr(' . $args[0] . ', 1)';
            }

            if ('parseEncapsed' == $name) {
                assertArgs(3, $args, $name);

                return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
                     . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
            }

            if ('parseEncapsedDoc' == $name) {
                assertArgs(2, $args, $name);

                return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
                     . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, ' . $args[1] . '); } }'
                     . ' $s->value = preg_replace(\'~(\r\n|\n|\r)\z~\', \'\', $s->value);'
                     . ' if (\'\' === $s->value) array_pop(' . $args[0] . ');';
            }

            if ('makeNop' == $name) {
                assertArgs(2, $args, $name);

                return '$startAttributes = ' . $args[1] . ';'
                . ' if (isset($startAttributes[\'comments\']))'
                . ' { ' . $args[0] . ' = new Stmt\Nop([\'comments\' => $startAttributes[\'comments\']]); }'
                . ' else { ' . $args[0] . ' = null; }';
            }

            if ('strKind' == $name) {
                assertArgs(1, $args, $name);

                return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
                     . '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
                     . '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
            }

            if ('setDocStringAttrs' == $name) {
                assertArgs(2, $args, $name);

                return $args[0] . '[\'kind\'] = strpos(' . $args[1] . ', "\'") === false '
                     . '? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; '
                     . 'preg_match(\'/\A[bB]?<<<[ \t]*[\\\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\\\'"]?(?:\r\n|\n|\r)\z/\', ' . $args[1] . ', $matches); '
                     . $args[0] . '[\'docLabel\'] = $matches[1];';
            }

            if ('prependLeadingComments' == $name) {
                assertArgs(1, $args, $name);

                return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
                . 'if (!empty($attrs[\'comments\']) && isset($stmts[0])) {'
                . '$stmts[0]->setAttribute(\'comments\', '
                . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
            }

            return $matches[0];
        },
        $code
    );
}

function assertArgs($num, $args, $name) {
    if ($num != count($args)) {
        die('Wrong argument count for ' . $name . '().');
    }
}

function resolveStackAccess($code) {
    $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
    $code = preg_replace('/#(\d+)/', '$$1', $code);
    return $code;
}

function removeTrailingWhitespace($code) {
    $lines = explode("\n", $code);
    $lines = array_map('rtrim', $lines);
    return implode("\n", $lines);
}

function ensureDirExists($dir) {
    if (!is_dir($dir)) {
        mkdir($dir, 0777, true);
    }
}

//////////////////////////////
/// Regex helper functions ///
//////////////////////////////

function regex($regex) {
    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
}

function magicSplit($regex, $string) {
    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);

    foreach ($pieces as &$piece) {
        $piece = trim($piece);
    }

    if ($pieces === ['']) {
        return [];
    }

    return $pieces;
}
<?php

namespace PhpParser;

/**
 * @codeCoverageIgnore
 */
class Autoloader
{
    /** @var bool Whether the autoloader has been registered. */
    private static $registered = false;

    /**
     * Registers PhpParser\Autoloader as an SPL autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader instead of appending
     */
    static public function register($prepend = false) {
        if (self::$registered === true) {
            return;
        }

        spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend);
        self::$registered = true;
    }

    /**
     * Handles autoloading of classes.
     *
     * @param string $class A class name.
     */
    static public function autoload($class) {
        if (0 === strpos($class, 'PhpParser\\')) {
            $fileName = __DIR__ . strtr(substr($class, 9), '\\', '/') . '.php';
            if (file_exists($fileName)) {
                require $fileName;
            }
        }
    }
}
<?php

namespace PhpParser;

interface Builder
{
    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode();
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Class_ extends Declaration
{
    protected $name;

    protected $extends = null;
    protected $implements = array();
    protected $type = 0;

    protected $uses = array();
    protected $constants = array();
    protected $properties = array();
    protected $methods = array();

    /**
     * Creates a class builder.
     *
     * @param string $name Name of the class
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Extends a class.
     *
     * @param Name|string $class Name of class to extend
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function extend($class) {
        $this->extends = $this->normalizeName($class);

        return $this;
    }

    /**
     * Implements one or more interfaces.
     *
     * @param Name|string ...$interfaces Names of interfaces to implement
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function implement() {
        foreach (func_get_args() as $interface) {
            $this->implements[] = $this->normalizeName($interface);
        }

        return $this;
    }

    /**
     * Makes the class abstract.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeAbstract() {
        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);

        return $this;
    }

    /**
     * Makes the class final.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeFinal() {
        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        $targets = array(
            'Stmt_TraitUse'    => &$this->uses,
            'Stmt_ClassConst'  => &$this->constants,
            'Stmt_Property'    => &$this->properties,
            'Stmt_ClassMethod' => &$this->methods,
        );

        $type = $stmt->getType();
        if (!isset($targets[$type])) {
            throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
        }

        $targets[$type][] = $stmt;

        return $this;
    }

    /**
     * Returns the built class node.
     *
     * @return Stmt\Class_ The built class node
     */
    public function getNode() {
        return new Stmt\Class_($this->name, array(
            'type' => $this->type,
            'extends' => $this->extends,
            'implements' => $this->implements,
            'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
        ), $this->attributes);
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

abstract class Declaration extends PhpParser\BuilderAbstract
{
    protected $attributes = array();

    abstract public function addStmt($stmt);

    /**
     * Adds multiple statements.
     *
     * @param array $stmts The statements to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmts(array $stmts) {
        foreach ($stmts as $stmt) {
            $this->addStmt($stmt);
        }

        return $this;
    }

    /**
     * Sets doc comment for the declaration.
     *
     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDocComment($docComment) {
        $this->attributes['comments'] = array(
            $this->normalizeDocComment($docComment)
        );

        return $this;
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

abstract class FunctionLike extends Declaration
{
    protected $returnByRef = false;
    protected $params = array();
    protected $returnType = null;

    /**
     * Make the function return by reference.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeReturnByRef() {
        $this->returnByRef = true;

        return $this;
    }

    /**
     * Adds a parameter.
     *
     * @param Node\Param|Param $param The parameter to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addParam($param) {
        $param = $this->normalizeNode($param);

        if (!$param instanceof Node\Param) {
            throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
        }

        $this->params[] = $param;

        return $this;
    }

    /**
     * Adds multiple parameters.
     *
     * @param array $params The parameters to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addParams(array $params) {
        foreach ($params as $param) {
            $this->addParam($param);
        }

        return $this;
    }

    /**
     * Sets the return type for PHP 7.
     *
     * @param string|Node\Name $type One of array, callable, string, int, float, bool,
     *                               or a class/interface name.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setReturnType($type)
    {
        if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
            $this->returnType = $type;
        } else {
            $this->returnType = $this->normalizeName($type);
        }

        return $this;
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Function_ extends FunctionLike
{
    protected $name;
    protected $stmts = array();

    /**
     * Creates a function builder.
     *
     * @param string $name Name of the function
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Returns the built function node.
     *
     * @return Stmt\Function_ The built function node
     */
    public function getNode() {
        return new Stmt\Function_($this->name, array(
            'byRef'      => $this->returnByRef,
            'params'     => $this->params,
            'returnType' => $this->returnType,
            'stmts'      => $this->stmts,
        ), $this->attributes);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Interface_ extends Declaration
{
    protected $name;
    protected $extends = array();
    protected $constants = array();
    protected $methods = array();

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Extends one or more interfaces.
     *
     * @param Name|string ...$interfaces Names of interfaces to extend
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function extend() {
        foreach (func_get_args() as $interface) {
            $this->extends[] = $this->normalizeName($interface);
        }

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        $type = $stmt->getType();
        switch ($type) {
            case 'Stmt_ClassConst':
                $this->constants[] = $stmt;
                break;

            case 'Stmt_ClassMethod':
                // we erase all statements in the body of an interface method
                $stmt->stmts = null;
                $this->methods[] = $stmt;
                break;

            default:
                throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
        }

        return $this;
    }

    /**
     * Returns the built interface node.
     *
     * @return Stmt\Interface_ The built interface node
     */
    public function getNode() {
        return new Stmt\Interface_($this->name, array(
            'extends' => $this->extends,
            'stmts' => array_merge($this->constants, $this->methods),
        ), $this->attributes);
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Method extends FunctionLike
{
    protected $name;
    protected $type = 0;
    protected $stmts = array();

    /**
     * Creates a method builder.
     *
     * @param string $name Name of the method
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Makes the method public.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePublic() {
        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);

        return $this;
    }

    /**
     * Makes the method protected.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeProtected() {
        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);

        return $this;
    }

    /**
     * Makes the method private.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePrivate() {
        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);

        return $this;
    }

    /**
     * Makes the method static.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeStatic() {
        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);

        return $this;
    }

    /**
     * Makes the method abstract.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeAbstract() {
        if (!empty($this->stmts)) {
            throw new \LogicException('Cannot make method with statements abstract');
        }

        $this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
        $this->stmts = null; // abstract methods don't have statements

        return $this;
    }

    /**
     * Makes the method final.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeFinal() {
        $this->setModifier(Stmt\Class_::MODIFIER_FINAL);

        return $this;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        if (null === $this->stmts) {
            throw new \LogicException('Cannot add statements to an abstract method');
        }

        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Returns the built method node.
     *
     * @return Stmt\ClassMethod The built method node
     */
    public function getNode() {
        return new Stmt\ClassMethod($this->name, array(
            'type'       => $this->type,
            'byRef'      => $this->returnByRef,
            'params'     => $this->params,
            'returnType' => $this->returnType,
            'stmts'      => $this->stmts,
        ), $this->attributes);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;

class Namespace_ extends PhpParser\BuilderAbstract
{
    private $name;
    private $stmts = array();

    /**
     * Creates a namespace builder.
     *
     * @param Node\Name|string|null $name Name of the namespace
     */
    public function __construct($name) {
        $this->name = null !== $name ? $this->normalizeName($name) : null;
    }

    /**
     * Adds a statement.
     *
     * @param Node|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $this->stmts[] = $this->normalizeNode($stmt);

        return $this;
    }

    /**
     * Adds multiple statements.
     *
     * @param array $stmts The statements to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmts(array $stmts) {
        foreach ($stmts as $stmt) {
            $this->addStmt($stmt);
        }

        return $this;
    }

    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode() {
        return new Stmt\Namespace_($this->name, $this->stmts);
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node;

class Param extends PhpParser\BuilderAbstract
{
    protected $name;

    protected $default = null;
    protected $type = null;
    protected $byRef = false;

    /**
     * Creates a parameter builder.
     *
     * @param string $name Name of the parameter
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Sets default value for the parameter.
     *
     * @param mixed $value Default value to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDefault($value) {
        $this->default = $this->normalizeValue($value);

        return $this;
    }

    /**
     * Sets type hint for the parameter.
     *
     * @param string|Node\Name $type Type hint to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setTypeHint($type) {
        if (in_array($type, array('array', 'callable', 'string', 'int', 'float', 'bool'))) {
            $this->type = $type;
        } else {
            $this->type = $this->normalizeName($type);
        }

        return $this;
    }

    /**
     * Make the parameter accept the value by reference.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeByRef() {
        $this->byRef = true;

        return $this;
    }

    /**
     * Returns the built parameter node.
     *
     * @return Node\Param The built parameter node
     */
    public function getNode() {
        return new Node\Param(
            $this->name, $this->default, $this->type, $this->byRef
        );
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Stmt;

class Property extends PhpParser\BuilderAbstract
{
    protected $name;

    protected $type = 0;
    protected $default = null;
    protected $attributes = array();

    /**
     * Creates a property builder.
     *
     * @param string $name Name of the property
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Makes the property public.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePublic() {
        $this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);

        return $this;
    }

    /**
     * Makes the property protected.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeProtected() {
        $this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);

        return $this;
    }

    /**
     * Makes the property private.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makePrivate() {
        $this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);

        return $this;
    }

    /**
     * Makes the property static.
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function makeStatic() {
        $this->setModifier(Stmt\Class_::MODIFIER_STATIC);

        return $this;
    }

    /**
     * Sets default value for the property.
     *
     * @param mixed $value Default value to use
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDefault($value) {
        $this->default = $this->normalizeValue($value);

        return $this;
    }

    /**
     * Sets doc comment for the property.
     *
     * @param PhpParser\Comment\Doc|string $docComment Doc comment to set
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function setDocComment($docComment) {
        $this->attributes = array(
            'comments' => array($this->normalizeDocComment($docComment))
        );

        return $this;
    }

    /**
     * Returns the built class node.
     *
     * @return Stmt\Property The built property node
     */
    public function getNode() {
        return new Stmt\Property(
            $this->type !== 0 ? $this->type : Stmt\Class_::MODIFIER_PUBLIC,
            array(
                new Stmt\PropertyProperty($this->name, $this->default)
            ),
            $this->attributes
        );
    }
}<?php

namespace PhpParser\Builder;

use PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;

class Trait_ extends Declaration
{
    protected $name;
    protected $properties = array();
    protected $methods = array();

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     */
    public function __construct($name) {
        $this->name = $name;
    }

    /**
     * Adds a statement.
     *
     * @param Stmt|PhpParser\Builder $stmt The statement to add
     *
     * @return $this The builder instance (for fluid interface)
     */
    public function addStmt($stmt) {
        $stmt = $this->normalizeNode($stmt);

        if ($stmt instanceof Stmt\Property) {
            $this->properties[] = $stmt;
        } else if ($stmt instanceof Stmt\ClassMethod) {
            $this->methods[] = $stmt;
        } else {
            throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
        }

        return $this;
    }

    /**
     * Returns the built trait node.
     *
     * @return Stmt\Trait_ The built interface node
     */
    public function getNode() {
        return new Stmt\Trait_(
            $this->name, array_merge($this->properties, $this->methods), $this->attributes
        );
    }
}
<?php

namespace PhpParser\Builder;

use PhpParser\BuilderAbstract;
use PhpParser\Node;
use PhpParser\Node\Stmt;

/**
 * @method $this as(string $alias) Sets alias for used name.
 */
class Use_ extends BuilderAbstract {
    protected $name;
    protected $type;
    protected $alias = null;

    /**
     * Creates a name use (alias) builder.
     *
     * @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
     * @param int              $type One of the Stmt\Use_::TYPE_* constants
     */
    public function __construct($name, $type) {
        $this->name = $this->normalizeName($name);
        $this->type = $type;
    }

    /**
     * Sets alias for used name.
     *
     * @param string $alias Alias to use (last component of full name by default)
     *
     * @return $this The builder instance (for fluid interface)
     */
    protected function as_($alias) {
        $this->alias = $alias;
        return $this;
    }
    public function __call($name, $args) {
        if (method_exists($this, $name . '_')) {
            return call_user_func_array(array($this, $name . '_'), $args);
        }

        throw new \LogicException(sprintf('Method "%s" does not exist', $name));
    }

    /**
     * Returns the built node.
     *
     * @return Node The built node
     */
    public function getNode() {
        $alias = null !== $this->alias ? $this->alias : $this->name->getLast();
        return new Stmt\Use_(array(
            new Stmt\UseUse($this->name, $alias)
        ), $this->type);
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Scalar;
use PhpParser\Comment;

abstract class BuilderAbstract implements Builder {
    /**
     * Normalizes a node: Converts builder objects to nodes.
     *
     * @param Node|Builder $node The node to normalize
     *
     * @return Node The normalized node
     */
    protected function normalizeNode($node) {
        if ($node instanceof Builder) {
            return $node->getNode();
        } elseif ($node instanceof Node) {
            return $node;
        }

        throw new \LogicException('Expected node or builder object');
    }

    /**
     * Normalizes a name: Converts plain string names to PhpParser\Node\Name.
     *
     * @param Name|string $name The name to normalize
     *
     * @return Name The normalized name
     */
    protected function normalizeName($name) {
        if ($name instanceof Name) {
            return $name;
        } elseif (is_string($name)) {
            if (!$name) {
                throw new \LogicException('Name cannot be empty');
            }

            if ($name[0] == '\\') {
                return new Name\FullyQualified(substr($name, 1));
            } elseif (0 === strpos($name, 'namespace\\')) {
                return new Name\Relative(substr($name, strlen('namespace\\')));
            } else {
                return new Name($name);
            }
        }

        throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
    }

    /**
     * Normalizes a value: Converts nulls, booleans, integers,
     * floats, strings and arrays into their respective nodes
     *
     * @param mixed $value The value to normalize
     *
     * @return Expr The normalized value
     */
    protected function normalizeValue($value) {
        if ($value instanceof Node) {
            return $value;
        } elseif (is_null($value)) {
            return new Expr\ConstFetch(
                new Name('null')
            );
        } elseif (is_bool($value)) {
            return new Expr\ConstFetch(
                new Name($value ? 'true' : 'false')
            );
        } elseif (is_int($value)) {
            return new Scalar\LNumber($value);
        } elseif (is_float($value)) {
            return new Scalar\DNumber($value);
        } elseif (is_string($value)) {
            return new Scalar\String_($value);
        } elseif (is_array($value)) {
            $items = array();
            $lastKey = -1;
            foreach ($value as $itemKey => $itemValue) {
                // for consecutive, numeric keys don't generate keys
                if (null !== $lastKey && ++$lastKey === $itemKey) {
                    $items[] = new Expr\ArrayItem(
                        $this->normalizeValue($itemValue)
                    );
                } else {
                    $lastKey = null;
                    $items[] = new Expr\ArrayItem(
                        $this->normalizeValue($itemValue),
                        $this->normalizeValue($itemKey)
                    );
                }
            }

            return new Expr\Array_($items);
        } else {
            throw new \LogicException('Invalid value');
        }
    }

    /**
     * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
     *
     * @param Comment\Doc|string $docComment The doc comment to normalize
     *
     * @return Comment\Doc The normalized doc comment
     */
    protected function normalizeDocComment($docComment) {
        if ($docComment instanceof Comment\Doc) {
            return $docComment;
        } else if (is_string($docComment)) {
            return new Comment\Doc($docComment);
        } else {
            throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
        }
    }

    /**
     * Sets a modifier in the $this->type property.
     *
     * @param int $modifier Modifier to set
     */
    protected function setModifier($modifier) {
        Stmt\Class_::verifyModifier($this->type, $modifier);
        $this->type |= $modifier;
    }
}
<?php

namespace PhpParser;

use PhpParser\Builder;
use PhpParser\Node\Stmt\Use_;

/**
 * The following methods use reserved keywords, so their implementation is defined with an underscore and made available
 * with the reserved name through __call() magic.
 *
 * @method Builder\Namespace_ namespace(string $name) Creates a namespace builder.
 * @method Builder\Class_     class(string $name)     Creates a class builder.
 * @method Builder\Interface_ interface(string $name) Creates an interface builder.
 * @method Builder\Trait_     trait(string $name)     Creates a trait builder.
 * @method Builder\Function_  function(string $name)  Creates a function builder.
 * @method Builder\Use_       use(string $name)       Creates a namespace/class use builder.
 */
class BuilderFactory
{
    /**
     * Creates a namespace builder.
     *
     * @param null|string|Node\Name $name Name of the namespace
     *
     * @return Builder\Namespace_ The created namespace builder
     */
    protected function _namespace($name) {
        return new Builder\Namespace_($name);
    }

    /**
     * Creates a class builder.
     *
     * @param string $name Name of the class
     *
     * @return Builder\Class_ The created class builder
     */
    protected function _class($name) {
        return new Builder\Class_($name);
    }

    /**
     * Creates an interface builder.
     *
     * @param string $name Name of the interface
     *
     * @return Builder\Interface_ The created interface builder
     */
    protected function _interface($name) {
        return new Builder\Interface_($name);
    }

    /**
     * Creates a trait builder.
     *
     * @param string $name Name of the trait
     *
     * @return Builder\Trait_ The created trait builder
     */
    protected function _trait($name) {
        return new Builder\Trait_($name);
    }

    /**
     * Creates a method builder.
     *
     * @param string $name Name of the method
     *
     * @return Builder\Method The created method builder
     */
    public function method($name) {
        return new Builder\Method($name);
    }

    /**
     * Creates a parameter builder.
     *
     * @param string $name Name of the parameter
     *
     * @return Builder\Param The created parameter builder
     */
    public function param($name) {
        return new Builder\Param($name);
    }

    /**
     * Creates a property builder.
     *
     * @param string $name Name of the property
     *
     * @return Builder\Property The created property builder
     */
    public function property($name) {
        return new Builder\Property($name);
    }

    /**
     * Creates a function builder.
     *
     * @param string $name Name of the function
     *
     * @return Builder\Function_ The created function builder
     */
    protected function _function($name) {
        return new Builder\Function_($name);
    }

    /**
     * Creates a namespace/class use builder.
     *
     * @param string|Node\Name Name to alias
     *
     * @return Builder\Use_ The create use builder
     */
    protected function _use($name) {
        return new Builder\Use_($name, Use_::TYPE_NORMAL);
    }

    public function __call($name, array $args) {
        if (method_exists($this, '_' . $name)) {
            return call_user_func_array(array($this, '_' . $name), $args);
        }

        throw new \LogicException(sprintf('Method "%s" does not exist', $name));
    }
}
<?php

namespace PhpParser;

class Comment
{
    protected $text;
    protected $line;
    protected $filePos;

    /**
     * Constructs a comment node.
     *
     * @param string $text         Comment text (including comment delimiters like /*)
     * @param int    $startLine    Line number the comment started on
     * @param int    $startFilePos File offset the comment started on
     */
    public function __construct($text, $startLine = -1, $startFilePos = -1) {
        $this->text = $text;
        $this->line = $startLine;
        $this->filePos = $startFilePos;
    }

    /**
     * Gets the comment text.
     *
     * @return string The comment text (including comment delimiters like /*)
     */
    public function getText() {
        return $this->text;
    }

    /**
     * Sets the comment text.
     *
     * @param string $text The comment text (including comment delimiters like /*)
     *
     * @deprecated Construct a new comment instead
     */
    public function setText($text) {
        $this->text = $text;
    }

    /**
     * Gets the line number the comment started on.
     *
     * @return int Line number
     */
    public function getLine() {
        return $this->line;
    }

    /**
     * Sets the line number the comment started on.
     *
     * @param int $line Line number
     *
     * @deprecated Construct a new comment instead
     */
    public function setLine($line) {
        $this->line = $line;
    }

    /**
     * Gets the file offset the comment started on.
     *
     * @return int File offset
     */
    public function getFilePos() {
        return $this->filePos;
    }

    /**
     * Gets the comment text.
     *
     * @return string The comment text (including comment delimiters like /*)
     */
    public function __toString() {
        return $this->text;
    }

    /**
     * Gets the reformatted comment text.
     *
     * "Reformatted" here means that we try to clean up the whitespace at the
     * starts of the lines. This is necessary because we receive the comments
     * without trailing whitespace on the first line, but with trailing whitespace
     * on all subsequent lines.
     *
     * @return mixed|string
     */
    public function getReformattedText() {
        $text = trim($this->text);
        $newlinePos = strpos($text, "\n");
        if (false === $newlinePos) {
            // Single line comments don't need further processing
            return $text;
        } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
            // Multi line comment of the type
            //
            //     /*
            //      * Some text.
            //      * Some more text.
            //      */
            //
            // is handled by replacing the whitespace sequences before the * by a single space
            return preg_replace('(^\s+\*)m', ' *', $this->text);
        } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
            // Multi line comment of the type
            //
            //    /*
            //        Some text.
            //        Some more text.
            //    */
            //
            // is handled by removing the whitespace sequence on the line before the closing
            // */ on all lines. So if the last line is "    */", then "    " is removed at the
            // start of all lines.
            return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
        } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
            // Multi line comment of the type
            //
            //     /* Some text.
            //        Some more text.
            //          Indented text.
            //        Even more text. */
            //
            // is handled by removing the difference between the shortest whitespace prefix on all
            // lines and the length of the "/* " opening sequence.
            $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
            $removeLen = $prefixLen - strlen($matches[0]);
            return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
        }

        // No idea how to format this comment, so simply return as is
        return $text;
    }

    private function getShortestWhitespacePrefixLen($str) {
        $lines = explode("\n", $str);
        $shortestPrefixLen = INF;
        foreach ($lines as $line) {
            preg_match('(^\s*)', $line, $matches);
            $prefixLen = strlen($matches[0]);
            if ($prefixLen < $shortestPrefixLen) {
                $shortestPrefixLen = $prefixLen;
            }
        }
        return $shortestPrefixLen;
    }
}<?php

namespace PhpParser\Comment;

class Doc extends \PhpParser\Comment
{
}<?php

namespace PhpParser;

class Error extends \RuntimeException
{
    protected $rawMessage;
    protected $attributes;

    /**
     * Creates an Exception signifying a parse error.
     *
     * @param string    $message    Error message
     * @param array|int $attributes Attributes of node/token where error occurred
     *                              (or start line of error -- deprecated)
     */
    public function __construct($message, $attributes = array()) {
        $this->rawMessage = (string) $message;
        if (is_array($attributes)) {
            $this->attributes = $attributes;
        } else {
            $this->attributes = array('startLine' => $attributes);
        }
        $this->updateMessage();
    }

    /**
     * Gets the error message
     *
     * @return string Error message
     */
    public function getRawMessage() {
        return $this->rawMessage;
    }

    /**
     * Gets the line the error starts in.
     *
     * @return int Error start line
     */
    public function getStartLine() {
        return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1;
    }

    /**
     * Gets the line the error ends in.
     *
     * @return int Error end line
     */
    public function getEndLine() {
        return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1;
    }


    /**
     * Gets the attributes of the node/token the error occurred at.
     *
     * @return array
     */
    public function getAttributes() {
        return $this->attributes;
    }

    /**
     * Sets the line of the PHP file the error occurred in.
     *
     * @param string $message Error message
     */
    public function setRawMessage($message) {
        $this->rawMessage = (string) $message;
        $this->updateMessage();
    }

    /**
     * Sets the line the error starts in.
     *
     * @param int $line Error start line
     */
    public function setStartLine($line) {
        $this->attributes['startLine'] = (int) $line;
        $this->updateMessage();
    }

    /**
     * Returns whether the error has start and end column information.
     *
     * For column information enable the startFilePos and endFilePos in the lexer options.
     *
     * @return bool
     */
    public function hasColumnInfo() {
        return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']);
    }

    /**
     * Gets the start column (1-based) into the line where the error started.
     *
     * @param string $code Source code of the file
     * @return int
     */
    public function getStartColumn($code) {
        if (!$this->hasColumnInfo()) {
            throw new \RuntimeException('Error does not have column information');
        }

        return $this->toColumn($code, $this->attributes['startFilePos']);
    }

    /**
     * Gets the end column (1-based) into the line where the error ended.
     *
     * @param string $code Source code of the file
     * @return int
     */
    public function getEndColumn($code) {
        if (!$this->hasColumnInfo()) {
            throw new \RuntimeException('Error does not have column information');
        }

        return $this->toColumn($code, $this->attributes['endFilePos']);
    }

    private function toColumn($code, $pos) {
        if ($pos > strlen($code)) {
            throw new \RuntimeException('Invalid position information');
        }

        $lineStartPos = strrpos($code, "\n", $pos - strlen($code));
        if (false === $lineStartPos) {
            $lineStartPos = -1;
        }

        return $pos - $lineStartPos;
    }

    /**
     * Updates the exception message after a change to rawMessage or rawLine.
     */
    protected function updateMessage() {
        $this->message = $this->rawMessage;

        if (-1 === $this->getStartLine()) {
            $this->message .= ' on unknown line';
        } else {
            $this->message .= ' on line ' . $this->getStartLine();
        }
    }

    /** @deprecated Use getStartLine() instead */
    public function getRawLine() {
        return $this->getStartLine();
    }

    /** @deprecated Use setStartLine() instead */
    public function setRawLine($line) {
        $this->setStartLine($line);
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Scalar\LNumber;
use PhpParser\Parser\Tokens;

class Lexer
{
    protected $code;
    protected $tokens;
    protected $pos;
    protected $line;
    protected $filePos;

    protected $tokenMap;
    protected $dropTokens;

    protected $usedAttributes;

    /**
     * Creates a Lexer.
     *
     * @param array $options Options array. Currently only the 'usedAttributes' option is supported,
     *                       which is an array of attributes to add to the AST nodes. Possible
     *                       attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
     *                       'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
     *                       first three. For more info see getNextToken() docs.
     */
    public function __construct(array $options = array()) {
        // map from internal tokens to PhpParser tokens
        $this->tokenMap = $this->createTokenMap();

        // map of tokens to drop while lexing (the map is only used for isset lookup,
        // that's why the value is simply set to 1; the value is never actually used.)
        $this->dropTokens = array_fill_keys(
            array(T_WHITESPACE, T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT), 1
        );

        // the usedAttributes member is a map of the used attribute names to a dummy
        // value (here "true")
        $options += array(
            'usedAttributes' => array('comments', 'startLine', 'endLine'),
        );
        $this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
    }

    /**
     * Initializes the lexer for lexing the provided source code.
     *
     * @param string $code The source code to lex
     *
     * @throws Error on lexing errors (unterminated comment or unexpected character)
     */
    public function startLexing($code) {
        $scream = ini_set('xdebug.scream', '0');

        $this->resetErrors();
        $this->tokens = @token_get_all($code);
        $this->handleErrors();

        if (false !== $scream) {
            ini_set('xdebug.scream', $scream);
        }

        $this->code = $code; // keep the code around for __halt_compiler() handling
        $this->pos  = -1;
        $this->line =  1;
        $this->filePos = 0;
    }

    protected function resetErrors() {
        if (function_exists('error_clear_last')) {
            error_clear_last();
        } else {
            // set error_get_last() to defined state by forcing an undefined variable error
            set_error_handler(function() { return false; }, 0);
            @$undefinedVariable;
            restore_error_handler();
        }
    }

    protected function handleErrors() {
        $error = error_get_last();
        if (null === $error) {
            return;
        }

        if (preg_match(
            '~^Unterminated comment starting line ([0-9]+)$~',
            $error['message'], $matches
        )) {
            throw new Error('Unterminated comment', (int) $matches[1]);
        }

        if (preg_match(
            '~^Unexpected character in input:  \'(.)\' \(ASCII=([0-9]+)\)~s',
            $error['message'], $matches
        )) {
            throw new Error(sprintf(
                'Unexpected character "%s" (ASCII %d)',
                $matches[1], $matches[2]
            ));
        }

        // PHP cuts error message after null byte, so need special case
        if (preg_match('~^Unexpected character in input:  \'$~', $error['message'])) {
            throw new Error('Unexpected null byte');
        }
    }

    /**
     * Fetches the next token.
     *
     * The available attributes are determined by the 'usedAttributes' option, which can
     * be specified in the constructor. The following attributes are supported:
     *
     *  * 'comments'      => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
     *                       representing all comments that occurred between the previous
     *                       non-discarded token and the current one.
     *  * 'startLine'     => Line in which the node starts.
     *  * 'endLine'       => Line in which the node ends.
     *  * 'startTokenPos' => Offset into the token array of the first token in the node.
     *  * 'endTokenPos'   => Offset into the token array of the last token in the node.
     *  * 'startFilePos'  => Offset into the code string of the first character that is part of the node.
     *  * 'endFilePos'    => Offset into the code string of the last character that is part of the node.
     *
     * @param mixed $value           Variable to store token content in
     * @param mixed $startAttributes Variable to store start attributes in
     * @param mixed $endAttributes   Variable to store end attributes in
     *
     * @return int Token id
     */
    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $startAttributes = array();
        $endAttributes   = array();

        while (1) {
            if (isset($this->tokens[++$this->pos])) {
                $token = $this->tokens[$this->pos];
            } else {
                // EOF token with ID 0
                $token = "\0";
            }

            if (isset($this->usedAttributes['startLine'])) {
                $startAttributes['startLine'] = $this->line;
            }
            if (isset($this->usedAttributes['startTokenPos'])) {
                $startAttributes['startTokenPos'] = $this->pos;
            }
            if (isset($this->usedAttributes['startFilePos'])) {
                $startAttributes['startFilePos'] = $this->filePos;
            }

            if (\is_string($token)) {
                $value = $token;
                if (isset($token[1])) {
                    // bug in token_get_all
                    $this->filePos += 2;
                    $id = ord('"');
                } else {
                    $this->filePos += 1;
                    $id = ord($token);
                }
            } elseif (!isset($this->dropTokens[$token[0]])) {
                $value = $token[1];
                $id = $this->tokenMap[$token[0]];

                $this->line += substr_count($value, "\n");
                $this->filePos += \strlen($value);
            } else {
                if (T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0]) {
                    if (isset($this->usedAttributes['comments'])) {
                        $comment = T_DOC_COMMENT === $token[0]
                            ? new Comment\Doc($token[1], $this->line, $this->filePos)
                            : new Comment($token[1], $this->line, $this->filePos);
                        $startAttributes['comments'][] = $comment;
                    }
                }

                $this->line += substr_count($token[1], "\n");
                $this->filePos += \strlen($token[1]);
                continue;
            }

            if (isset($this->usedAttributes['endLine'])) {
                $endAttributes['endLine'] = $this->line;
            }
            if (isset($this->usedAttributes['endTokenPos'])) {
                $endAttributes['endTokenPos'] = $this->pos;
            }
            if (isset($this->usedAttributes['endFilePos'])) {
                $endAttributes['endFilePos'] = $this->filePos - 1;
            }

            return $id;
        }

        throw new \RuntimeException('Reached end of lexer loop');
    }

    /**
     * Returns the token array for current code.
     *
     * The token array is in the same format as provided by the
     * token_get_all() function and does not discard tokens (i.e.
     * whitespace and comments are included). The token position
     * attributes are against this token array.
     *
     * @return array Array of tokens in token_get_all() format
     */
    public function getTokens() {
        return $this->tokens;
    }

    /**
     * Handles __halt_compiler() by returning the text after it.
     *
     * @return string Remaining text
     */
    public function handleHaltCompiler() {
        // text after T_HALT_COMPILER, still including ();
        $textAfter = substr($this->code, $this->filePos);

        // ensure that it is followed by ();
        // this simplifies the situation, by not allowing any comments
        // in between of the tokens.
        if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
            throw new Error('__HALT_COMPILER must be followed by "();"');
        }

        // prevent the lexer from returning any further tokens
        $this->pos = count($this->tokens);

        // return with (); removed
        return (string) substr($textAfter, strlen($matches[0])); // (string) converts false to ''
    }

    /**
     * Creates the token map.
     *
     * The token map maps the PHP internal token identifiers
     * to the identifiers used by the Parser. Additionally it
     * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
     *
     * @return array The token map
     */
    protected function createTokenMap() {
        $tokenMap = array();

        // 256 is the minimum possible token number, as everything below
        // it is an ASCII value
        for ($i = 256; $i < 1000; ++$i) {
            if (T_DOUBLE_COLON === $i) {
                // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
                $tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
            } elseif(T_OPEN_TAG_WITH_ECHO === $i) {
                // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
                $tokenMap[$i] = Tokens::T_ECHO;
            } elseif(T_CLOSE_TAG === $i) {
                // T_CLOSE_TAG is equivalent to ';'
                $tokenMap[$i] = ord(';');
            } elseif ('UNKNOWN' !== $name = token_name($i)) {
                if ('T_HASHBANG' === $name) {
                    // HHVM uses a special token for #! hashbang lines
                    $tokenMap[$i] = Tokens::T_INLINE_HTML;
                } else if (defined($name = 'PhpParser\Parser\Tokens::' . $name)) {
                    // Other tokens can be mapped directly
                    $tokenMap[$i] = constant($name);
                }
            }
        }

        // HHVM uses a special token for numbers that overflow to double
        if (defined('T_ONUMBER')) {
            $tokenMap[T_ONUMBER] = Tokens::T_DNUMBER;
        }
        // HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
        if (defined('T_COMPILER_HALT_OFFSET')) {
            $tokenMap[T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
        }

        return $tokenMap;
    }
}
<?php

namespace PhpParser\Lexer;

use PhpParser\Parser\Tokens;

/**
 * ATTENTION: This code is WRITE-ONLY. Do not try to read it.
 */
class Emulative extends \PhpParser\Lexer
{
    protected $newKeywords;
    protected $inObjectAccess;

    const T_ELLIPSIS   = 1001;
    const T_POW        = 1002;
    const T_POW_EQUAL  = 1003;
    const T_COALESCE   = 1004;
    const T_SPACESHIP  = 1005;
    const T_YIELD_FROM = 1006;

    const PHP_7_0 = '7.0.0dev';
    const PHP_5_6 = '5.6.0rc1';
    const PHP_5_5 = '5.5.0beta1';

    public function __construct(array $options = array()) {
        parent::__construct($options);

        $newKeywordsPerVersion = array(
            self::PHP_5_5 => array(
                'finally'       => Tokens::T_FINALLY,
                'yield'         => Tokens::T_YIELD,
            ),
        );

        $this->newKeywords = array();
        foreach ($newKeywordsPerVersion as $version => $newKeywords) {
            if (version_compare(PHP_VERSION, $version, '>=')) {
                break;
            }

            $this->newKeywords += $newKeywords;
        }

        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
            return;
        }
        $this->tokenMap[self::T_COALESCE]   = Tokens::T_COALESCE;
        $this->tokenMap[self::T_SPACESHIP]  = Tokens::T_SPACESHIP;
        $this->tokenMap[self::T_YIELD_FROM] = Tokens::T_YIELD_FROM;

        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
            return;
        }
        $this->tokenMap[self::T_ELLIPSIS]  = Tokens::T_ELLIPSIS;
        $this->tokenMap[self::T_POW]       = Tokens::T_POW;
        $this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
    }

    public function startLexing($code) {
        $this->inObjectAccess = false;

        $preprocessedCode = $this->preprocessCode($code);
        parent::startLexing($preprocessedCode);
        if ($preprocessedCode !== $code) {
            $this->postprocessTokens();
        }

        // Set code property back to the original code, so __halt_compiler()
        // handling and (start|end)FilePos attributes use the correct offsets
        $this->code = $code;
    }

    /*
     * Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
     * ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
     * use it here.
     * Later when preprocessing the tokens these sequences will either be replaced
     * by real tokens or replaced with their original content (e.g. if they occurred
     * inside a string, i.e. a place where they don't have a special meaning).
     */
    protected function preprocessCode($code) {
        if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
            return $code;
        }

        $code = str_replace('??', '~__EMU__COALESCE__~', $code);
        $code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
        $code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
            // Encoding $0 in order to preserve exact whitespace
            return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
        }, $code);

        if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
            return $code;
        }

        $code = str_replace('...', '~__EMU__ELLIPSIS__~', $code);
        $code = preg_replace('((?<!/)\*\*=)', '~__EMU__POWEQUAL__~', $code);
        $code = preg_replace('((?<!/)\*\*(?!/))', '~__EMU__POW__~', $code);

        return $code;
    }

    /*
     * Replaces the ~__EMU__...~ sequences with real tokens or their original
     * value.
     */
    protected function postprocessTokens() {
        // we need to manually iterate and manage a count because we'll change
        // the tokens array on the way
        for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
            // first check that the following tokens are of form ~LABEL~,
            // then match the __EMU__... sequence.
            if ('~' === $this->tokens[$i]
                && isset($this->tokens[$i + 2])
                && '~' === $this->tokens[$i + 2]
                && T_STRING === $this->tokens[$i + 1][0]
                && preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
            ) {
                if ('ELLIPSIS' === $matches[1]) {
                    $replace = array(
                        array(self::T_ELLIPSIS, '...', $this->tokens[$i + 1][2])
                    );
                } else if ('POW' === $matches[1]) {
                    $replace = array(
                        array(self::T_POW, '**', $this->tokens[$i + 1][2])
                    );
                } else if ('POWEQUAL' === $matches[1]) {
                    $replace = array(
                        array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
                    );
                } else if ('COALESCE' === $matches[1]) {
                    $replace = array(
                        array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
                    );
                } else if ('SPACESHIP' === $matches[1]) {
                    $replace = array(
                        array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
                    );
                } else if ('YIELDFROM' === $matches[1]) {
                    $content = hex2bin($matches[2]);
                    $replace = array(
                        array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
                    );
                } else {
                    throw new \RuntimeException('Invalid __EMU__ sequence');
                }

                array_splice($this->tokens, $i, 3, $replace);
                $c -= 3 - count($replace);
            // for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
            // in their content with the original character sequence
            } elseif (is_array($this->tokens[$i])
                      && 0 !== strpos($this->tokens[$i][1], '__EMU__')
            ) {
                $this->tokens[$i][1] = preg_replace_callback(
                    '(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
                    array($this, 'restoreContentCallback'),
                    $this->tokens[$i][1]
                );
            }
        }
    }

    /*
     * This method is a callback for restoring EMU sequences in
     * multichar tokens (like strings) to their original value.
     */
    public function restoreContentCallback(array $matches) {
        if ('ELLIPSIS' === $matches[1]) {
            return '...';
        } else if ('POW' === $matches[1]) {
            return '**';
        } else if ('POWEQUAL' === $matches[1]) {
            return '**=';
        } else if ('COALESCE' === $matches[1]) {
            return '??';
        } else if ('SPACESHIP' === $matches[1]) {
            return '<=>';
        } else if ('YIELDFROM' === $matches[1]) {
            return hex2bin($matches[2]);
        } else {
            return $matches[0];
        }
    }

    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
        $token = parent::getNextToken($value, $startAttributes, $endAttributes);

        // replace new keywords by their respective tokens. This is not done
        // if we currently are in an object access (e.g. in $obj->namespace
        // "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
        if (Tokens::T_STRING === $token && !$this->inObjectAccess) {
            if (isset($this->newKeywords[strtolower($value)])) {
                return $this->newKeywords[strtolower($value)];
            }
        } else {
            // keep track of whether we currently are in an object access (after ->)
            $this->inObjectAccess = Tokens::T_OBJECT_OPERATOR === $token;
        }

        return $token;
    }
}
<?php

namespace PhpParser;

interface Node
{
    /**
     * Gets the type of the node.
     *
     * @return string Type of the node
     */
    public function getType();

    /**
     * Gets the names of the sub nodes.
     *
     * @return array Names of sub nodes
     */
    public function getSubNodeNames();

    /**
     * Gets line the node started in.
     *
     * @return int Line
     */
    public function getLine();

    /**
     * Sets line the node started in.
     *
     * @param int $line Line
     */
    public function setLine($line);

    /**
     * Gets the doc comment of the node.
     *
     * The doc comment has to be the last comment associated with the node.
     *
     * @return null|Comment\Doc Doc comment object or null
     */
    public function getDocComment();

    /**
     * Sets an attribute on a node.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function setAttribute($key, $value);

    /**
     * Returns whether an attribute exists.
     *
     * @param string $key
     *
     * @return bool
     */
    public function hasAttribute($key);

    /**
     * Returns the value of an attribute.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public function &getAttribute($key, $default = null);

    /**
     * Returns all attributes for the given node.
     *
     * @return array
     */
    public function getAttributes();
}<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Arg extends NodeAbstract
{
    /** @var Expr Value to pass */
    public $value;
    /** @var bool Whether to pass by ref */
    public $byRef;
    /** @var bool Whether to unpack the argument */
    public $unpack;

    /**
     * Constructs a function call argument node.
     *
     * @param Expr  $value      Value to pass
     * @param bool  $byRef      Whether to pass by ref
     * @param bool  $unpack     Whether to unpack the argument
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $value, $byRef = false, $unpack = false, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
        $this->byRef = $byRef;
        $this->unpack = $unpack;
    }

    public function getSubNodeNames() {
        return array('value', 'byRef', 'unpack');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Const_ extends NodeAbstract
{
    /** @var string Name */
    public $name;
    /** @var Expr Value */
    public $value;

    /**
     * Constructs a const node for use in class const and const statements.
     *
     * @param string  $name       Name
     * @param Expr    $value      Value
     * @param array   $attributes Additional attributes
     */
    public function __construct($name, Expr $value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('name', 'value');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

abstract class Expr extends NodeAbstract
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ArrayDimFetch extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var null|Expr Array index / dim */
    public $dim;

    /**
     * Constructs an array index fetch node.
     *
     * @param Expr      $var        Variable
     * @param null|Expr $dim        Array index / dim
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $dim = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->dim = $dim;
    }

    public function getSubnodeNames() {
        return array('var', 'dim');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ArrayItem extends Expr
{
    /** @var null|Expr Key */
    public $key;
    /** @var Expr Value */
    public $value;
    /** @var bool Whether to assign by reference */
    public $byRef;

    /**
     * Constructs an array item node.
     *
     * @param Expr      $value      Value
     * @param null|Expr $key        Key
     * @param bool      $byRef      Whether to assign by reference
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $value, Expr $key = null, $byRef = false, array $attributes = array()) {
        parent::__construct($attributes);
        $this->key = $key;
        $this->value = $value;
        $this->byRef = $byRef;
    }

    public function getSubNodeNames() {
        return array('key', 'value', 'byRef');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Array_ extends Expr
{
    // For use in "kind" attribute
    const KIND_LONG = 1;  // array() syntax
    const KIND_SHORT = 2; // [] syntax

    /** @var ArrayItem[] Items */
    public $items;

    /**
     * Constructs an array node.
     *
     * @param ArrayItem[] $items      Items of the array
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $items = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->items = $items;
    }

    public function getSubNodeNames() {
        return array('items');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Assign extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an assignment node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

abstract class AssignOp extends Expr
{
    /** @var Expr Variable */
    public $var;
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a compound assignment operation node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseAnd extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseOr extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class BitwiseXor extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Concat extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Div extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Minus extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Mod extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Mul extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Plus extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class Pow extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class ShiftLeft extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr\AssignOp;

use PhpParser\Node\Expr\AssignOp;

class ShiftRight extends AssignOp
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class AssignRef extends Expr
{
    /** @var Expr Variable reference is assigned to */
    public $var;
    /** @var Expr Variable which is referenced */
    public $expr;

    /**
     * Constructs an assignment node.
     *
     * @param Expr  $var        Variable
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('var', 'expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

abstract class BinaryOp extends Expr
{
    /** @var Expr The left hand side expression */
    public $left;
    /** @var Expr The right hand side expression */
    public $right;

    /**
     * Constructs a bitwise and node.
     *
     * @param Expr  $left       The left hand side expression
     * @param Expr  $right      The right hand side expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $left, Expr $right, array $attributes = array()) {
        parent::__construct($attributes);
        $this->left = $left;
        $this->right = $right;
    }

    public function getSubNodeNames() {
        return array('left', 'right');
    }
}
<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BitwiseXor extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BooleanAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class BooleanOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Coalesce extends BinaryOp
{
}
<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Concat extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Div extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Equal extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Greater extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class GreaterOrEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Identical extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalAnd extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalOr extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class LogicalXor extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Minus extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Mod extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Mul extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class NotEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class NotIdentical extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Plus extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Pow extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class ShiftLeft extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class ShiftRight extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Smaller extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class SmallerOrEqual extends BinaryOp
{
}<?php

namespace PhpParser\Node\Expr\BinaryOp;

use PhpParser\Node\Expr\BinaryOp;

class Spaceship extends BinaryOp
{
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class BitwiseNot extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a bitwise not node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class BooleanNot extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a boolean not node.
     *
     * @param Expr $expr       Expression
     * @param array               $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

abstract class Cast extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a cast node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Array_ extends Cast
{
}<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Bool_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Double extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Int_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Object_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class String_ extends Cast
{
}
<?php

namespace PhpParser\Node\Expr\Cast;

use PhpParser\Node\Expr\Cast;

class Unset_ extends Cast
{
}<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class ClassConstFetch extends Expr
{
    /** @var Name|Expr Class name */
    public $class;
    /** @var string Constant name */
    public $name;

    /**
     * Constructs a class const fetch node.
     *
     * @param Name|Expr $class      Class name
     * @param string    $name       Constant name
     * @param array     $attributes Additional attributes
     */
    public function __construct($class, $name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->class = $class;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('class', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Clone_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a clone node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;

class Closure extends Expr implements FunctionLike
{
    /** @var bool Whether the closure is static */
    public $static;
    /** @var bool Whether to return by reference */
    public $byRef;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var ClosureUse[] use()s */
    public $uses;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a lambda function node.
     *
     * @param array $subNodes   Array of the following optional subnodes:
     *                          'static'     => false  : Whether the closure is static
     *                          'byRef'      => false  : Whether to return by reference
     *                          'params'     => array(): Parameters
     *                          'uses'       => array(): use()s
     *                          'returnType' => null   : Return type
     *                          'stmts'      => array(): Statements
     * @param array $attributes Additional attributes
     */
    public function __construct(array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->static = isset($subNodes['static']) ? $subNodes['static'] : false;
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->uses = isset($subNodes['uses']) ? $subNodes['uses'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('static', 'byRef', 'params', 'uses', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ClosureUse extends Expr
{
    /** @var string Name of variable */
    public $var;
    /** @var bool Whether to use by reference */
    public $byRef;

    /**
     * Constructs a closure use node.
     *
     * @param string      $var        Name of variable
     * @param bool        $byRef      Whether to use by reference
     * @param array       $attributes Additional attributes
     */
    public function __construct($var, $byRef = false, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->byRef = $byRef;
    }

    public function getSubNodeNames() {
        return array('var', 'byRef');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class ConstFetch extends Expr
{
    /** @var Name Constant name */
    public $name;

    /**
     * Constructs a const fetch node.
     *
     * @param Name  $name       Constant name
     * @param array $attributes Additional attributes
     */
    public function __construct(Name $name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Empty_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an empty() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ErrorSuppress extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an error suppress node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Eval_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an eval() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Exit_ extends Expr
{
    /* For use in "kind" attribute */
    const KIND_EXIT = 1;
    const KIND_DIE = 2;

    /** @var null|Expr Expression */
    public $expr;

    /**
     * Constructs an exit() node.
     *
     * @param null|Expr $expr       Expression
     * @param array                    $attributes Additional attributes
     */
    public function __construct(Expr $expr = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class FuncCall extends Expr
{
    /** @var Node\Name|Expr Function name */
    public $name;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Node\Name|Expr $name       Function name
     * @param Node\Arg[]                    $args       Arguments
     * @param array                                   $attributes Additional attributes
     */
    public function __construct($name, array $args = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Include_ extends Expr
{
    const TYPE_INCLUDE      = 1;
    const TYPE_INCLUDE_ONCE = 2;
    const TYPE_REQUIRE      = 3;
    const TYPE_REQUIRE_ONCE = 4;

    /** @var Expr Expression */
    public $expr;
    /** @var int Type of include */
    public $type;

    /**
     * Constructs an include node.
     *
     * @param Expr  $expr       Expression
     * @param int   $type       Type of include
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, $type, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
        $this->type = $type;
    }

    public function getSubNodeNames() {
        return array('expr', 'type');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class Instanceof_ extends Expr
{
    /** @var Expr Expression */
    public $expr;
    /** @var Name|Expr Class name */
    public $class;

    /**
     * Constructs an instanceof check node.
     *
     * @param Expr      $expr       Expression
     * @param Name|Expr $class      Class name
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $expr, $class, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
        $this->class = $class;
    }

    public function getSubNodeNames() {
        return array('expr', 'class');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Isset_ extends Expr
{
    /** @var Expr[] Variables */
    public $vars;

    /**
     * Constructs an array node.
     *
     * @param Expr[] $vars       Variables
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct($attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class List_ extends Expr
{
    /** @var Expr[] List of variables to assign to */
    public $vars;

    /**
     * Constructs a list() destructuring node.
     *
     * @param Expr[] $vars       List of variables to assign to
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct($attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr;

class MethodCall extends Expr
{
    /** @var Expr Variable holding object */
    public $var;
    /** @var string|Expr Method name */
    public $name;
    /** @var Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Expr        $var        Variable holding object
     * @param string|Expr $name       Method name
     * @param Arg[]       $args       Arguments
     * @param array       $attributes Additional attributes
     */
    public function __construct(Expr $var, $name, array $args = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('var', 'name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class New_ extends Expr
{
    /** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
    public $class;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a function call node.
     *
     * @param Node\Name|Expr|Node\Stmt\Class_ $class      Class name (or class node for anonymous classes)
     * @param Node\Arg[]                      $args       Arguments
     * @param array                           $attributes Additional attributes
     */
    public function __construct($class, array $args = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->class = $class;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('class', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PostDec extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a post decrement node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PostInc extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a post increment node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PreDec extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a pre decrement node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PreInc extends Expr
{
    /** @var Expr Variable */
    public $var;

    /**
     * Constructs a pre increment node.
     *
     * @param Expr  $var        Variable
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $var, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
    }

    public function getSubNodeNames() {
        return array('var');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Print_ extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs an print() node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class PropertyFetch extends Expr
{
    /** @var Expr Variable holding object */
    public $var;
    /** @var string|Expr Property name */
    public $name;

    /**
     * Constructs a function call node.
     *
     * @param Expr        $var        Variable holding object
     * @param string|Expr $name       Property name
     * @param array       $attributes Additional attributes
     */
    public function __construct(Expr $var, $name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->var = $var;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('var', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class ShellExec extends Expr
{
    /** @var array Encapsed string array */
    public $parts;

    /**
     * Constructs a shell exec (backtick) node.
     *
     * @param array $parts      Encapsed string array
     * @param array $attributes Additional attributes
     */
    public function __construct(array $parts, array $attributes = array()) {
        parent::__construct($attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node;
use PhpParser\Node\Expr;

class StaticCall extends Expr
{
    /** @var Node\Name|Expr Class name */
    public $class;
    /** @var string|Expr Method name */
    public $name;
    /** @var Node\Arg[] Arguments */
    public $args;

    /**
     * Constructs a static method call node.
     *
     * @param Node\Name|Expr $class      Class name
     * @param string|Expr    $name       Method name
     * @param Node\Arg[]     $args       Arguments
     * @param array          $attributes Additional attributes
     */
    public function __construct($class, $name, array $args = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->class = $class;
        $this->name = $name;
        $this->args = $args;
    }

    public function getSubNodeNames() {
        return array('class', 'name', 'args');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

class StaticPropertyFetch extends Expr
{
    /** @var Name|Expr Class name */
    public $class;
    /** @var string|Expr Property name */
    public $name;

    /**
     * Constructs a static property fetch node.
     *
     * @param Name|Expr   $class      Class name
     * @param string|Expr $name       Property name
     * @param array       $attributes Additional attributes
     */
    public function __construct($class, $name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->class = $class;
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('class', 'name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Ternary extends Expr
{
    /** @var Expr Condition */
    public $cond;
    /** @var null|Expr Expression for true */
    public $if;
    /** @var Expr Expression for false */
    public $else;

    /**
     * Constructs a ternary operator node.
     *
     * @param Expr      $cond       Condition
     * @param null|Expr $if         Expression for true
     * @param Expr      $else       Expression for false
     * @param array                    $attributes Additional attributes
     */
    public function __construct(Expr $cond, $if, Expr $else, array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->if = $if;
        $this->else = $else;
    }

    public function getSubNodeNames() {
        return array('cond', 'if', 'else');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class UnaryMinus extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a unary minus node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class UnaryPlus extends Expr
{
    /** @var Expr Expression */
    public $expr;

    /**
     * Constructs a unary plus node.
     *
     * @param Expr $expr       Expression
     * @param array               $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Variable extends Expr
{
    /** @var string|Expr Name */
    public $name;

    /**
     * Constructs a variable node.
     *
     * @param string|Expr $name       Name
     * @param array                      $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class YieldFrom extends Expr
{
    /** @var Expr Expression to yield from */
    public $expr;

    /**
     * Constructs an "yield from" node.
     *
     * @param Expr  $expr       Expression
     * @param array $attributes Additional attributes
     */
    public function __construct(Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;

class Yield_ extends Expr
{
    /** @var null|Expr Key expression */
    public $key;
    /** @var null|Expr Value expression */
    public $value;

    /**
     * Constructs a yield expression node.
     *
     * @param null|Expr $value      Value expression
     * @param null|Expr $key        Key expression
     * @param array     $attributes Additional attributes
     */
    public function __construct(Expr $value = null, Expr $key = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->key = $key;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('key', 'value');
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\Node;

interface FunctionLike extends Node
{
    /**
     * Whether to return by reference
     *
     * @return bool
     */
    public function returnsByRef();

    /**
     * List of parameters
     *
     * @return Node\Param[]
     */
    public function getParams();

    /**
     * Get the declared return type or null
     * 
     * @return null|string|Node\Name
     */
    public function getReturnType();

    /**
     * The function body
     *
     * @return Node\Stmt[]
     */
    public function getStmts();
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

class Name extends NodeAbstract
{
    /** @var string[] Parts of the name */
    public $parts;

    /**
     * Constructs a name node.
     *
     * @param string|array $parts      Parts of the name (or name as string)
     * @param array        $attributes Additional attributes
     */
    public function __construct($parts, array $attributes = array()) {
        if (!is_array($parts)) {
            $parts = explode('\\', $parts);
        }

        parent::__construct($attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }

    /**
     * Gets the first part of the name, i.e. everything before the first namespace separator.
     *
     * @return string First part of the name
     */
    public function getFirst() {
        return $this->parts[0];
    }

    /**
     * Gets the last part of the name, i.e. everything after the last namespace separator.
     *
     * @return string Last part of the name
     */
    public function getLast() {
        return $this->parts[count($this->parts) - 1];
    }

    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return 1 == count($this->parts);
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return 1 < count($this->parts);
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return false;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return false;
    }

    /**
     * Returns a string representation of the name by imploding the namespace parts with a separator.
     *
     * @param string $separator The separator to use (defaults to the namespace separator \)
     *
     * @return string String representation
     */
    public function toString($separator = '\\') {
        return implode($separator, $this->parts);
    }

    /**
     * Returns a string representation of the name by imploding the namespace parts with the
     * namespace separator.
     *
     * @return string String representation
     */
    public function __toString() {
        return implode('\\', $this->parts);
    }

    /**
     * Sets the whole name.
     *
     * @deprecated Create a new Name instead, or manually modify the $parts property
     *
     * @param string|array|self $name The name to set the whole name to
     */
    public function set($name) {
        $this->parts = self::prepareName($name);
    }

    /**
     * Prepends a name to this name.
     *
     * @deprecated Use Name::concat($name1, $name2) instead
     *
     * @param string|array|self $name Name to prepend
     */
    public function prepend($name) {
        $this->parts = array_merge(self::prepareName($name), $this->parts);
    }

    /**
     * Appends a name to this name.
     *
     * @deprecated Use Name::concat($name1, $name2) instead
     *
     * @param string|array|self $name Name to append
     */
    public function append($name) {
        $this->parts = array_merge($this->parts, self::prepareName($name));
    }

    /**
     * Sets the first part of the name.
     *
     * @deprecated Use concat($first, $name->slice(1)) instead
     *
     * @param string|array|self $name The name to set the first part to
     */
    public function setFirst($name) {
        array_splice($this->parts, 0, 1, self::prepareName($name));
    }

    /**
     * Sets the last part of the name.
     *
     * @param string|array|self $name The name to set the last part to
     */
    public function setLast($name) {
        array_splice($this->parts, -1, 1, self::prepareName($name));
    }

    /**
     * Gets a slice of a name (similar to array_slice).
     *
     * This method returns a new instance of the same type as the original and with the same
     * attributes.
     *
     * If the slice is empty, a Name with an empty parts array is returned. While this is
     * meaningless in itself, it works correctly in conjunction with concat().
     *
     * @param int $offset Offset to start the slice at
     *
     * @return static Sliced name
     */
    public function slice($offset) {
        // TODO negative offset and length
        if ($offset < 0 || $offset > count($this->parts)) {
            throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
        }

        return new static(array_slice($this->parts, $offset), $this->attributes);
    }

    /**
     * Concatenate two names, yielding a new Name instance.
     *
     * The type of the generated instance depends on which class this method is called on, for
     * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
     *
     * @param string|array|self $name1      The first name
     * @param string|array|self $name2      The second name
     * @param array             $attributes Attributes to assign to concatenated name
     *
     * @return static Concatenated name
     */
    public static function concat($name1, $name2, array $attributes = []) {
        return new static(
            array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
        );
    }

    /**
     * Prepares a (string, array or Name node) name for use in name changing methods by converting
     * it to an array.
     *
     * @param string|array|self $name Name to prepare
     *
     * @return array Prepared name
     */
    private static function prepareName($name) {
        if (is_string($name)) {
            return explode('\\', $name);
        } elseif (is_array($name)) {
            return $name;
        } elseif ($name instanceof self) {
            return $name->parts;
        }

        throw new \InvalidArgumentException(
            'When changing a name you need to pass either a string, an array or a Name node'
        );
    }
}
<?php

namespace PhpParser\Node\Name;

class FullyQualified extends \PhpParser\Node\Name
{
    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return false;
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return false;
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return true;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return false;
    }
}<?php

namespace PhpParser\Node\Name;

class Relative extends \PhpParser\Node\Name
{
    /**
     * Checks whether the name is unqualified. (E.g. Name)
     *
     * @return bool Whether the name is unqualified
     */
    public function isUnqualified() {
        return false;
    }

    /**
     * Checks whether the name is qualified. (E.g. Name\Name)
     *
     * @return bool Whether the name is qualified
     */
    public function isQualified() {
        return false;
    }

    /**
     * Checks whether the name is fully qualified. (E.g. \Name)
     *
     * @return bool Whether the name is fully qualified
     */
    public function isFullyQualified() {
        return false;
    }

    /**
     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
     *
     * @return bool Whether the name is relative
     */
    public function isRelative() {
        return true;
    }
}<?php

namespace PhpParser\Node;

use PhpParser\Error;
use PhpParser\NodeAbstract;

class Param extends NodeAbstract
{
    /** @var null|string|Name Typehint */
    public $type;
    /** @var bool Whether parameter is passed by reference */
    public $byRef;
    /** @var bool Whether this is a variadic argument */
    public $variadic;
    /** @var string Name */
    public $name;
    /** @var null|Expr Default value */
    public $default;

    /**
     * Constructs a parameter node.
     *
     * @param string           $name       Name
     * @param null|Expr        $default    Default value
     * @param null|string|Name $type       Typehint
     * @param bool             $byRef      Whether is passed by reference
     * @param bool             $variadic   Whether this is a variadic argument
     * @param array            $attributes Additional attributes
     */
    public function __construct($name, Expr $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = $type;
        $this->byRef = $byRef;
        $this->variadic = $variadic;
        $this->name = $name;
        $this->default = $default;

        if ($variadic && null !== $default) {
            throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
        }
    }

    public function getSubNodeNames() {
        return array('type', 'byRef', 'variadic', 'name', 'default');
    }
}
<?php

namespace PhpParser\Node;

abstract class Scalar extends Expr
{
}<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class DNumber extends Scalar
{
    /** @var float Number value */
    public $value;

    /**
     * Constructs a float number scalar node.
     *
     * @param float $value      Value of the number
     * @param array $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * @internal
     *
     * Parses a DNUMBER token like PHP would.
     *
     * @param string $str A string number
     *
     * @return float The parsed number
     */
    public static function parse($str) {
        // if string contains any of .eE just cast it to float
        if (false !== strpbrk($str, '.eE')) {
            return (float) $str;
        }

        // otherwise it's an integer notation that overflowed into a float
        // if it starts with 0 it's one of the special integer notations
        if ('0' === $str[0]) {
            // hex
            if ('x' === $str[1] || 'X' === $str[1]) {
                return hexdec($str);
            }

            // bin
            if ('b' === $str[1] || 'B' === $str[1]) {
                return bindec($str);
            }

            // oct
            // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
            // so that only the digits before that are used
            return octdec(substr($str, 0, strcspn($str, '89')));
        }

        // dec
        return (float) $str;
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class Encapsed extends Scalar
{
    /** @var array Encaps list */
    public $parts;

    /**
     * Constructs an encapsed string node.
     *
     * @param array $parts      Encaps list
     * @param array $attributes Additional attributes
     */
    public function __construct(array $parts, array $attributes = array()) {
        parent::__construct($attributes);
        $this->parts = $parts;
    }

    public function getSubNodeNames() {
        return array('parts');
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

class EncapsedStringPart extends Scalar
{
    /** @var string String value */
    public $value;

    /**
     * Constructs a node representing a string part of an encapsed string.
     *
     * @param string $value      String value
     * @param array  $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Error;
use PhpParser\Node\Scalar;

class LNumber extends Scalar
{
    /* For use in "kind" attribute */
    const KIND_BIN = 2;
    const KIND_OCT = 8;
    const KIND_DEC = 10;
    const KIND_HEX = 16;

    /** @var int Number value */
    public $value;

    /**
     * Constructs an integer number scalar node.
     *
     * @param int   $value      Value of the number
     * @param array $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * Constructs an LNumber node from a string number literal.
     *
     * @param string $str               String number literal (decimal, octal, hex or binary)
     * @param array  $attributes        Additional attributes
     * @param bool   $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5)
     *
     * @return LNumber The constructed LNumber, including kind attribute
     */
    public static function fromString($str, array $attributes = array(), $allowInvalidOctal = false) {
        if ('0' !== $str[0] || '0' === $str) {
            $attributes['kind'] = LNumber::KIND_DEC;
            return new LNumber((int) $str, $attributes);
        }

        if ('x' === $str[1] || 'X' === $str[1]) {
            $attributes['kind'] = LNumber::KIND_HEX;
            return new LNumber(hexdec($str), $attributes);
        }

        if ('b' === $str[1] || 'B' === $str[1]) {
            $attributes['kind'] = LNumber::KIND_BIN;
            return new LNumber(bindec($str), $attributes);
        }

        if (!$allowInvalidOctal && strpbrk($str, '89')) {
            throw new Error('Invalid numeric literal', $attributes);
        }

        // use intval instead of octdec to get proper cutting behavior with malformed numbers
        $attributes['kind'] = LNumber::KIND_OCT;
        return new LNumber(intval($str, 8), $attributes);
    }
}
<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Node\Scalar;

abstract class MagicConst extends Scalar
{
    /**
     * Constructs a magic constant node.
     *
     * @param array $attributes Additional attributes
     */
    public function __construct(array $attributes = array()) {
        parent::__construct($attributes);
    }

    public function getSubNodeNames() {
        return array();
    }

    /**
     * Get name of magic constant.
     *
     * @return string Name of magic constant
     */
    abstract public function getName();
}
<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Class_ extends MagicConst
{
    public function getName() {
        return '__CLASS__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Dir extends MagicConst
{
    public function getName() {
        return '__DIR__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class File extends MagicConst
{
    public function getName() {
        return '__FILE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Function_ extends MagicConst
{
    public function getName() {
        return '__FUNCTION__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Line extends MagicConst
{
    public function getName() {
        return '__LINE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Method extends MagicConst
{
    public function getName() {
        return '__METHOD__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Namespace_ extends MagicConst
{
    public function getName() {
        return '__NAMESPACE__';
    }
}<?php

namespace PhpParser\Node\Scalar\MagicConst;

use PhpParser\Node\Scalar\MagicConst;

class Trait_ extends MagicConst
{
    public function getName() {
        return '__TRAIT__';
    }
}<?php

namespace PhpParser\Node\Scalar;

use PhpParser\Error;
use PhpParser\Node\Scalar;

class String_ extends Scalar
{
    /* For use in "kind" attribute */
    const KIND_SINGLE_QUOTED = 1;
    const KIND_DOUBLE_QUOTED = 2;
    const KIND_HEREDOC = 3;
    const KIND_NOWDOC = 4;

    /** @var string String value */
    public $value;

    protected static $replacements = array(
        '\\' => '\\',
        '$'  =>  '$',
        'n'  => "\n",
        'r'  => "\r",
        't'  => "\t",
        'f'  => "\f",
        'v'  => "\v",
        'e'  => "\x1B",
    );

    /**
     * Constructs a string scalar node.
     *
     * @param string $value      Value of the string
     * @param array  $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }

    /**
     * @internal
     *
     * Parses a string token.
     *
     * @param string $str String token content
     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
     *
     * @return string The parsed string
     */
    public static function parse($str, $parseUnicodeEscape = true) {
        $bLength = 0;
        if ('b' === $str[0] || 'B' === $str[0]) {
            $bLength = 1;
        }

        if ('\'' === $str[$bLength]) {
            return str_replace(
                array('\\\\', '\\\''),
                array(  '\\',   '\''),
                substr($str, $bLength + 1, -1)
            );
        } else {
            return self::parseEscapeSequences(
                substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape
            );
        }
    }

    /**
     * @internal
     *
     * Parses escape sequences in strings (all string types apart from single quoted).
     *
     * @param string      $str   String without quotes
     * @param null|string $quote Quote type
     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
     *
     * @return string String with escape sequences parsed
     */
    public static function parseEscapeSequences($str, $quote, $parseUnicodeEscape = true) {
        if (null !== $quote) {
            $str = str_replace('\\' . $quote, $quote, $str);
        }

        $extra = '';
        if ($parseUnicodeEscape) {
            $extra = '|u\{([0-9a-fA-F]+)\}';
        }

        return preg_replace_callback(
            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
            function($matches) {
                $str = $matches[1];

                if (isset(self::$replacements[$str])) {
                    return self::$replacements[$str];
                } elseif ('x' === $str[0] || 'X' === $str[0]) {
                    return chr(hexdec($str));
                } elseif ('u' === $str[0]) {
                    return self::codePointToUtf8(hexdec($matches[2]));
                } else {
                    return chr(octdec($str));
                }
            },
            $str
        );
    }

    private static function codePointToUtf8($num) {
        if ($num <= 0x7F) {
            return chr($num);
        }
        if ($num <= 0x7FF) {
            return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
        }
        if ($num <= 0xFFFF) {
            return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
        }
        if ($num <= 0x1FFFFF) {
            return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
                 . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
        }
        throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
    }

    /**
     * @internal
     *
     * Parses a constant doc string.
     *
     * @param string $startToken Doc string start token content (<<<SMTHG)
     * @param string $str        String token content
     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
     *
     * @return string Parsed string
     */
    public static function parseDocString($startToken, $str, $parseUnicodeEscape = true) {
        // strip last newline (thanks tokenizer for sticking it into the string!)
        $str = preg_replace('~(\r\n|\n|\r)\z~', '', $str);

        // nowdoc string
        if (false !== strpos($startToken, '\'')) {
            return $str;
        }

        return self::parseEscapeSequences($str, null, $parseUnicodeEscape);
    }
}
<?php

namespace PhpParser\Node;

use PhpParser\NodeAbstract;

abstract class Stmt extends NodeAbstract
{
}<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Break_ extends Node\Stmt
{
    /** @var null|Node\Expr Number of loops to break */
    public $num;

    /**
     * Constructs a break node.
     *
     * @param null|Node\Expr $num        Number of loops to break
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $num = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->num = $num;
    }

    public function getSubNodeNames() {
        return array('num');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Case_ extends Node\Stmt
{
    /** @var null|Node\Expr $cond Condition (null for default) */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a case node.
     *
     * @param null|Node\Expr $cond       Condition (null for default)
     * @param Node[]         $stmts      Statements
     * @param array          $attributes Additional attributes
     */
    public function __construct($cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Catch_ extends Node\Stmt
{
    /** @var Node\Name Class of exception */
    public $type;
    /** @var string Variable for exception */
    public $var;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a catch node.
     *
     * @param Node\Name $type       Class of exception
     * @param string    $var        Variable for exception
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Name $type, $var, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = $type;
        $this->var = $var;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('type', 'var', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class ClassConst extends Node\Stmt
{
    /** @var Node\Const_[] Constant declarations */
    public $consts;

    /**
     * Constructs a class const list node.
     *
     * @param Node\Const_[] $consts     Constant declarations
     * @param array         $attributes Additional attributes
     */
    public function __construct(array $consts, array $attributes = array()) {
        parent::__construct($attributes);
        $this->consts = $consts;
    }

    public function getSubNodeNames() {
        return array('consts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

abstract class ClassLike extends Node\Stmt {
    /** @var string Name */
    public $name;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Gets all methods defined directly in this class/interface/trait
     *
     * @return ClassMethod[]
     */
    public function getMethods() {
        $methods = array();
        foreach ($this->stmts as $stmt) {
            if ($stmt instanceof ClassMethod) {
                $methods[] = $stmt;
            }
        }
        return $methods;
    }

    /**
     * Gets method with the given name defined directly in this class/interface/trait.
     *
     * @param string $name Name of the method (compared case-insensitively)
     *
     * @return ClassMethod|null Method node or null if the method does not exist
     */
    public function getMethod($name) {
        $lowerName = strtolower($name);
        foreach ($this->stmts as $stmt) {
            if ($stmt instanceof ClassMethod && $lowerName === strtolower($stmt->name)) {
                return $stmt;
            }
        }
        return null;
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Error;

class ClassMethod extends Node\Stmt implements FunctionLike
{
    /** @var int Type */
    public $type;
    /** @var bool Whether to return by reference */
    public $byRef;
    /** @var string Name */
    public $name;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a class method node.
     *
     * @param string      $name       Name
     * @param array       $subNodes   Array of the following optional subnodes:
     *                                'type'       => MODIFIER_PUBLIC: Type
     *                                'byRef'      => false          : Whether to return by reference
     *                                'params'     => array()        : Parameters
     *                                'returnType' => null           : Return type
     *                                'stmts'      => array()        : Statements
     * @param array       $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
        $this->byRef = isset($subNodes['byRef'])  ? $subNodes['byRef']  : false;
        $this->name = $name;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : array();

        if ($this->type & Class_::MODIFIER_STATIC) {
            switch (strtolower($this->name)) {
                case '__construct':
                    throw new Error(sprintf('Constructor %s() cannot be static', $this->name));
                case '__destruct':
                    throw new Error(sprintf('Destructor %s() cannot be static', $this->name));
                case '__clone':
                    throw new Error(sprintf('Clone method %s() cannot be static', $this->name));
            }
        }
    }

    public function getSubNodeNames() {
        return array('type', 'byRef', 'name', 'params', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }

    public function isPublic() {
        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    }

    public function isProtected() {
        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    }

    public function isPrivate() {
        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    }

    public function isAbstract() {
        return (bool) ($this->type & Class_::MODIFIER_ABSTRACT);
    }

    public function isFinal() {
        return (bool) ($this->type & Class_::MODIFIER_FINAL);
    }

    public function isStatic() {
        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Class_ extends ClassLike
{
    const MODIFIER_PUBLIC    =  1;
    const MODIFIER_PROTECTED =  2;
    const MODIFIER_PRIVATE   =  4;
    const MODIFIER_STATIC    =  8;
    const MODIFIER_ABSTRACT  = 16;
    const MODIFIER_FINAL     = 32;

    const VISIBILITY_MODIFER_MASK = 7; // 1 | 2 | 4

    /** @var int Type */
    public $type;
    /** @var null|Node\Name Name of extended class */
    public $extends;
    /** @var Node\Name[] Names of implemented interfaces */
    public $implements;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a class node.
     *
     * @param string|null $name       Name
     * @param array       $subNodes   Array of the following optional subnodes:
     *                                'type'       => 0      : Type
     *                                'extends'    => null   : Name of extended class
     *                                'implements' => array(): Names of implemented interfaces
     *                                'stmts'      => array(): Statements
     * @param array       $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
        $this->name = $name;
        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : null;
        $this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();

        if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
        }

        if (isset(self::$specialNames[strtolower($this->extends)])) {
            throw new Error(
                sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
                $this->extends->getAttributes()
            );
        }

        foreach ($this->implements as $interface) {
            if (isset(self::$specialNames[strtolower($interface)])) {
                throw new Error(
                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
                    $interface->getAttributes()
                );
            }
        }
    }

    public function getSubNodeNames() {
        return array('type', 'name', 'extends', 'implements', 'stmts');
    }

    public function isAbstract() {
        return (bool) ($this->type & self::MODIFIER_ABSTRACT);
    }

    public function isFinal() {
        return (bool) ($this->type & self::MODIFIER_FINAL);
    }

    public function isAnonymous() {
        return null === $this->name;
    }

    /**
     * @internal
     */
    public static function verifyModifier($a, $b) {
        if ($a & self::VISIBILITY_MODIFER_MASK && $b & self::VISIBILITY_MODIFER_MASK) {
            throw new Error('Multiple access type modifiers are not allowed');
        }

        if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
            throw new Error('Multiple abstract modifiers are not allowed');
        }

        if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) {
            throw new Error('Multiple static modifiers are not allowed');
        }

        if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
            throw new Error('Multiple final modifiers are not allowed');
        }

        if ($a & 48 && $b & 48) {
            throw new Error('Cannot use the final modifier on an abstract class member');
        }
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Const_ extends Node\Stmt
{
    /** @var Node\Const_[] Constant declarations */
    public $consts;

    /**
     * Constructs a const list node.
     *
     * @param Node\Const_[] $consts     Constant declarations
     * @param array         $attributes Additional attributes
     */
    public function __construct(array $consts, array $attributes = array()) {
        parent::__construct($attributes);
        $this->consts = $consts;
    }

    public function getSubNodeNames() {
        return array('consts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Continue_ extends Node\Stmt
{
    /** @var null|Node\Expr Number of loops to continue */
    public $num;

    /**
     * Constructs a continue node.
     *
     * @param null|Node\Expr $num        Number of loops to continue
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $num = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->num = $num;
    }

    public function getSubNodeNames() {
        return array('num');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class DeclareDeclare extends Node\Stmt
{
    /** @var string Key */
    public $key;
    /** @var Node\Expr Value */
    public $value;

    /**
     * Constructs a declare key=>value pair node.
     *
     * @param string    $key        Key
     * @param Node\Expr $value      Value
     * @param array     $attributes Additional attributes
     */
    public function __construct($key, Node\Expr $value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->key = $key;
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('key', 'value');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
class Declare_ extends Node\Stmt
{
    /** @var DeclareDeclare[] List of declares */
    public $declares;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a declare node.
     *
     * @param DeclareDeclare[] $declares   List of declares
     * @param Node[]|null      $stmts      Statements
     * @param array            $attributes Additional attributes
     */
    public function __construct(array $declares, array $stmts = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->declares = $declares;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('declares', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Do_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a do while node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Echo_ extends Node\Stmt
{
    /** @var Node\Expr[] Expressions */
    public $exprs;

    /**
     * Constructs an echo node.
     *
     * @param Node\Expr[] $exprs      Expressions
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $exprs, array $attributes = array()) {
        parent::__construct($attributes);
        $this->exprs = $exprs;
    }

    public function getSubNodeNames() {
        return array('exprs');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class ElseIf_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs an elseif node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Else_ extends Node\Stmt
{
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs an else node.
     *
     * @param Node[] $stmts      Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct(array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class For_ extends Node\Stmt
{
    /** @var Node\Expr[] Init expressions */
    public $init;
    /** @var Node\Expr[] Loop conditions */
    public $cond;
    /** @var Node\Expr[] Loop expressions */
    public $loop;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a for loop node.
     *
     * @param array $subNodes   Array of the following optional subnodes:
     *                          'init'  => array(): Init expressions
     *                          'cond'  => array(): Loop conditions
     *                          'loop'  => array(): Loop expressions
     *                          'stmts' => array(): Statements
     * @param array $attributes Additional attributes
     */
    public function __construct(array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->init = isset($subNodes['init']) ? $subNodes['init'] : array();
        $this->cond = isset($subNodes['cond']) ? $subNodes['cond'] : array();
        $this->loop = isset($subNodes['loop']) ? $subNodes['loop'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('init', 'cond', 'loop', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Foreach_ extends Node\Stmt
{
    /** @var Node\Expr Expression to iterate */
    public $expr;
    /** @var null|Node\Expr Variable to assign key to */
    public $keyVar;
    /** @var bool Whether to assign value by reference */
    public $byRef;
    /** @var Node\Expr Variable to assign value to */
    public $valueVar;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a foreach node.
     *
     * @param Node\Expr $expr       Expression to iterate
     * @param Node\Expr $valueVar   Variable to assign value to
     * @param array     $subNodes   Array of the following optional subnodes:
     *                              'keyVar' => null   : Variable to assign key to
     *                              'byRef'  => false  : Whether to assign value by reference
     *                              'stmts'  => array(): Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
        $this->keyVar = isset($subNodes['keyVar']) ? $subNodes['keyVar'] : null;
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->valueVar = $valueVar;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('expr', 'keyVar', 'byRef', 'valueVar', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Node\FunctionLike;

class Function_ extends Node\Stmt implements FunctionLike
{
    /** @var bool Whether function returns by reference */
    public $byRef;
    /** @var string Name */
    public $name;
    /** @var Node\Param[] Parameters */
    public $params;
    /** @var null|string|Node\Name Return type */
    public $returnType;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a function node.
     *
     * @param string $name       Name
     * @param array  $subNodes   Array of the following optional subnodes:
     *                           'byRef'      => false  : Whether to return by reference
     *                           'params'     => array(): Parameters
     *                           'returnType' => null   : Return type
     *                           'stmts'      => array(): Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
        $this->name = $name;
        $this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
        $this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
    }

    public function getSubNodeNames() {
        return array('byRef', 'name', 'params', 'returnType', 'stmts');
    }

    public function returnsByRef() {
        return $this->byRef;
    }

    public function getParams() {
        return $this->params;
    }

    public function getReturnType() {
        return $this->returnType;
    }

    public function getStmts() {
        return $this->stmts;
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Global_ extends Node\Stmt
{
    /** @var Node\Expr[] Variables */
    public $vars;

    /**
     * Constructs a global variables list node.
     *
     * @param Node\Expr[] $vars       Variables to unset
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct($attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Goto_ extends Stmt
{
    /** @var string Name of label to jump to */
    public $name;

    /**
     * Constructs a goto node.
     *
     * @param string $name       Name of label to jump to
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;
use PhpParser\Node\Name;

class GroupUse extends Stmt
{
    /** @var int Type of group use */
    public $type;
    /** @var Name Prefix for uses */
    public $prefix;
    /** @var UseUse[] Uses */
    public $uses;

    /**
     * Constructs a group use node.
     *
     * @param Name     $prefix     Prefix for uses
     * @param UseUse[] $uses       Uses
     * @param int      $type       Type of group use
     * @param array    $attributes Additional attributes
     */
    public function __construct(Name $prefix, array $uses, $type = Use_::TYPE_NORMAL, array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = $type;
        $this->prefix = $prefix;
        $this->uses = $uses;
    }

    public function getSubNodeNames() {
        return array('type', 'prefix', 'uses');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class HaltCompiler extends Stmt
{
    /** @var string Remaining text after halt compiler statement. */
    public $remaining;

    /**
     * Constructs a __halt_compiler node.
     *
     * @param string $remaining  Remaining text after halt compiler statement.
     * @param array  $attributes Additional attributes
     */
    public function __construct($remaining, array $attributes = array()) {
        parent::__construct($attributes);
        $this->remaining = $remaining;
    }

    public function getSubNodeNames() {
        return array('remaining');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class If_ extends Node\Stmt
{
    /** @var Node\Expr Condition expression */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;
    /** @var ElseIf_[] Elseif clauses */
    public $elseifs;
    /** @var null|Else_ Else clause */
    public $else;

    /**
     * Constructs an if node.
     *
     * @param Node\Expr $cond       Condition
     * @param array     $subNodes   Array of the following optional subnodes:
     *                              'stmts'   => array(): Statements
     *                              'elseifs' => array(): Elseif clauses
     *                              'else'    => null   : Else clause
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
        $this->elseifs = isset($subNodes['elseifs']) ? $subNodes['elseifs'] : array();
        $this->else = isset($subNodes['else']) ? $subNodes['else'] : null;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts', 'elseifs', 'else');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class InlineHTML extends Stmt
{
    /** @var string String */
    public $value;

    /**
     * Constructs an inline HTML node.
     *
     * @param string $value      String
     * @param array  $attributes Additional attributes
     */
    public function __construct($value, array $attributes = array()) {
        parent::__construct($attributes);
        $this->value = $value;
    }

    public function getSubNodeNames() {
        return array('value');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Interface_ extends ClassLike
{
    /** @var Node\Name[] Extended interfaces */
    public $extends;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a class node.
     *
     * @param string $name       Name
     * @param array  $subNodes   Array of the following optional subnodes:
     *                           'extends' => array(): Name of extended interfaces
     *                           'stmts'   => array(): Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
        $this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();

        if (isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
        }

        foreach ($this->extends as $interface) {
            if (isset(self::$specialNames[strtolower($interface)])) {
                throw new Error(
                    sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
                    $interface->getAttributes()
                );
            }
        }
    }

    public function getSubNodeNames() {
        return array('name', 'extends', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Label extends Stmt
{
    /** @var string Name */
    public $name;

    /**
     * Constructs a label node.
     *
     * @param string $name       Name
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
    }

    public function getSubNodeNames() {
        return array('name');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Namespace_ extends Node\Stmt
{
    /** @var null|Node\Name Name */
    public $name;
    /** @var Node[] Statements */
    public $stmts;

    protected static $specialNames = array(
        'self'   => true,
        'parent' => true,
        'static' => true,
    );

    /**
     * Constructs a namespace node.
     *
     * @param null|Node\Name $name       Name
     * @param null|Node[]    $stmts      Statements
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->stmts = $stmts;

        if (isset(self::$specialNames[strtolower($this->name)])) {
            throw new Error(
                sprintf('Cannot use \'%s\' as namespace name', $this->name),
                $this->name->getAttributes()
            );
        }

        if (null !== $this->stmts) {
            foreach ($this->stmts as $stmt) {
                if ($stmt instanceof self) {
                    throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
                }
            }
        }
    }

    public function getSubNodeNames() {
        return array('name', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

/** Nop/empty statement (;). */
class Nop extends Node\Stmt
{
    public function getSubNodeNames() {
        return array();
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class Property extends Node\Stmt
{
    /** @var int Modifiers */
    public $type;
    /** @var PropertyProperty[] Properties */
    public $props;

    /**
     * Constructs a class property list node.
     *
     * @param int                $type       Modifiers
     * @param PropertyProperty[] $props      Properties
     * @param array              $attributes Additional attributes
     */
    public function __construct($type, array $props, array $attributes = array()) {
        if ($type & Class_::MODIFIER_ABSTRACT) {
            throw new Error('Properties cannot be declared abstract');
        }

        if ($type & Class_::MODIFIER_FINAL) {
            throw new Error('Properties cannot be declared final');
        }

        parent::__construct($attributes);
        $this->type = $type;
        $this->props = $props;
    }

    public function getSubNodeNames() {
        return array('type', 'props');
    }

    public function isPublic() {
        return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
            || ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
    }

    public function isProtected() {
        return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
    }

    public function isPrivate() {
        return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
    }

    public function isStatic() {
        return (bool) ($this->type & Class_::MODIFIER_STATIC);
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class PropertyProperty extends Node\Stmt
{
    /** @var string Name */
    public $name;
    /** @var null|Node\Expr Default */
    public $default;

    /**
     * Constructs a class property node.
     *
     * @param string         $name       Name
     * @param null|Node\Expr $default    Default value
     * @param array          $attributes Additional attributes
     */
    public function __construct($name, Node\Expr $default = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->default = $default;
    }

    public function getSubNodeNames() {
        return array('name', 'default');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Return_ extends Node\Stmt
{
    /** @var null|Node\Expr Expression */
    public $expr;

    /**
     * Constructs a return node.
     *
     * @param null|Node\Expr $expr       Expression
     * @param array          $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class StaticVar extends Node\Stmt
{
    /** @var string Name */
    public $name;
    /** @var null|Node\Expr Default value */
    public $default;

    /**
     * Constructs a static variable node.
     *
     * @param string         $name       Name
     * @param null|Node\Expr $default    Default value
     * @param array          $attributes Additional attributes
     */
    public function __construct($name, Node\Expr $default = null, array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->default = $default;
    }

    public function getSubNodeNames() {
        return array('name', 'default');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Static_ extends Stmt
{
    /** @var StaticVar[] Variable definitions */
    public $vars;

    /**
     * Constructs a static variables list node.
     *
     * @param StaticVar[] $vars       Variable definitions
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct($attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Switch_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Case_[] Case list */
    public $cases;

    /**
     * Constructs a case node.
     *
     * @param Node\Expr $cond       Condition
     * @param Case_[]   $cases      Case list
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $cases, array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->cases = $cases;
    }

    public function getSubNodeNames() {
        return array('cond', 'cases');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Throw_ extends Node\Stmt
{
    /** @var Node\Expr Expression */
    public $expr;

    /**
     * Constructs a throw node.
     *
     * @param Node\Expr $expr       Expression
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $expr, array $attributes = array()) {
        parent::__construct($attributes);
        $this->expr = $expr;
    }

    public function getSubNodeNames() {
        return array('expr');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;


class TraitUse extends Node\Stmt
{
    /** @var Node\Name[] Traits */
    public $traits;
    /** @var TraitUseAdaptation[] Adaptations */
    public $adaptations;

    /**
     * Constructs a trait use node.
     *
     * @param Node\Name[]          $traits      Traits
     * @param TraitUseAdaptation[] $adaptations Adaptations
     * @param array                $attributes  Additional attributes
     */
    public function __construct(array $traits, array $adaptations = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->traits = $traits;
        $this->adaptations = $adaptations;
    }

    public function getSubNodeNames() {
        return array('traits', 'adaptations');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

abstract class TraitUseAdaptation extends Node\Stmt
{
    /** @var Node\Name Trait name */
    public $trait;
    /** @var string Method name */
    public $method;
}
<?php

namespace PhpParser\Node\Stmt\TraitUseAdaptation;

use PhpParser\Node;

class Alias extends Node\Stmt\TraitUseAdaptation
{
    /** @var null|int New modifier */
    public $newModifier;
    /** @var null|string New name */
    public $newName;

    /**
     * Constructs a trait use precedence adaptation node.
     *
     * @param null|Node\Name $trait       Trait name
     * @param string         $method      Method name
     * @param null|int       $newModifier New modifier
     * @param null|string    $newName     New name
     * @param array          $attributes  Additional attributes
     */
    public function __construct($trait, $method, $newModifier, $newName, array $attributes = array()) {
        parent::__construct($attributes);
        $this->trait = $trait;
        $this->method = $method;
        $this->newModifier = $newModifier;
        $this->newName = $newName;
    }

    public function getSubNodeNames() {
        return array('trait', 'method', 'newModifier', 'newName');
    }
}
<?php

namespace PhpParser\Node\Stmt\TraitUseAdaptation;

use PhpParser\Node;

class Precedence extends Node\Stmt\TraitUseAdaptation
{
    /** @var Node\Name[] Overwritten traits */
    public $insteadof;

    /**
     * Constructs a trait use precedence adaptation node.
     *
     * @param Node\Name   $trait       Trait name
     * @param string      $method      Method name
     * @param Node\Name[] $insteadof   Overwritten traits
     * @param array       $attributes  Additional attributes
     */
    public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = array()) {
        parent::__construct($attributes);
        $this->trait = $trait;
        $this->method = $method;
        $this->insteadof = $insteadof;
    }

    public function getSubNodeNames() {
        return array('trait', 'method', 'insteadof');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Trait_ extends ClassLike
{
    /**
     * Constructs a trait node.
     *
     * @param string $name       Name
     * @param Node[] $stmts      Statements
     * @param array  $attributes Additional attributes
     */
    public function __construct($name, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->name = $name;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('name', 'stmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class TryCatch extends Node\Stmt
{
    /** @var Node[] Statements */
    public $stmts;
    /** @var Catch_[] Catches */
    public $catches;
    /** @var null|Node[] Finally statements */
    public $finallyStmts;

    /**
     * Constructs a try catch node.
     *
     * @param Node[]      $stmts        Statements
     * @param Catch_[]    $catches      Catches
     * @param null|Node[] $finallyStmts Finally statements (null means no finally clause)
     * @param array|null  $attributes   Additional attributes
     */
    public function __construct(array $stmts, array $catches, array $finallyStmts = null, array $attributes = array()) {
        if (empty($catches) && null === $finallyStmts) {
            throw new Error('Cannot use try without catch or finally');
        }

        parent::__construct($attributes);
        $this->stmts = $stmts;
        $this->catches = $catches;
        $this->finallyStmts = $finallyStmts;
    }

    public function getSubNodeNames() {
        return array('stmts', 'catches', 'finallyStmts');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class Unset_ extends Node\Stmt
{
    /** @var Node\Expr[] Variables to unset */
    public $vars;

    /**
     * Constructs an unset node.
     *
     * @param Node\Expr[] $vars       Variables to unset
     * @param array       $attributes Additional attributes
     */
    public function __construct(array $vars, array $attributes = array()) {
        parent::__construct($attributes);
        $this->vars = $vars;
    }

    public function getSubNodeNames() {
        return array('vars');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;
use PhpParser\Error;

class UseUse extends Node\Stmt
{
    /** @var int One of the Stmt\Use_::TYPE_* constants. Will only differ from TYPE_UNKNOWN for mixed group uses */
    public $type;
    /** @var Node\Name Namespace, class, function or constant to alias */
    public $name;
    /** @var string Alias */
    public $alias;

    /**
     * Constructs an alias (use) node.
     *
     * @param Node\Name   $name       Namespace/Class to alias
     * @param null|string $alias      Alias
     * @param int         $type       Type of the use element (for mixed group use declarations only)
     * @param array       $attributes Additional attributes
     */
    public function __construct(Node\Name $name, $alias = null, $type = Use_::TYPE_UNKNOWN, array $attributes = array()) {
        if (null === $alias) {
            $alias = $name->getLast();
        }

        if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
            throw new Error(sprintf(
                'Cannot use %s as %s because \'%2$s\' is a special class name',
                $name, $alias
            ));
        }

        parent::__construct($attributes);
        $this->type = $type;
        $this->name = $name;
        $this->alias = $alias;
    }

    public function getSubNodeNames() {
        return array('type', 'name', 'alias');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node\Stmt;

class Use_ extends Stmt
{
    /**
     * Unknown type. Both Stmt\Use_ / Stmt\GroupUse and Stmt\UseUse have a $type property, one of them will always be
     * TYPE_UNKNOWN while the other has one of the three other possible types. For normal use statements the type on the
     * Stmt\UseUse is unknown. It's only the other way around for mixed group use declarations.
     */
    const TYPE_UNKNOWN = 0;
    /** Class or namespace import */
    const TYPE_NORMAL = 1;
    /** Function import */
    const TYPE_FUNCTION = 2;
    /** Constant import */
    const TYPE_CONSTANT = 3;

    /** @var int Type of alias */
    public $type;
    /** @var UseUse[] Aliases */
    public $uses;

    /**
     * Constructs an alias (use) list node.
     *
     * @param UseUse[] $uses       Aliases
     * @param int      $type       Type of alias
     * @param array    $attributes Additional attributes
     */
    public function __construct(array $uses, $type = self::TYPE_NORMAL, array $attributes = array()) {
        parent::__construct($attributes);
        $this->type = $type;
        $this->uses = $uses;
    }

    public function getSubNodeNames() {
        return array('type', 'uses');
    }
}
<?php

namespace PhpParser\Node\Stmt;

use PhpParser\Node;

class While_ extends Node\Stmt
{
    /** @var Node\Expr Condition */
    public $cond;
    /** @var Node[] Statements */
    public $stmts;

    /**
     * Constructs a while node.
     *
     * @param Node\Expr $cond       Condition
     * @param Node[]    $stmts      Statements
     * @param array     $attributes Additional attributes
     */
    public function __construct(Node\Expr $cond, array $stmts = array(), array $attributes = array()) {
        parent::__construct($attributes);
        $this->cond = $cond;
        $this->stmts = $stmts;
    }

    public function getSubNodeNames() {
        return array('cond', 'stmts');
    }
}
<?php

namespace PhpParser;

abstract class NodeAbstract implements Node
{
    protected $attributes;

    /**
     * Creates a Node.
     *
     * @param array $attributes Array of attributes
     */
    public function __construct(array $attributes = array()) {
        $this->attributes = $attributes;
    }

    /**
     * Gets the type of the node.
     *
     * @return string Type of the node
     */
    public function getType() {
        return strtr(substr(rtrim(get_class($this), '_'), 15), '\\', '_');
    }

    /**
     * Gets line the node started in.
     *
     * @return int Line
     */
    public function getLine() {
        return $this->getAttribute('startLine', -1);
    }

    /**
     * Sets line the node started in.
     *
     * @param int $line Line
     */
    public function setLine($line) {
        $this->setAttribute('startLine', (int) $line);
    }

    /**
     * Gets the doc comment of the node.
     *
     * The doc comment has to be the last comment associated with the node.
     *
     * @return null|Comment\Doc Doc comment object or null
     */
    public function getDocComment() {
        $comments = $this->getAttribute('comments');
        if (!$comments) {
            return null;
        }

        $lastComment = $comments[count($comments) - 1];
        if (!$lastComment instanceof Comment\Doc) {
            return null;
        }

        return $lastComment;
    }

    public function setAttribute($key, $value) {
        $this->attributes[$key] = $value;
    }

    public function hasAttribute($key) {
        return array_key_exists($key, $this->attributes);
    }

    public function &getAttribute($key, $default = null) {
        if (!array_key_exists($key, $this->attributes)) {
            return $default;
        } else {
            return $this->attributes[$key];
        }
    }

    public function getAttributes() {
        return $this->attributes;
    }
}
<?php

namespace PhpParser;

class NodeDumper
{
    private $dumpComments;

    /**
     * Constructs a NodeDumper.
     *
     * @param array $options Boolean option 'dumpComments' controls whether comments should be
     *                       dumped
     */
    public function __construct(array $options = []) {
        $this->dumpComments = !empty($options['dumpComments']);
    }

    /**
     * Dumps a node or array.
     *
     * @param array|Node $node Node or array to dump
     *
     * @return string Dumped value
     */
    public function dump($node) {
        if ($node instanceof Node) {
            $r = $node->getType() . '(';

            foreach ($node->getSubNodeNames() as $key) {
                $r .= "\n    " . $key . ': ';

                $value = $node->$key;
                if (null === $value) {
                    $r .= 'null';
                } elseif (false === $value) {
                    $r .= 'false';
                } elseif (true === $value) {
                    $r .= 'true';
                } elseif (is_scalar($value)) {
                    $r .= $value;
                } else {
                    $r .= str_replace("\n", "\n    ", $this->dump($value));
                }
            }

            if ($this->dumpComments && $comments = $node->getAttribute('comments')) {
                $r .= "\n    comments: " . str_replace("\n", "\n    ", $this->dump($comments));
            }
        } elseif (is_array($node)) {
            $r = 'array(';

            foreach ($node as $key => $value) {
                $r .= "\n    " . $key . ': ';

                if (null === $value) {
                    $r .= 'null';
                } elseif (false === $value) {
                    $r .= 'false';
                } elseif (true === $value) {
                    $r .= 'true';
                } elseif (is_scalar($value)) {
                    $r .= $value;
                } else {
                    $r .= str_replace("\n", "\n    ", $this->dump($value));
                }
            }
        } elseif ($node instanceof Comment) {
            return $node->getReformattedText();
        } else {
            throw new \InvalidArgumentException('Can only dump nodes and arrays.');
        }

        return $r . "\n)";
    }
}
<?php

namespace PhpParser;

class NodeTraverser implements NodeTraverserInterface
{
    /**
     * @var NodeVisitor[] Visitors
     */
    protected $visitors;

    /**
     * @var bool
     */
    private $cloneNodes;

    /**
     * Constructs a node traverser.
     *
     * @param bool $cloneNodes Should the traverser clone the nodes when traversing the AST
     */
    public function __construct($cloneNodes = false) {
        $this->visitors = array();
        $this->cloneNodes = $cloneNodes;
    }

    /**
     * Adds a visitor.
     *
     * @param NodeVisitor $visitor Visitor to add
     */
    public function addVisitor(NodeVisitor $visitor) {
        $this->visitors[] = $visitor;
    }

    /**
     * Removes an added visitor.
     *
     * @param NodeVisitor $visitor
     */
    public function removeVisitor(NodeVisitor $visitor) {
        foreach ($this->visitors as $index => $storedVisitor) {
            if ($storedVisitor === $visitor) {
                unset($this->visitors[$index]);
                break;
            }
        }
    }

    /**
     * Traverses an array of nodes using the registered visitors.
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return Node[] Traversed array of nodes
     */
    public function traverse(array $nodes) {
        foreach ($this->visitors as $visitor) {
            if (null !== $return = $visitor->beforeTraverse($nodes)) {
                $nodes = $return;
            }
        }

        $nodes = $this->traverseArray($nodes);

        foreach ($this->visitors as $visitor) {
            if (null !== $return = $visitor->afterTraverse($nodes)) {
                $nodes = $return;
            }
        }

        return $nodes;
    }

    protected function traverseNode(Node $node) {
        if ($this->cloneNodes) {
            $node = clone $node;
        }

        foreach ($node->getSubNodeNames() as $name) {
            $subNode =& $node->$name;

            if (is_array($subNode)) {
                $subNode = $this->traverseArray($subNode);
            } elseif ($subNode instanceof Node) {
                $traverseChildren = true;
                foreach ($this->visitors as $visitor) {
                    $return = $visitor->enterNode($subNode);
                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
                        $traverseChildren = false;
                    } else if (null !== $return) {
                        $subNode = $return;
                    }
                }

                if ($traverseChildren) {
                    $subNode = $this->traverseNode($subNode);
                }

                foreach ($this->visitors as $visitor) {
                    if (null !== $return = $visitor->leaveNode($subNode)) {
                        if (is_array($return)) {
                            throw new \LogicException(
                                'leaveNode() may only return an array ' .
                                'if the parent structure is an array'
                            );
                        }
                        $subNode = $return;
                    }
                }
            }
        }

        return $node;
    }

    protected function traverseArray(array $nodes) {
        $doNodes = array();

        foreach ($nodes as $i => &$node) {
            if (is_array($node)) {
                $node = $this->traverseArray($node);
            } elseif ($node instanceof Node) {
                $traverseChildren = true;
                foreach ($this->visitors as $visitor) {
                    $return = $visitor->enterNode($node);
                    if (self::DONT_TRAVERSE_CHILDREN === $return) {
                        $traverseChildren = false;
                    } else if (null !== $return) {
                        $node = $return;
                    }
                }

                if ($traverseChildren) {
                    $node = $this->traverseNode($node);
                }

                foreach ($this->visitors as $visitor) {
                    $return = $visitor->leaveNode($node);

                    if (self::REMOVE_NODE === $return) {
                        $doNodes[] = array($i, array());
                        break;
                    } elseif (is_array($return)) {
                        $doNodes[] = array($i, $return);
                        break;
                    } elseif (null !== $return) {
                        $node = $return;
                    }
                }
            }
        }

        if (!empty($doNodes)) {
            while (list($i, $replace) = array_pop($doNodes)) {
                array_splice($nodes, $i, 1, $replace);
            }
        }

        return $nodes;
    }
}
<?php

namespace PhpParser;

interface NodeTraverserInterface
{
    /**
     * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
     * of the current node will not be traversed for any visitors.
     *
     * For subsequent visitors enterNode() will still be called on the current
     * node and leaveNode() will also be invoked for the current node.
     */
    const DONT_TRAVERSE_CHILDREN = 1;

    /**
     * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
     * in an array, it will be removed from the array.
     *
     * For subsequent visitors leaveNode() will still be invoked for the
     * removed node.
     */
    const REMOVE_NODE = false;

    /**
     * Adds a visitor.
     *
     * @param NodeVisitor $visitor Visitor to add
     */
    function addVisitor(NodeVisitor $visitor);

    /**
     * Removes an added visitor.
     *
     * @param NodeVisitor $visitor
     */
    function removeVisitor(NodeVisitor $visitor);

    /**
     * Traverses an array of nodes using the registered visitors.
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return Node[] Traversed array of nodes
     */
    function traverse(array $nodes);
}

<?php

namespace PhpParser;

interface NodeVisitor
{
    /**
     * Called once before traversal.
     *
     * Return value semantics:
     *  * null:      $nodes stays as-is
     *  * otherwise: $nodes is set to the return value
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return null|Node[] Array of nodes
     */
    public function beforeTraverse(array $nodes);

    /**
     * Called when entering a node.
     *
     * Return value semantics:
     *  * null:      $node stays as-is
     *  * otherwise: $node is set to the return value
     *
     * @param Node $node Node
     *
     * @return null|Node Node
     */
    public function enterNode(Node $node);

    /**
     * Called when leaving a node.
     *
     * Return value semantics:
     *  * null:      $node stays as-is
     *  * false:     $node is removed from the parent array
     *  * array:     The return value is merged into the parent array (at the position of the $node)
     *  * otherwise: $node is set to the return value
     *
     * @param Node $node Node
     *
     * @return null|Node|false|Node[] Node
     */
    public function leaveNode(Node $node);

    /**
     * Called once after traversal.
     *
     * Return value semantics:
     *  * null:      $nodes stays as-is
     *  * otherwise: $nodes is set to the return value
     *
     * @param Node[] $nodes Array of nodes
     *
     * @return null|Node[] Array of nodes
     */
    public function afterTraverse(array $nodes);
}<?php

namespace PhpParser\NodeVisitor;

use PhpParser\NodeVisitorAbstract;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;

class NameResolver extends NodeVisitorAbstract
{
    /** @var null|Name Current namespace */
    protected $namespace;

    /** @var array Map of format [aliasType => [aliasName => originalName]] */
    protected $aliases;

    public function beforeTraverse(array $nodes) {
        $this->resetState();
    }

    public function enterNode(Node $node) {
        if ($node instanceof Stmt\Namespace_) {
            $this->resetState($node->name);
        } elseif ($node instanceof Stmt\Use_) {
            foreach ($node->uses as $use) {
                $this->addAlias($use, $node->type, null);
            }
        } elseif ($node instanceof Stmt\GroupUse) {
            foreach ($node->uses as $use) {
                $this->addAlias($use, $node->type, $node->prefix);
            }
        } elseif ($node instanceof Stmt\Class_) {
            if (null !== $node->extends) {
                $node->extends = $this->resolveClassName($node->extends);
            }

            foreach ($node->implements as &$interface) {
                $interface = $this->resolveClassName($interface);
            }

            if (null !== $node->name) {
                $this->addNamespacedName($node);
            }
        } elseif ($node instanceof Stmt\Interface_) {
            foreach ($node->extends as &$interface) {
                $interface = $this->resolveClassName($interface);
            }

            $this->addNamespacedName($node);
        } elseif ($node instanceof Stmt\Trait_) {
            $this->addNamespacedName($node);
        } elseif ($node instanceof Stmt\Function_) {
            $this->addNamespacedName($node);
            $this->resolveSignature($node);
        } elseif ($node instanceof Stmt\ClassMethod
                  || $node instanceof Expr\Closure
        ) {
            $this->resolveSignature($node);
        } elseif ($node instanceof Stmt\Const_) {
            foreach ($node->consts as $const) {
                $this->addNamespacedName($const);
            }
        } elseif ($node instanceof Expr\StaticCall
                  || $node instanceof Expr\StaticPropertyFetch
                  || $node instanceof Expr\ClassConstFetch
                  || $node instanceof Expr\New_
                  || $node instanceof Expr\Instanceof_
        ) {
            if ($node->class instanceof Name) {
                $node->class = $this->resolveClassName($node->class);
            }
        } elseif ($node instanceof Stmt\Catch_) {
            $node->type = $this->resolveClassName($node->type);
        } elseif ($node instanceof Expr\FuncCall) {
            if ($node->name instanceof Name) {
                $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
            }
        } elseif ($node instanceof Expr\ConstFetch) {
            $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
        } elseif ($node instanceof Stmt\TraitUse) {
            foreach ($node->traits as &$trait) {
                $trait = $this->resolveClassName($trait);
            }

            foreach ($node->adaptations as $adaptation) {
                if (null !== $adaptation->trait) {
                    $adaptation->trait = $this->resolveClassName($adaptation->trait);
                }

                if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
                    foreach ($adaptation->insteadof as &$insteadof) {
                        $insteadof = $this->resolveClassName($insteadof);
                    }
                }
            }

        }
    }

    protected function resetState(Name $namespace = null) {
        $this->namespace = $namespace;
        $this->aliases   = array(
            Stmt\Use_::TYPE_NORMAL   => array(),
            Stmt\Use_::TYPE_FUNCTION => array(),
            Stmt\Use_::TYPE_CONSTANT => array(),
        );
    }

    protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
        // Add prefix for group uses
        $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
        // Type is determined either by individual element or whole use declaration
        $type |= $use->type;

        // Constant names are case sensitive, everything else case insensitive
        if ($type === Stmt\Use_::TYPE_CONSTANT) {
            $aliasName = $use->alias;
        } else {
            $aliasName = strtolower($use->alias);
        }

        if (isset($this->aliases[$type][$aliasName])) {
            $typeStringMap = array(
                Stmt\Use_::TYPE_NORMAL   => '',
                Stmt\Use_::TYPE_FUNCTION => 'function ',
                Stmt\Use_::TYPE_CONSTANT => 'const ',
            );

            throw new Error(
                sprintf(
                    'Cannot use %s%s as %s because the name is already in use',
                    $typeStringMap[$type], $name, $use->alias
                ),
                $use->getLine()
            );
        }

        $this->aliases[$type][$aliasName] = $name;
    }

    /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
    private function resolveSignature($node) {
        foreach ($node->params as $param) {
            if ($param->type instanceof Name) {
                $param->type = $this->resolveClassName($param->type);
            }
        }
        if ($node->returnType instanceof Name) {
            $node->returnType = $this->resolveClassName($node->returnType);
        }
    }

    protected function resolveClassName(Name $name) {
        // don't resolve special class names
        if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
            if (!$name->isUnqualified()) {
                throw new Error(
                    sprintf("'\\%s' is an invalid class name", $name->toString()),
                    $name->getLine()
                );
            }

            return $name;
        }

        // fully qualified names are already resolved
        if ($name->isFullyQualified()) {
            return $name;
        }

        $aliasName = strtolower($name->getFirst());
        if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
            // resolve aliases (for non-relative names)
            $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
            return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
        }

        if (null !== $this->namespace) {
            // if no alias exists prepend current namespace
            return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
        }

        return new FullyQualified($name->parts, $name->getAttributes());
    }

    protected function resolveOtherName(Name $name, $type) {
        // fully qualified names are already resolved
        if ($name->isFullyQualified()) {
            return $name;
        }

        // resolve aliases for qualified names
        $aliasName = strtolower($name->getFirst());
        if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
            $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
            return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
        }

        if ($name->isUnqualified()) {
            if ($type === Stmt\Use_::TYPE_CONSTANT) {
                // constant aliases are case-sensitive, function aliases case-insensitive
                $aliasName = $name->getFirst();
            }

            if (!isset($this->aliases[$type][$aliasName])) {
                // unqualified, unaliased names cannot be resolved at compile-time
                return $name;
            }

            // resolve unqualified aliases
            return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
        }

        if (null !== $this->namespace) {
            // if no alias exists prepend current namespace
            return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
        }

        return new FullyQualified($name->parts, $name->getAttributes());
    }

    protected function addNamespacedName(Node $node) {
        if (null !== $this->namespace) {
            $node->namespacedName = Name::concat($this->namespace, $node->name);
        } else {
            $node->namespacedName = new Name($node->name);
        }
    }
}
<?php

namespace PhpParser;

/**
 * @codeCoverageIgnore
 */
class NodeVisitorAbstract implements NodeVisitor
{
    public function beforeTraverse(array $nodes)    { }
    public function enterNode(Node $node) { }
    public function leaveNode(Node $node) { }
    public function afterTraverse(array $nodes)     { }
}<?php

namespace PhpParser;

interface Parser {
    /**
     * Parses PHP code into a node tree.
     *
     * @param string $code The source code to parse
     *
     * @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
     *                     unable to recover from an error).
     */
    public function parse($code);

    /**
     * Get array of errors that occurred during the last parse.
     *
     * This method may only return multiple errors if the 'throwOnError' option is disabled.
     *
     * @return Error[]
     */
    public function getErrors();
}
<?php

namespace PhpParser\Parser;

use PhpParser\Error;
use PhpParser\Parser;

class Multiple implements Parser {
    /** @var Parser[] List of parsers to try, in order of preference */
    private $parsers;
    /** @var Error[] Errors collected during last parse */
    private $errors;

    /**
     * Create a parser which will try multiple parsers in an order of preference.
     *
     * Parsers will be invoked in the order they're provided to the constructor. If one of the
     * parsers runs without errors, it's output is returned. Otherwise the errors (and
     * PhpParser\Error exception) of the first parser are used.
     *
     * @param Parser[] $parsers
     */
    public function __construct(array $parsers) {
        $this->parsers = $parsers;
        $this->errors = [];
    }

    public function parse($code) {
        list($firstStmts, $firstErrors, $firstError) = $this->tryParse($this->parsers[0], $code);
        if ($firstErrors === []) {
            $this->errors = [];
            return $firstStmts;
        }

        for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
            list($stmts, $errors) = $this->tryParse($this->parsers[$i], $code);
            if ($errors === []) {
                $this->errors = [];
                return $stmts;
            }
        }

        $this->errors = $firstErrors;
        if ($firstError) {
            throw $firstError;
        }
        return $firstStmts;
    }

    public function getErrors() {
        return $this->errors;
    }

    private function tryParse(Parser $parser, $code) {
        $stmts = null;
        $error = null;
        try {
            $stmts = $parser->parse($code);
        } catch (Error $error) {}
        $errors = $parser->getErrors();
        return [$stmts, $errors, $error];
    }
}
<?php

namespace PhpParser\Parser;

use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;

/* This is an automatically GENERATED file, which should not be manually edited.
 * Instead edit one of the following:
 *  * the grammar files grammar/php5.y or grammar/php7.y
 *  * the skeleton file grammar/parser.template
 *  * the preprocessing script grammar/rebuildParsers.php
 */
class Php5 extends \PhpParser\ParserAbstract
{
    protected $tokenToSymbolMapSize = 392;
    protected $actionTableSize = 1012;
    protected $gotoTableSize = 649;

    protected $invalidSymbol = 157;
    protected $errorSymbol = 1;
    protected $defaultAction = -32766;
    protected $unexpectedTokenRule = 32767;

    protected $YY2TBLSTATE  = 405;
    protected $YYNLSTATES   = 667;

    protected $symbolToName = array(
        "EOF",
        "error",
        "T_INCLUDE",
        "T_INCLUDE_ONCE",
        "T_EVAL",
        "T_REQUIRE",
        "T_REQUIRE_ONCE",
        "','",
        "T_LOGICAL_OR",
        "T_LOGICAL_XOR",
        "T_LOGICAL_AND",
        "T_PRINT",
        "T_YIELD",
        "T_DOUBLE_ARROW",
        "T_YIELD_FROM",
        "'='",
        "T_PLUS_EQUAL",
        "T_MINUS_EQUAL",
        "T_MUL_EQUAL",
        "T_DIV_EQUAL",
        "T_CONCAT_EQUAL",
        "T_MOD_EQUAL",
        "T_AND_EQUAL",
        "T_OR_EQUAL",
        "T_XOR_EQUAL",
        "T_SL_EQUAL",
        "T_SR_EQUAL",
        "T_POW_EQUAL",
        "'?'",
        "':'",
        "T_COALESCE",
        "T_BOOLEAN_OR",
        "T_BOOLEAN_AND",
        "'|'",
        "'^'",
        "'&'",
        "T_IS_EQUAL",
        "T_IS_NOT_EQUAL",
        "T_IS_IDENTICAL",
        "T_IS_NOT_IDENTICAL",
        "T_SPACESHIP",
        "'<'",
        "T_IS_SMALLER_OR_EQUAL",
        "'>'",
        "T_IS_GREATER_OR_EQUAL",
        "T_SL",
        "T_SR",
        "'+'",
        "'-'",
        "'.'",
        "'*'",
        "'/'",
        "'%'",
        "'!'",
        "T_INSTANCEOF",
        "'~'",
        "T_INC",
        "T_DEC",
        "T_INT_CAST",
        "T_DOUBLE_CAST",
        "T_STRING_CAST",
        "T_ARRAY_CAST",
        "T_OBJECT_CAST",
        "T_BOOL_CAST",
        "T_UNSET_CAST",
        "'@'",
        "T_POW",
        "'['",
        "T_NEW",
        "T_CLONE",
        "T_EXIT",
        "T_IF",
        "T_ELSEIF",
        "T_ELSE",
        "T_ENDIF",
        "T_LNUMBER",
        "T_DNUMBER",
        "T_STRING",
        "T_STRING_VARNAME",
        "T_VARIABLE",
        "T_NUM_STRING",
        "T_INLINE_HTML",
        "T_ENCAPSED_AND_WHITESPACE",
        "T_CONSTANT_ENCAPSED_STRING",
        "T_ECHO",
        "T_DO",
        "T_WHILE",
        "T_ENDWHILE",
        "T_FOR",
        "T_ENDFOR",
        "T_FOREACH",
        "T_ENDFOREACH",
        "T_DECLARE",
        "T_ENDDECLARE",
        "T_AS",
        "T_SWITCH",
        "T_ENDSWITCH",
        "T_CASE",
        "T_DEFAULT",
        "T_BREAK",
        "T_CONTINUE",
        "T_GOTO",
        "T_FUNCTION",
        "T_CONST",
        "T_RETURN",
        "T_TRY",
        "T_CATCH",
        "T_FINALLY",
        "T_THROW",
        "T_USE",
        "T_INSTEADOF",
        "T_GLOBAL",
        "T_STATIC",
        "T_ABSTRACT",
        "T_FINAL",
        "T_PRIVATE",
        "T_PROTECTED",
        "T_PUBLIC",
        "T_VAR",
        "T_UNSET",
        "T_ISSET",
        "T_EMPTY",
        "T_HALT_COMPILER",
        "T_CLASS",
        "T_TRAIT",
        "T_INTERFACE",
        "T_EXTENDS",
        "T_IMPLEMENTS",
        "T_OBJECT_OPERATOR",
        "T_LIST",
        "T_ARRAY",
        "T_CALLABLE",
        "T_CLASS_C",
        "T_TRAIT_C",
        "T_METHOD_C",
        "T_FUNC_C",
        "T_LINE",
        "T_FILE",
        "T_START_HEREDOC",
        "T_END_HEREDOC",
        "T_DOLLAR_OPEN_CURLY_BRACES",
        "T_CURLY_OPEN",
        "T_PAAMAYIM_NEKUDOTAYIM",
        "T_NAMESPACE",
        "T_NS_C",
        "T_DIR",
        "T_NS_SEPARATOR",
        "T_ELLIPSIS",
        "';'",
        "'{'",
        "'}'",
        "'('",
        "')'",
        "'$'",
        "'`'",
        "']'",
        "'\"'"
    );

    protected $tokenToSymbol = array(
            0,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,   53,  156,  157,  153,   52,   35,  157,
          151,  152,   50,   47,    7,   48,   49,   51,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,   29,  148,
           41,   15,   43,   28,   65,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,   67,  157,  155,   34,  157,  154,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  149,   33,  150,   55,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,    1,    2,    3,    4,
            5,    6,    8,    9,   10,   11,   12,   13,   14,   16,
           17,   18,   19,   20,   21,   22,   23,   24,   25,   26,
           27,   30,   31,   32,   36,   37,   38,   39,   40,   42,
           44,   45,   46,   54,   56,   57,   58,   59,   60,   61,
           62,   63,   64,   66,   68,   69,   70,   71,   72,   73,
           74,   75,   76,   77,   78,   79,   80,   81,  157,  157,
           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
          132,  133,  134,  135,  136,  137,  157,  157,  157,  157,
          157,  157,  138,  139,  140,  141,  142,  143,  144,  145,
          146,  147
    );

    protected $action = array(
          672,  673,  674,  675,  676,-32766,  677,  678,  679,  715,
          716,  216,  217,  218,  219,  220,  221,  222,  223,  224,
          282,  225,  226,  227,  228,  229,  230,  231,  232,  233,
          234,  235,  236,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
        -32766,-32767,-32767,-32767,-32767,  356,  237,  238,-32766,-32766,
        -32766,-32766,  680,-32766,    0,-32766,-32766,-32766,-32766,-32766,
        -32766,-32767,-32767,-32767,-32767,-32767,  681,  682,  683,  684,
          685,  686,  687, 1178,  204,  747,-32766,-32766,-32766,-32766,
        -32766,   23,  688,  689,  690,  691,  692,  693,  694,  695,
          696,  697,  698,  718,  719,  720,  721,  722,  710,  711,
          712,  713,  714,  699,  700,  701,  702,  703,  704,  705,
          741,  742,  743,  744,  745,  746,  706,  707,  708,  709,
          739,  730,  728,  729,  725,  726,   46,  717,  723,  724,
          731,  732,  734,  733,  735,  736,   52,   53,  420,   54,
           55,  727,  738,  737,  447,   56,   57,  332,   58,-32766,
        -32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,    7,-32767,
        -32767,-32767,-32767,   50,  329, 1185,  518,  945,  946,  947,
          944,  943,  942,  937, 1211,  306, 1213, 1212,  763,  764,
          821,   59,   60,-32766,-32766,-32766,  808,   61, 1172,   62,
          291,  292,   63,   64,   65,   66,   67,   68,   69,   70,
          441,   24,  299,   71,  413,-32766,-32766,-32766,  116, 1087,
         1088,  749,  633, 1178,  213,  214,  215,  464,-32766,-32766,
        -32766,  822,  407, 1099,  321,-32766,  900,-32766,-32766,-32766,
        -32766,-32766,-32766, 1036,  200, -269,  428,   27,-32766,  419,
        -32766,-32766,-32766,-32766,-32766,  120,  491,  945,  946,  947,
          944,  943,  942,  297,  473,  474,  283,  623,  125,-32766,
          893,  894,  339,  477,  478,-32766, 1093, 1094, 1095, 1096,
         1090, 1091,  307,  492,-32766,    8,  425,  492, 1097, 1092,
          425,  121, -220,  869,  460,   39,  412,  332,  318,   18,
          319,  421, -122, -122, -122,   -4,  822,  463,   99,  100,
          101,  811,  301, 1036,   38,   19,  422, -122,  465, -122,
          466, -122,  467, -122,  102,  423, -122, -122, -122,   28,
           29,  468,  424,  624,   30,  469,  425,  812,   72,  348,
          923,  349,  350,  470,  471,-32766,-32766,-32766,  298,  472,
         1036,  809,  793,  840,  475,  476,-32767,-32767,-32767,-32767,
           94,   95,   96,   97,   98,-32766,  440,-32766,-32766,-32766,
        -32766, 1137,  213,  214,  215,  295,  421,  239,  824,  638,
         -122,  280,  463,  893,  894,  367,  811, 1036, 1203,   38,
           19,  422,  200,  465, 1054,  466,  492,  467,  127,  425,
          423,  213,  214,  215,   28,   29,  468,  424,  414,   30,
          469, 1036,  870,   72,  317,  822,  349,  350,  470,  471,
         1036,  200,  214,  215,  472, 1182,  919,  755,  840,  475,
          476,  213,  214,  215,  295,  918,   76,   77,   78,   47,
          338,  200,  477,  644,  326,  438,   31,  294,  331,  805,
          334,  200,  241,  824,  638,   -4,   32,  119,   79,   80,
           81,   82,   83,   84,   85,   86,   87,   88,   89,   90,
           91,   92,   93,   94,   95,   96,   97,   98,   99,  100,
          101, 1208,  301,  242,  822,  421,  801,  124,-32766,-32766,
        -32766,  463,  899,  207,  102,  811,  909,  126,   38,   19,
          422,  545,  465, 1172,  466,   34,  467,  762,-32766,  423,
        -32766,-32766,  647,   28,   29,  468,  822, 1036,   30,  469,
         -216,  117,   72,  803,   49,  349,  350,-32766,-32766,-32766,
        -32766,-32766,-32766,  472,  123, 1036,  234,  235,  236,  213,
          214,  215, 1036,  115,  641, 1138,  124,-32766,  200,-32766,
        -32766,-32766,  237,  238,  421,   96,   97,   98,  293,  200,
          463,  585,  856,  638,  811,  439, 1036,   38,   19,  422,
          284,  465,  215,  466,  749,  467, 1178,  339,  423,  231,
          232,  233,   28,   29,  468,  822,  421,   30,  469,  296,
          200,   72,  463,  415,  349,  350,  811,-32766,-32766,   38,
           19,  422,  472,  465,-32766,  466,  118,  467,  377, 1064,
          423,-32766,-32766,  642,   28,   29,  468,  822, 1099,   30,
          469,-32766,  434,   72,  129,  640,  349,  350,  576,  205,
          492,  824,  638,  425,  472,  206,-32766,-32766,-32766,  244,
          492,  237,  238,  425,  243,  653,  449,   20,  429,  301,
          332,  454,  591,  130,  357,  421,-32766,  763,  764,  599,
          600,  463,  646,  824,  638,  811,  922,  666,   38,   19,
          422,  308,  465,  650,  466,  128,  467,  756,  643,  423,
          820,  934,  656,   28,   29,  468,  822,  421,   30,  469,
          833,  102,   72,  463,   44,  349,  350,  811,   51,   48,
           38,   19,  422,  472,  465,   43,  466,   41,  467,  299,
           45,  423,   42,  605,  513,   28,   29,  468,-32766,  632,
           30,  469,  579,  432,   72,  749,  750,  349,  350,  534,
          512,  435,  824,  638, 1206,  472,  433,   33,  103,  104,
          105,  106,  107,  108,  109,  110,  111,  112,  113,  114,
          533,  776,  517,  524,  437,  622,  421, 1057,  612,  516,
          602,  619,  463,  279,  824,  638,  811,  458,  595,   38,
           19,  422,  596,  465,  330,  466,  240,  467,  975,  977,
          423,  609,  582,  -80,   28,   29,  468,  537,   12,   30,
          469,  477,  327,   72,  208,  209,  349,  350,    9, 1098,
          210,  303,  211,  333,  472,  842,  841,  384,  757,  370,
            0,  328,    0,    0,  202,  322,    0,    0, -497,  208,
          209,    0, 1087, 1088,  320,  210,-32766,  211, -498,    0,
         1089, 1144,    0,  824,  638,    0,    0,    4,  835,  202,
         -398, -407,    0,    3,   11, -406,   75, 1087, 1088,    0,
         -497,-32766,  409,  393,  408, 1089,  385,  434,  526,  372,
          302, 1143,  864,  863,  796,  857,  813,  798,  819,  807,
            0,  761,  661,  660,   37,   36,  926,  565,  810, 1093,
         1094, 1095, 1096, 1090, 1091,  383,  854,  852,  929,  804,
          759, 1097, 1092,  806,  818,  290,  760,  928,  212,  802,
        -32766,  930,  565,  927, 1093, 1094, 1095, 1096, 1090, 1091,
          383,  872, 1209,  639,  649,  651, 1097, 1092,  652,  654,
          655, 1034,  658,  212,  663,-32766,  664,  665,  122,  324,
          325,  405,  406,    0,  758, 1210,  839,  838,  766,  453,
         1207, 1179, 1177, 1163, 1175, 1078,  911, 1183, 1173,  829,
          836, 1038, 1039,  827,  935,  794,  765,  837,  662, 1050,
          861,  768,  767,  862,    0,  304,  289,  281,   25,   26,
          203,  305,  335,   74,   73,  411,  417,   35,   40,-32766,
           22,    0, 1015,  569, -217, 1016, 1103,  901, 1080, 1044,
         1040, 1041,  629,  559,  461,  457,  455,  450,  378,   16,
           15,   14, -216,    0,    0, -416,    0, 1045,  603, 1157,
         1104, 1205, 1077, 1174, 1158, 1162, 1176, 1063, 1048, 1049,
         1046, 1047
    );

    protected $actionCheck = array(
            2,    3,    4,    5,    6,    8,    8,    9,   10,   11,
           12,   31,   32,   33,   34,   35,   36,   37,   38,   39,
            7,   41,   42,   43,   44,   45,   46,   47,   48,   49,
           50,   51,   52,    8,    9,   10,   31,   32,   33,   34,
           35,   36,   37,   38,   39,    7,   66,   67,   31,   32,
           33,   34,   54,   28,    0,   30,   31,   32,   33,   34,
           35,   36,   37,   38,   39,   40,   68,   69,   70,   71,
           72,   73,   74,   79,    7,   77,   31,   32,   33,   34,
           35,    7,   84,   85,   86,   87,   88,   89,   90,   91,
           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
          122,  123,  124,  125,  126,  127,   67,  129,  130,  131,
          132,  133,  134,  135,  136,  137,    2,    3,    4,    5,
            6,  143,  144,  145,    7,   11,   12,  153,   14,   31,
           32,   33,   34,   35,   36,   37,   38,   39,  103,   41,
           42,   43,   44,   67,  109,  152,   82,  112,  113,  114,
          115,  116,  117,  118,   77,    7,   79,   80,  102,  103,
            1,   47,   48,    8,    9,   10,  148,   53,   79,   55,
           56,   57,   58,   59,   60,   61,   62,   63,   64,   65,
            7,   67,   68,   69,   70,    8,    9,   10,  149,   75,
           76,   77,   77,   79,    8,    9,   10,   83,    8,    9,
           10,    1,  146,  139,  128,   28,  152,   30,   31,   32,
           33,   34,   35,   12,   28,   79,  102,    7,   28,    7,
           30,   31,   32,   33,   34,  149,  112,  112,  113,  114,
          115,  116,  117,    7,  120,  121,   35,   77,  149,  103,
          130,  131,  153,  129,  130,  109,  132,  133,  134,  135,
          136,  137,  138,  143,  118,    7,  146,  143,  144,  145,
          146,    7,  152,   29,    7,  151,    7,  153,  154,  152,
          156,   71,   72,   73,   74,    0,    1,   77,   50,   51,
           52,   81,   54,   12,   84,   85,   86,   87,   88,   89,
           90,   91,   92,   93,   66,   95,   96,   97,   98,   99,
          100,  101,  102,  143,  104,  105,  146,  148,  108,    7,
          150,  111,  112,  113,  114,    8,    9,   10,   35,  119,
           12,  148,  122,  123,  124,  125,   41,   42,   43,   44,
           45,   46,   47,   48,   49,   28,    7,   30,   31,   32,
           33,  155,    8,    9,   10,   35,   71,   13,  148,  149,
          150,   13,   77,  130,  131,   79,   81,   12,   82,   84,
           85,   86,   28,   88,  152,   90,  143,   92,   67,  146,
           95,    8,    9,   10,   99,  100,  101,  102,  103,  104,
          105,   12,  148,  108,  109,    1,  111,  112,  113,  114,
           12,   28,    9,   10,  119,   77,  148,  122,  123,  124,
          125,    8,    9,   10,   35,  148,    8,    9,   10,   67,
           67,   28,  129,   29,    7,   29,  140,  141,  143,  148,
            7,   28,   29,  148,  149,  150,   28,   13,   30,   31,
           32,   33,   34,   35,   36,   37,   38,   39,   40,   41,
           42,   43,   44,   45,   46,   47,   48,   49,   50,   51,
           52,  150,   54,   15,    1,   71,  148,  147,    8,    9,
           10,   77,  152,   15,   66,   81,   79,  149,   84,   85,
           86,  128,   88,   79,   90,   13,   92,  148,   28,   95,
           30,   31,   29,   99,  100,  101,    1,   12,  104,  105,
          152,  149,  108,  148,   67,  111,  112,    8,    9,   10,
           31,   32,   33,  119,   29,   12,   50,   51,   52,    8,
            9,   10,   12,   15,   29,  152,  147,   28,   28,   30,
           31,   32,   66,   67,   71,   47,   48,   49,   35,   28,
           77,   82,  148,  149,   81,  149,   12,   84,   85,   86,
          153,   88,   10,   90,   77,   92,   79,  153,   95,   47,
           48,   49,   99,  100,  101,    1,   71,  104,  105,   35,
           28,  108,   77,  123,  111,  112,   81,    8,    9,   84,
           85,   86,  119,   88,   31,   90,  149,   92,   78,  112,
           95,   31,   32,   29,   99,  100,  101,    1,  139,  104,
          105,  151,  146,  108,  149,  149,  111,  112,  153,   15,
          143,  148,  149,  146,  119,   15,    8,    9,   10,   15,
          143,   66,   67,  146,   15,   29,   72,   73,  151,   54,
          153,   72,   73,   97,   98,   71,   28,  102,  103,  106,
          107,   77,   29,  148,  149,   81,  148,  149,   84,   85,
           86,   29,   88,   29,   90,   29,   92,  148,  149,   95,
           29,  148,  149,   99,  100,  101,    1,   71,  104,  105,
           35,   66,  108,   77,   67,  111,  112,   81,   67,   67,
           84,   85,   86,  119,   88,   67,   90,   67,   92,   68,
           67,   95,   67,   74,   77,   99,  100,  101,   82,   89,
          104,  105,   87,  102,  108,   77,   77,  111,  112,   77,
           77,   77,  148,  149,   77,  119,   77,   15,   16,   17,
           18,   19,   20,   21,   22,   23,   24,   25,   26,   27,
           77,   77,   77,   82,   86,   79,   71,   79,   79,   79,
           79,   91,   77,   94,  148,  149,   81,  102,   96,   84,
           85,   86,  109,   88,  110,   90,   29,   92,   56,   57,
           95,   93,   96,   94,   99,  100,  101,   94,   94,  104,
          105,  129,  126,  108,   47,   48,  111,  112,  142,  139,
           53,  151,   55,  126,  119,  123,  123,  146,  150,  142,
           -1,  127,   -1,   -1,   67,  128,   -1,   -1,  128,   47,
           48,   -1,   75,   76,  128,   53,   79,   55,  128,   -1,
           83,  139,   -1,  148,  149,   -1,   -1,  142,  147,   67,
          142,  142,   -1,  142,  142,  142,  149,   75,   76,   -1,
          128,   79,  146,  146,  146,   83,  146,  146,  146,  146,
          151,  156,  148,  148,  148,  148,  148,  148,  148,  148,
           -1,  148,  148,  148,  148,  148,  148,  130,  148,  132,
          133,  134,  135,  136,  137,  138,  148,  148,  148,  148,
          148,  144,  145,  148,  148,  151,  148,  148,  151,  148,
          153,  148,  130,  148,  132,  133,  134,  135,  136,  137,
          138,  148,  150,  149,  149,  149,  144,  145,  149,  149,
          149,  154,  149,  151,  149,  153,  149,  149,  149,  149,
          149,  149,  149,   -1,  150,  150,  150,  150,  150,  150,
          150,  150,  150,  150,  150,  150,  150,  150,  150,  150,
          150,  150,  150,  150,  150,  150,  150,  150,  150,  150,
          150,  150,  150,  150,   -1,  151,  151,  151,  151,  151,
          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
          151,   -1,  152,  152,  152,  152,  152,  152,  152,  152,
          152,  152,  152,  152,  152,  152,  152,  152,  152,  152,
          152,  152,  152,   -1,   -1,  154,   -1,  155,  155,  155,
          155,  155,  155,  155,  155,  155,  155,  155,  155,  155,
          155,  155
    );

    protected $actionBase = array(
            0,  220,  295,  109,  109,  180,  739,   -2,   -2,   -2,
           -2,   -2,  135,  574,  473,  606,  473,  505,  404,  675,
          675,  675,  330,  389,  513,  513,  826,  513,  328,  365,
          291,  520,  495,  221,  544,  398,  398,  398,  398,  134,
          134,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  398,  398,  398,  398,  398,  398,  398,  398,  398,
          398,  254,  179,  434,  482,  741,  731,  735,  736,  828,
          659,  823,  780,  781,  636,  782,  783,  784,  785,  786,
          779,  787,  843,  788,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,   -3,  354,  383,  413,  206,
          579,  521,  521,  521,  521,  521,  521,  521,  175,  175,
          175,  175,  175,  175,  175,  175,  175,  175,  175,  175,
          175,  175,  175,  175,  175,  403,  618,  618,  618,  552,
          737,  510,  762,  762,  762,  762,  762,  762,  762,  762,
          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
          762,  762,  762,  762,  762,  762,  762,  762,  762,  762,
          762,  762,  762,  762,  762,  470,  -20,  -20,  509,  563,
          327,  570,  210,  489,  197,   25,   25,   25,   25,   25,
           17,   45,    5,    5,    5,    5,  712,  305,  305,  305,
          305,  118,  118,  118,  118,  776,  777,  797,  799,  303,
          303,  652,  652,  631,  769,  498,  498,  522,  522,  487,
          487,  487,  487,  487,  487,  487,  487,  487,  487,  460,
          156,  818,  130,  130,  130,  130,  243,   84,  243,  682,
          695,  248,  248,  248,  476,  476,  476,   76,  661,  296,
          338,  338,  338,  296,  545,  545,  545,  477,  477,  477,
          477,  466,  687,  477,  477,  477,  362,  626,   97,  465,
          676,  800,  662,  803,  508,  689,   96,  698,  696,  407,
          611,  564,  569,  543,  688,  406,  407,  254,  523,  447,
          585,  720,  642,  349,  732,   38,  193,  363,  519,   59,
          414,  137,  770,  738,  821,  820,   13,  321,  690,  585,
          585,  585,   74,  469,  771,  772,   59,  358,  565,  565,
          565,  565,  802,  773,  565,  565,  565,  565,  801,  796,
          268,  277,  778,  232,  718,  638,  638,  638,  638,  638,
          638,  645,  638,  808,  627,  819,  819,  663,  671,  645,
          817,  817,  817,  817,  645,  638,  819,  819,  645,  631,
          819,  230,  645,  656,  638,  667,  667,  817,  715,  714,
          627,  670,  674,  819,  819,  819,  674,  663,  645,  817,
          653,  681,   67,  819,  817,  632,  632,  653,  645,  632,
          671,  632,   54,  641,  630,  816,  813,  815,  643,  754,
          673,  672,  805,  734,  812,  665,  649,  806,  807,  702,
          713,  711,  644,  518,  635,  628,  617,  633,  691,  622,
          686,  611,  701,  615,  615,  615,  680,  685,  680,  615,
          615,  615,  615,  615,  615,  615,  615,  842,  657,  693,
          677,  658,  710,  604,  703,  683,  610,  763,  650,  702,
          702,  795,  829,  836,  841,  757,  639,  699,  831,  680,
          856,  717,  274,  468,  640,  798,  651,  664,  700,  680,
          804,  680,  765,  680,  827,  647,  775,  702,  774,  615,
          825,  855,  854,  853,  852,  851,  850,  849,  848,  621,
          847,  709,  625,  835,  168,  809,  688,  646,  697,  708,
          433,  846,  648,  680,  680,  767,  687,  680,  768,  753,
          716,  839,  705,  834,  845,  650,  833,  680,  655,  844,
          433,  623,  629,  822,  678,  704,  814,  669,  824,  811,
          755,  458,  619,  752,  634,  706,  838,  837,  840,  707,
          756,  759,  614,  660,  668,  666,  789,  760,  810,  728,
          790,  791,  830,  679,  701,  692,  654,  684,  620,  761,
          792,  832,  729,  730,  743,  793,  745,  794,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,  134,  134,
           -2,   -2,   -2,   -2,    0,    0,    0,    0,    0,   -2,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,    0,    0,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,  418,  -20,  -20,  -20,  -20,  418,  -20,  -20,
          -20,  -20,  -20,  -20,  -20,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  -20,  418,  418,  418,  -20,  487,  -20,  487,
          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
          487,  487,  487,  487,  487,  487,  487,  487,  487,  487,
          487,  487,  418,    0,    0,  418,  -20,  418,  -20,  418,
          -20,  418,  418,  418,  418,  418,  418,  -20,  -20,  -20,
          -20,  -20,  -20,    0,  248,  248,  248,  248,  -20,  -20,
          -20,  -20,   55,   55,   55,   55,  487,  487,  487,  487,
          487,  487,  248,  248,  476,  476,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,  487,   55,  487,  638,
          638,  638,  638,  638,  296,  638,  296,  296,    0,    0,
            0,    0,    0,    0,  638,  296,    0,   -6,   -6,   -6,
            0,  638,  638,  638,  638,  638,  638,  638,  638,   -6,
          638,  638,  638,  819,  296,    0,   -6,  546,  546,  546,
          546,  433,   59,    0,  638,  638,    0,  670,    0,    0,
            0,  819,    0,    0,    0,    0,    0,  615,  274,  699,
            0,  322,    0,    0,    0,    0,    0,    0,    0,  639,
          322,  246,  246,    0,    0,  621,  615,  615,  615,    0,
            0,  639,  639,    0,    0,    0,    0,    0,    0,  427,
          639,    0,    0,    0,    0,  427,  279,    0,    0,  279,
            0,  433
    );

    protected $actionDefault = array(
            3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,  524,  524,32767,  481,32767,32767,
        32767,32767,32767,32767,32767,  287,  287,  287,32767,32767,
        32767,  513,  513,  513,  513,  513,  513,  513,  513,  513,
          513,  513,32767,32767,32767,32767,32767,  369,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,  375,  529,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,  350,  351,  353,  354,  286,  514,
          237,  376,  528,  285,  239,  314,  485,32767,32767,32767,
          316,  116,  248,  193,  484,  119,  284,  224,  368,  370,
          315,  291,  296,  297,  298,  299,  300,  301,  302,  303,
          304,  305,  306,  307,  290,  441,  347,  346,  345,  443,
        32767,  442,  478,  478,  481,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,  312,  469,  468,  313,  439,
          317,  440,  319,  444,  318,  335,  336,  333,  334,  337,
          446,  445,  462,  463,  460,  461,  289,  338,  339,  340,
          341,  464,  465,  466,  467,  271,  271,  271,  271,32767,
        32767,  523,  523,32767,32767,  326,  327,  453,  454,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
          272,32767,  228,  228,  228,  228,  228,32767,32767,32767,
        32767,  321,  322,  320,  448,  449,  447,32767,  415,32767,
        32767,32767,32767,  417,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,  486,32767,32767,32767,
        32767,32767,32767,32767,32767,  499,  404,32767,32767,32767,
          397,  212,  214,  161,  472,32767,32767,32767,32767,  504,
          331,32767,32767,32767,32767,32767,32767,  537,32767,  499,
        32767,32767,32767,32767,32767,32767,32767,32767,  344,  323,
          324,  325,32767,32767,32767,32767,  503,  497,  456,  457,
          458,  459,32767,32767,  450,  451,  452,  455,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,  165,32767,  412,32767,  418,  418,32767,32767,  165,
        32767,32767,32767,32767,  165,32767,  502,  501,  165,32767,
          398,  480,  165,  178,32767,  176,  176,32767,  198,  198,
        32767,32767,  180,  473,  492,32767,  180,32767,  165,32767,
          386,  167,  480,32767,32767,  230,  230,  386,  165,  230,
        32767,  230,32767,   82,  422,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,  399,
        32767,32767,32767,32767,  365,  366,  475,  488,32767,  489,
        32767,  397,32767,  329,  330,  332,  309,32767,  311,  355,
          356,  357,  358,  359,  360,  361,  363,32767,32767,  402,
          405,32767,32767,32767,   84,  108,  247,32767,  536,   84,
          400,32767,32767,  294,  536,32767,32767,32767,32767,  531,
        32767,32767,  288,32767,32767,32767,   84,32767,   84,  243,
        32767,  163,32767,  521,32767,32767,  497,  401,32767,  328,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,  498,
        32767,32767,32767,32767,  219,32767,  435,32767,   84,32767,
          179,32767,32767,  292,  238,32767,32767,  530,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,  164,32767,32767,
          181,32767,32767,  497,32767,32767,32767,32767,32767,32767,
        32767,32767,  283,32767,32767,32767,32767,32767,  497,32767,
        32767,32767,  223,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,   82,   60,32767,  265,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,  121,  121,
            3,  121,  121,    3,  121,  121,  121,  121,  121,  121,
          121,  121,  121,  121,  121,  121,  121,  206,  250,  209,
          198,  198,  158,  250,  250,  250,  257
    );

    protected $goto = array(
          160,  160,  134,  134,  139,  134,  135,  136,  137,  142,
          144,  181,  162,  158,  158,  158,  158,  139,  139,  159,
          159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
          154,  155,  156,  157,  178,  133,  179,  493,  494,  360,
          495,  499,  500,  501,  502,  503,  504,  505,  506,  962,
          138,  140,  141,  143,  165,  170,  180,  196,  245,  248,
          250,  252,  254,  255,  256,  257,  258,  259,  267,  268,
          269,  270,  285,  286,  311,  312,  313,  379,  380,  381,
          549,  182,  183,  184,  185,  186,  187,  188,  189,  190,
          191,  192,  193,  194,  145,  146,  147,  161,  148,  163,
          149,  197,  164,  150,  151,  152,  198,  153,  131,  625,
          567,  753,  567,  567,  567,  567,  567,  567,  567,  567,
          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
          567,  567,  567,  567,  567,  567,  567,  567,  567,  567,
          567,  567,  567,  567,  567, 1100,    6, 1100, 1100, 1100,
         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
         1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
          885,  885, 1189, 1189,  583,  586,  631,  168,  341,  509,
          754,  509,  171,  172,  173,  388,  389,  390,  391,  167,
          195,  199,  201,  249,  251,  253,  260,  261,  262,  263,
          264,  265,  271,  272,  273,  274,  287,  288,  314,  315,
          316,  394,  395,  396,  397,  169,  174,  246,  247,  175,
          176,  177,  497,  497,  497,  497,  497,  497,  523, 1200,
         1200,  784,  497,  497,  497,  497,  497,  497,  497,  497,
          497,  497,  508, 1200,  508,  387,  608,  543,  543,  573,
          539,  580,  606,  790,  752,  541,  541,  496,  498,  529,
          546,  574,  577,  587,  593,  871, 1169,  851, 1169,  657,
          634,  511,  880,  875,  566,  815,  566,  566,  566,  566,
          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
          566,  566,  566,  566,  566,  566,  566,  566,  566,  566,
          566,  566,  566,  566,  566,  566,  566,  566,  566,  551,
          552,  553,  554,  555,  556,  557,  558,  560,  589,  514,
          855,  550,  590,  344,  404,  522,  519,  519,  519,  443,
          445,  933,  636,  519, 1161, 1101,  618,  931,  522,  522,
          276,  277,  278,  430,  430,  430,  430,  430,  430,  538,
          519,  903, 1058,  430,  430,  430,  430,  430,  430,  430,
          430,  430,  430, 1065,  544, 1065,  892,  892,  892,  892,
          892,  535,  892,  659,  562,  777,  594,  868,  882,  613,
          867,  616,  878,  620,  621,  628,  630,  635,  637,  342,
          343,  849,  849,  849,  849,  323,  310,  844,  850,  615,
          548, 1199, 1199,  572,  941,  777,  777,  519,  519,  536,
          568,  519,  519, 1081,  519, 1199,  510, 1168,  510, 1168,
         1019,   17,   13,  355, 1061, 1062, 1193,  520, 1058, 1202,
          611, 1076, 1075,  617,  361,  358,  547,  561, 1184, 1184,
         1184, 1059, 1160, 1059,  598,  607, 1186, 1149,  362,   21,
         1167, 1060,  527,  375,  604, 1009,  540,  369,  369,  369,
          898,  889,  960,  770,  770,  778,  778,  778,  780,  369,
          769,  398,  451,  347,  773,  368,  386,  373,  907,  771,
          645,  402,   10, 1051, 1056,  446,  781,  578,  912,  859,
         1146,  459,  949,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,  528
    );

    protected $gotoCheck = array(
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   53,
          112,   11,  112,  112,  112,  112,  112,  112,  112,  112,
          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
          112,  112,  112,  112,  112,  112,  112,  112,  112,  112,
          112,  112,  112,  112,  112,  119,   90,  119,  119,  119,
          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
           70,   70,   70,   70,   56,   56,   56,   23,   65,  112,
           12,  112,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,  109,  109,  109,  109,  109,  109,   93,  134,
          134,   25,  109,  109,  109,  109,  109,  109,  109,  109,
          109,  109,  109,  134,  109,   47,   47,   47,   47,   47,
           47,   36,   36,   10,   10,   47,   47,   47,   47,   47,
           47,   47,   47,   47,   47,   10,  110,   10,  110,   10,
            5,   10,   10,   10,   53,   46,   53,   53,   53,   53,
           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
           53,   53,   53,   53,   53,   53,   53,   53,   53,   53,
           53,   53,   53,   53,   53,   53,   53,   53,   53,  102,
          102,  102,  102,  102,  102,  102,  102,  102,  102,    8,
           29,   40,   63,   63,   63,   40,    8,    8,    8,    7,
            7,    7,    7,    8,   75,    7,    7,    7,   40,   40,
           61,   61,   61,   53,   53,   53,   53,   53,   53,    8,
            8,   77,   75,   53,   53,   53,   53,   53,   53,   53,
           53,   53,   53,   53,  101,   53,   53,   53,   53,   53,
           53,   28,   53,   28,   28,   19,   28,   28,   28,   28,
           28,   28,   28,   28,   28,   28,   28,   28,   28,   65,
           65,   53,   53,   53,   53,  118,  118,   53,   53,   53,
            2,  133,  133,    2,   90,   19,   19,    8,    8,    8,
            8,    8,    8,   30,    8,  133,  115,  111,  115,  111,
           30,   30,   30,   30,   75,   75,  132,    8,   75,  133,
           57,  117,  117,   57,   43,   57,    8,   30,  111,  111,
          111,   75,   75,   75,  120,   45,  130,  124,   54,   30,
          111,   75,   54,   44,   30,   94,   54,  116,  116,  116,
           74,   72,   93,   19,   19,   19,   19,   19,   19,  116,
           19,   18,   54,   14,   21,    9,  116,   13,   78,   20,
           67,   17,   54,  105,  107,   59,   22,   60,   79,   64,
          123,  100,   92,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   93
    );

    protected $gotoBase = array(
            0,    0, -200,    0,    0,  288,    0,  366,   42,  184,
          282,  109,  208,  170,  196,    0,    0,  115,  186,   98,
          171,  188,   86,    7,    0,  253,    0,    0, -228,  342,
           40,    0,    0,    0,    0,    0,  245,    0,    0,  -22,
          339,    0,    0,  436,  203,  205,  289,   -4,    0,    0,
            0,    0,    0,  104,   64,    0,  -99,   14,    0,   89,
           81, -283,    0,   34,   82, -231,    0,  163,    0,    0,
          -79,    0,  195,    0,  192,   38,    0,  368,  162,   87,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          144,    0,   72,  219,  194,    0,    0,    0,    0,    0,
           74,  379,  307,    0,    0,  107,    0,  105,    0,  -27,
           -3,  158,  -90,    0,    0,  157,  177,  150,  117,  -45,
          281,    0,    0,   78,  283,    0,    0,    0,    0,    0,
          204,    0,  439,  132,  -50,    0
    );

    protected $gotoDefault = array(
        -32768,  462,  668,    2,  669,  740,  748,  601,  479,  515,
          853,  791,  792,  364,  410,  480,  363,  399,  392,  779,
          772,  774,  782,  166,  400,  785,    1,  787,  521,  823,
         1010,  351,  795,  352,  592,  797,  531,  799,  800,  132,
          481,  365,  366,  532,  374,  581,  814,  266,  371,  816,
          353,  817,  826,  354,  614,  597,  563,  610,  482,  442,
          575,  275,  542,  570,  858,  340,  866,  648,  874,  877,
          483,  564,  888,  448,  896, 1086,  382,  902,  908,  913,
          916,  418,  401,  588,  920,  921,    5,  925,  626,  627,
          940,  300,  948,  961,  416, 1029, 1031,  484,  485,  525,
          456,  507,  530,  486, 1052,  436,  403, 1055,  487,  488,
          426,  427, 1073, 1070,  346, 1154,  345,  444,  309, 1141,
          584, 1105,  452, 1192, 1150,  336,  489,  490,  359,  376,
         1187,  431, 1194, 1201,  337,  571
    );

    protected $ruleToNonTerminal = array(
            0,    1,    3,    3,    2,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    6,    6,    6,    6,    6,    6,    6,
            7,    7,    8,    8,    9,    4,    4,    4,    4,    4,
            4,    4,    4,    4,    4,    4,   14,   14,   15,   15,
           15,   15,   17,   17,   13,   13,   18,   18,   19,   19,
           20,   20,   21,   21,   16,   16,   22,   24,   24,   25,
           26,   26,   28,   27,   27,   27,   27,   29,   29,   29,
           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
           29,   29,   29,   29,   29,   29,   10,   10,   50,   50,
           52,   51,   51,   44,   44,   54,   54,   55,   55,   11,
           12,   12,   12,   58,   58,   58,   59,   59,   62,   62,
           60,   60,   63,   63,   37,   37,   46,   46,   49,   49,
           49,   48,   48,   64,   38,   38,   38,   38,   65,   65,
           66,   66,   67,   67,   35,   35,   31,   31,   68,   33,
           33,   69,   32,   32,   34,   34,   45,   45,   45,   56,
           56,   71,   71,   72,   72,   74,   74,   74,   73,   73,
           57,   57,   75,   75,   75,   76,   76,   77,   77,   77,
           41,   41,   78,   78,   78,   42,   42,   79,   79,   61,
           61,   80,   80,   80,   80,   85,   85,   86,   86,   87,
           87,   87,   87,   87,   88,   89,   89,   84,   84,   81,
           81,   83,   83,   91,   91,   90,   90,   90,   90,   90,
           90,   82,   82,   92,   92,   43,   43,   36,   36,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   39,   39,   39,   39,   39,   39,   39,
           39,   39,   39,   30,   30,   40,   40,   97,   97,   98,
           98,   98,   98,  104,   93,   93,  100,  100,  106,  106,
          107,  108,  108,  108,  108,  108,  108,  112,  112,   53,
           53,   53,   94,   94,  113,  113,  109,  109,  114,  114,
          114,  114,   95,   95,   95,   99,   99,   99,  105,  105,
          119,  119,  119,  119,  119,  119,  119,  119,  119,  119,
          119,  119,  119,   23,   23,   23,   23,   23,   23,  121,
          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
          121,  121,  103,  103,   96,   96,   96,   96,  120,  120,
          123,  123,  122,  122,  124,  124,   47,   47,   47,   47,
          126,  126,  125,  125,  125,  125,  125,  127,  127,  111,
          111,  115,  115,  110,  110,  128,  128,  128,  128,  116,
          116,  116,  116,  102,  102,  117,  117,  117,   70,  129,
          129,  130,  130,  130,  101,  101,  131,  131,  132,  132,
          132,  132,  118,  118,  118,  118,  134,  133,  133,  133,
          133,  133,  133,  133,  135,  135,  135
    );

    protected $ruleToLength = array(
            1,    1,    2,    0,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    3,    1,    1,    1,    1,    1,    3,
            5,    4,    3,    4,    2,    3,    1,    1,    7,    8,
            6,    7,    3,    1,    3,    1,    3,    1,    1,    3,
            1,    2,    1,    2,    3,    1,    3,    3,    1,    3,
            2,    0,    1,    1,    1,    1,    1,    3,    5,    8,
            3,    5,    9,    3,    2,    3,    2,    3,    2,    3,
            2,    3,    3,    3,    1,    2,    5,    7,    9,    5,
            6,    3,    3,    2,    2,    1,    1,    1,    0,    2,
            8,    0,    4,    1,    3,    0,    1,    0,    1,   10,
            7,    6,    5,    1,    2,    2,    0,    2,    0,    2,
            0,    2,    1,    3,    1,    4,    1,    4,    1,    1,
            4,    1,    3,    3,    3,    4,    4,    5,    0,    2,
            4,    3,    1,    1,    1,    4,    0,    2,    3,    0,
            2,    4,    0,    2,    0,    3,    1,    2,    1,    1,
            0,    1,    3,    4,    6,    1,    1,    1,    0,    1,
            0,    2,    2,    3,    3,    1,    3,    1,    2,    2,
            3,    1,    1,    2,    4,    3,    1,    1,    3,    2,
            0,    3,    3,    9,    3,    1,    3,    0,    2,    4,
            5,    4,    4,    4,    3,    1,    1,    1,    3,    1,
            1,    0,    1,    1,    2,    1,    1,    1,    1,    1,
            1,    1,    3,    1,    3,    3,    1,    0,    1,    1,
            3,    3,    4,    4,    1,    2,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    2,    2,
            2,    2,    3,    3,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    2,
            2,    2,    2,    3,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    1,    3,    5,    4,    3,    4,    4,
            2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
            2,    2,    2,    2,    1,    1,    1,    3,    2,    1,
            2,   10,   11,    3,    3,    2,    4,    4,    3,    4,
            4,    4,    4,    7,    3,    2,    0,    4,    1,    3,
            2,    2,    4,    6,    2,    2,    4,    1,    1,    1,
            2,    3,    1,    1,    1,    1,    1,    1,    3,    3,
            4,    4,    0,    2,    1,    0,    1,    1,    0,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    3,    2,    1,    3,    1,    4,    3,    1,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    2,    2,    2,    2,
            3,    3,    3,    3,    3,    3,    3,    3,    5,    4,
            4,    3,    1,    3,    1,    1,    3,    3,    0,    2,
            0,    1,    3,    1,    3,    1,    1,    1,    1,    1,
            6,    4,    3,    4,    2,    4,    4,    1,    3,    1,
            2,    1,    1,    4,    1,    3,    6,    4,    4,    4,
            4,    1,    4,    0,    1,    1,    3,    1,    4,    3,
            1,    1,    1,    0,    0,    2,    3,    1,    3,    1,
            4,    2,    2,    2,    1,    2,    1,    1,    4,    3,
            3,    3,    6,    3,    1,    1,    1
    );

    protected function reduceRule0() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule1() {
         $this->semValue = $this->handleNamespaces($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule2() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule3() {
         $this->semValue = array();
    }

    protected function reduceRule4() {
         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule5() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule6() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule7() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule8() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule9() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule10() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule11() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule12() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule13() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule14() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule15() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule16() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule17() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule18() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule19() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule20() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule21() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule22() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule23() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule24() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule25() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule26() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule27() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule28() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule29() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule30() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule31() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule32() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule33() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule34() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule35() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule36() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule37() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule38() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule39() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule40() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule41() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule42() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule43() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule44() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule45() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule46() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule47() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule48() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule49() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule50() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule51() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule52() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule53() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule54() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule55() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule56() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule57() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule58() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule59() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule60() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule61() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule62() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule63() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule64() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule65() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule66() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule67() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule68() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule69() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule70() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule71() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule72() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule73() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule74() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule75() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule76() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule77() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule78() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule79() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule80() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule81() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule82() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule83() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule84() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule85() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule86() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule87() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule88() {
         $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule89() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule90() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule91() {
         $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule92() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule93() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule94() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule95() {
         $this->semValue = new Stmt\Const_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule96() {
         $this->semValue = Stmt\Use_::TYPE_FUNCTION;
    }

    protected function reduceRule97() {
         $this->semValue = Stmt\Use_::TYPE_CONSTANT;
    }

    protected function reduceRule98() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule99() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule100() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule101() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule102() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule103() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule104() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule105() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule106() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule107() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule108() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule109() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule110() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule111() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule112() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
    }

    protected function reduceRule113() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)]; $this->semValue->type = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule114() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule115() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule116() {
         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule117() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule118() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule119() {
         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule120() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule121() {
         $this->semValue = array();
    }

    protected function reduceRule122() {
         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule123() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule124() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule125() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule126() {
         throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule127() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments']) && isset($stmts[0])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
    }

    protected function reduceRule128() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(5-2)], ['stmts' => is_array($this->semStack[$this->stackPos-(5-3)]) ? $this->semStack[$this->stackPos-(5-3)] : array($this->semStack[$this->stackPos-(5-3)]), 'elseifs' => $this->semStack[$this->stackPos-(5-4)], 'else' => $this->semStack[$this->stackPos-(5-5)]], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule129() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(8-2)], ['stmts' => $this->semStack[$this->stackPos-(8-4)], 'elseifs' => $this->semStack[$this->stackPos-(8-5)], 'else' => $this->semStack[$this->stackPos-(8-6)]], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule130() {
         $this->semValue = new Stmt\While_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule131() {
         $this->semValue = new Stmt\Do_($this->semStack[$this->stackPos-(5-4)], is_array($this->semStack[$this->stackPos-(5-2)]) ? $this->semStack[$this->stackPos-(5-2)] : array($this->semStack[$this->stackPos-(5-2)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule132() {
         $this->semValue = new Stmt\For_(['init' => $this->semStack[$this->stackPos-(9-3)], 'cond' => $this->semStack[$this->stackPos-(9-5)], 'loop' => $this->semStack[$this->stackPos-(9-7)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule133() {
         $this->semValue = new Stmt\Switch_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule134() {
         $this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule135() {
         $this->semValue = new Stmt\Break_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule136() {
         $this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule137() {
         $this->semValue = new Stmt\Continue_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule138() {
         $this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule139() {
         $this->semValue = new Stmt\Return_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule140() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule141() {
         $this->semValue = new Stmt\Global_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule142() {
         $this->semValue = new Stmt\Static_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule143() {
         $this->semValue = new Stmt\Echo_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule144() {
         $this->semValue = new Stmt\InlineHTML($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule145() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule146() {
         $this->semValue = new Stmt\Unset_($this->semStack[$this->stackPos-(5-3)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule147() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(7-3)], $this->semStack[$this->stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$this->stackPos-(7-5)][1], 'stmts' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule148() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(9-3)], $this->semStack[$this->stackPos-(9-7)][0], ['keyVar' => $this->semStack[$this->stackPos-(9-5)], 'byRef' => $this->semStack[$this->stackPos-(9-7)][1], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule149() {
         $this->semValue = new Stmt\Declare_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule150() {
         $this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule151() {
         $this->semValue = new Stmt\Throw_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule152() {
         $this->semValue = new Stmt\Goto_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule153() {
         $this->semValue = new Stmt\Label($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule154() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule155() {
         $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule156() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule157() {
         $startAttributes = $this->startAttributeStack[$this->stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $this->semValue = null; };
            if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule158() {
         $this->semValue = array();
    }

    protected function reduceRule159() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule160() {
         $this->semValue = new Stmt\Catch_($this->semStack[$this->stackPos-(8-3)], substr($this->semStack[$this->stackPos-(8-4)], 1), $this->semStack[$this->stackPos-(8-7)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule161() {
         $this->semValue = null;
    }

    protected function reduceRule162() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule163() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule164() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule165() {
         $this->semValue = false;
    }

    protected function reduceRule166() {
         $this->semValue = true;
    }

    protected function reduceRule167() {
         $this->semValue = false;
    }

    protected function reduceRule168() {
         $this->semValue = true;
    }

    protected function reduceRule169() {
         $this->semValue = new Stmt\Function_($this->semStack[$this->stackPos-(10-3)], ['byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-5)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule170() {
         $this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], ['type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule171() {
         $this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], ['extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule172() {
         $this->semValue = new Stmt\Trait_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule173() {
         $this->semValue = 0;
    }

    protected function reduceRule174() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule175() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule176() {
         $this->semValue = null;
    }

    protected function reduceRule177() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule178() {
         $this->semValue = array();
    }

    protected function reduceRule179() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule180() {
         $this->semValue = array();
    }

    protected function reduceRule181() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule182() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule183() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule184() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule185() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule186() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule187() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule188() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule189() {
         $this->semValue = null;
    }

    protected function reduceRule190() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule191() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule192() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule193() {
         $this->semValue = new Stmt\DeclareDeclare($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule194() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule195() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule196() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule197() {
         $this->semValue = $this->semStack[$this->stackPos-(5-3)];
    }

    protected function reduceRule198() {
         $this->semValue = array();
    }

    protected function reduceRule199() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule200() {
         $this->semValue = new Stmt\Case_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule201() {
         $this->semValue = new Stmt\Case_(null, $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule202() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule203() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule204() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule205() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule206() {
         $this->semValue = array();
    }

    protected function reduceRule207() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule208() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(3-2)], is_array($this->semStack[$this->stackPos-(3-3)]) ? $this->semStack[$this->stackPos-(3-3)] : array($this->semStack[$this->stackPos-(3-3)]), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule209() {
         $this->semValue = array();
    }

    protected function reduceRule210() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule211() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule212() {
         $this->semValue = null;
    }

    protected function reduceRule213() {
         $this->semValue = new Stmt\Else_(is_array($this->semStack[$this->stackPos-(2-2)]) ? $this->semStack[$this->stackPos-(2-2)] : array($this->semStack[$this->stackPos-(2-2)]), $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule214() {
         $this->semValue = null;
    }

    protected function reduceRule215() {
         $this->semValue = new Stmt\Else_($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule216() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule217() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-2)], true);
    }

    protected function reduceRule218() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule219() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule220() {
         $this->semValue = array();
    }

    protected function reduceRule221() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule222() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule223() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule224() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule225() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule226() {
         $this->semValue = 'array';
    }

    protected function reduceRule227() {
         $this->semValue = 'callable';
    }

    protected function reduceRule228() {
         $this->semValue = null;
    }

    protected function reduceRule229() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule230() {
         $this->semValue = null;
    }

    protected function reduceRule231() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule232() {
         $this->semValue = array();
    }

    protected function reduceRule233() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule234() {
         $this->semValue = array(new Node\Arg($this->semStack[$this->stackPos-(3-2)], false, false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes));
    }

    protected function reduceRule235() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule236() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule237() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(1-1)], false, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule238() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], true, false, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule239() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], false, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule240() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule241() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule242() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule243() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule244() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule245() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule246() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule247() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule248() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule249() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule250() {
         $this->semValue = array();
    }

    protected function reduceRule251() {
         $this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule252() {
         $this->semValue = new Stmt\ClassConst($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule253() {
         $this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], ['type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule254() {
         $this->semValue = new Stmt\TraitUse($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule255() {
         $this->semValue = array();
    }

    protected function reduceRule256() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule257() {
         $this->semValue = array();
    }

    protected function reduceRule258() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule259() {
         $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule260() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(5-1)][0], $this->semStack[$this->stackPos-(5-1)][1], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule261() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], null, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule262() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule263() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule264() {
         $this->semValue = array($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)]);
    }

    protected function reduceRule265() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule266() {
         $this->semValue = array(null, $this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule267() {
         $this->semValue = null;
    }

    protected function reduceRule268() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule269() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule270() {
         $this->semValue = 0;
    }

    protected function reduceRule271() {
         $this->semValue = 0;
    }

    protected function reduceRule272() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule273() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule274() {
         Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule275() {
         $this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
    }

    protected function reduceRule276() {
         $this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
    }

    protected function reduceRule277() {
         $this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
    }

    protected function reduceRule278() {
         $this->semValue = Stmt\Class_::MODIFIER_STATIC;
    }

    protected function reduceRule279() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule280() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule281() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule282() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule283() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule284() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule285() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule286() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule287() {
         $this->semValue = array();
    }

    protected function reduceRule288() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule289() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule290() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule291() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule292() {
         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule293() {
         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule294() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule295() {
         $this->semValue = new Expr\Clone_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule296() {
         $this->semValue = new Expr\AssignOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule297() {
         $this->semValue = new Expr\AssignOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule298() {
         $this->semValue = new Expr\AssignOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule299() {
         $this->semValue = new Expr\AssignOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule300() {
         $this->semValue = new Expr\AssignOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule301() {
         $this->semValue = new Expr\AssignOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule302() {
         $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule303() {
         $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule304() {
         $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule305() {
         $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule306() {
         $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule307() {
         $this->semValue = new Expr\AssignOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule308() {
         $this->semValue = new Expr\PostInc($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule309() {
         $this->semValue = new Expr\PreInc($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule310() {
         $this->semValue = new Expr\PostDec($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule311() {
         $this->semValue = new Expr\PreDec($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule312() {
         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule313() {
         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule314() {
         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule315() {
         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule316() {
         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule317() {
         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule318() {
         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule319() {
         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule320() {
         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule321() {
         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule322() {
         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule323() {
         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule324() {
         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule325() {
         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule326() {
         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule327() {
         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule328() {
         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule329() {
         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule330() {
         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule331() {
         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule332() {
         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule333() {
         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule334() {
         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule335() {
         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule336() {
         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule337() {
         $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule338() {
         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule339() {
         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule340() {
         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule341() {
         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule342() {
         $this->semValue = new Expr\Instanceof_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule343() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule344() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule345() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule346() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule347() {
         $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule348() {
         $this->semValue = new Expr\Isset_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule349() {
         $this->semValue = new Expr\Empty_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule350() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule351() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule352() {
         $this->semValue = new Expr\Eval_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule353() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule354() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule355() {
         $this->semValue = new Expr\Cast\Int_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule356() {
         $this->semValue = new Expr\Cast\Double($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule357() {
         $this->semValue = new Expr\Cast\String_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule358() {
         $this->semValue = new Expr\Cast\Array_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule359() {
         $this->semValue = new Expr\Cast\Object_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule360() {
         $this->semValue = new Expr\Cast\Bool_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule361() {
         $this->semValue = new Expr\Cast\Unset_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule362() {
         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes;
            $attrs['kind'] = strtolower($this->semStack[$this->stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
            $this->semValue = new Expr\Exit_($this->semStack[$this->stackPos-(2-2)], $attrs);
    }

    protected function reduceRule363() {
         $this->semValue = new Expr\ErrorSuppress($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule364() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule365() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule366() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule367() {
         $this->semValue = new Expr\ShellExec($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule368() {
         $this->semValue = new Expr\Print_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule369() {
         $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule370() {
         $this->semValue = new Expr\YieldFrom($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule371() {
         $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-4)], 'uses' => $this->semStack[$this->stackPos-(10-6)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule372() {
         $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$this->stackPos-(11-3)], 'params' => $this->semStack[$this->stackPos-(11-5)], 'uses' => $this->semStack[$this->stackPos-(11-7)], 'returnType' => $this->semStack[$this->stackPos-(11-8)], 'stmts' => $this->semStack[$this->stackPos-(11-10)]], $this->startAttributeStack[$this->stackPos-(11-1)] + $this->endAttributes);
    }

    protected function reduceRule373() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule374() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule375() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule376() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule377() {
         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $attrs);
    }

    protected function reduceRule378() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule379() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule380() {
         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(4-1)][0] === "'" || ($this->semStack[$this->stackPos-(4-1)][1] === "'" && ($this->semStack[$this->stackPos-(4-1)][0] === 'b' || $this->semStack[$this->stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
            $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(4-1)]), $attrs), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule381() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule382() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule383() {
         $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
    }

    protected function reduceRule384() {
         $this->semValue = new Expr\New_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule385() {
         list($class, $ctorArgs) = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule386() {
         $this->semValue = array();
    }

    protected function reduceRule387() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule388() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule389() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule390() {
         $this->semValue = new Expr\ClosureUse(substr($this->semStack[$this->stackPos-(2-2)], 1), $this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule391() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule392() {
         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule393() {
         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-4)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule394() {

            if ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\StaticPropertyFetch) {
                $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(2-1)]->class, new Expr\Variable($this->semStack[$this->stackPos-(2-1)]->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
            } elseif ($this->semStack[$this->stackPos-(2-1)] instanceof Node\Expr\ArrayDimFetch) {
                $tmp = $this->semStack[$this->stackPos-(2-1)];
                while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
                    $tmp = $tmp->var;
                }

                $this->semValue = new Expr\StaticCall($tmp->var->class, $this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
                $tmp->var = new Expr\Variable($tmp->var->name, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
            } else {
                throw new \Exception;
            }

    }

    protected function reduceRule395() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule396() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule397() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule398() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule399() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule400() {
         $this->semValue = new Name\FullyQualified($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule401() {
         $this->semValue = new Name\Relative($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule402() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule403() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule404() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule405() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule406() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule407() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule408() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule409() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule410() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule411() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule412() {
         $this->semValue = null;
    }

    protected function reduceRule413() {
         $this->semValue = null;
    }

    protected function reduceRule414() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule415() {
         $this->semValue = array();
    }

    protected function reduceRule416() {
         $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$this->stackPos-(1-1)], '`', false), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes));
    }

    protected function reduceRule417() {
         foreach ($this->semStack[$this->stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', false); } }; $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule418() {
         $this->semValue = array();
    }

    protected function reduceRule419() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule420() {
         $this->semValue = Scalar\LNumber::fromString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes, true);
    }

    protected function reduceRule421() {
         $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule422() {
         $attrs = $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(1-1)][0] === "'" || ($this->semStack[$this->stackPos-(1-1)][1] === "'" && ($this->semStack[$this->stackPos-(1-1)][0] === 'b' || $this->semStack[$this->stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
            $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(1-1)], false), $attrs);
    }

    protected function reduceRule423() {
         $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule424() {
         $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule425() {
         $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule426() {
         $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule427() {
         $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule428() {
         $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule429() {
         $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule430() {
         $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule431() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
            $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], false), $attrs);
    }

    protected function reduceRule432() {
         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
            $this->semValue = new Scalar\String_('', $attrs);
    }

    protected function reduceRule433() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule434() {
         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule435() {
         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule436() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule437() {
         $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule438() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule439() {
         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule440() {
         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule441() {
         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule442() {
         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule443() {
         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule444() {
         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule445() {
         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule446() {
         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule447() {
         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule448() {
         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule449() {
         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule450() {
         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule451() {
         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule452() {
         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule453() {
         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule454() {
         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule455() {
         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule456() {
         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule457() {
         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule458() {
         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule459() {
         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule460() {
         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule461() {
         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule462() {
         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule463() {
         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule464() {
         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule465() {
         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule466() {
         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule467() {
         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule468() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule469() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule470() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule471() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule472() {
         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule473() {
         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule474() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule475() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule476() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule477() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$this->stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule478() {
         $this->semValue = array();
    }

    protected function reduceRule479() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule480() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule481() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule482() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule483() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule484() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule485() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule486() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule487() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule488() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule489() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule490() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule491() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule492() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule493() {
         $this->semValue = new Expr\MethodCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule494() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule495() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule496() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule497() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule498() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule499() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule500() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule501() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule502() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule503() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule504() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule505() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], substr($this->semStack[$this->stackPos-(3-3)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule506() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-5)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule507() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule508() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule509() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule510() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule511() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule512() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule513() {
         $this->semValue = null;
    }

    protected function reduceRule514() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule515() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule516() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule517() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule518() {
         $this->semValue = new Expr\List_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule519() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule520() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule521() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule522() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule523() {
         $this->semValue = null;
    }

    protected function reduceRule524() {
         $this->semValue = array();
    }

    protected function reduceRule525() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule526() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule527() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule528() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule529() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule530() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-1)], true, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule531() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(2-2)], null, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule532() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule533() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule534() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule535() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]);
    }

    protected function reduceRule536() {
         $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule537() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule538() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule539() {
         $this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule540() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule541() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule542() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule543() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule544() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule545() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule546() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }
}
<?php

namespace PhpParser\Parser;

use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;

/* This is an automatically GENERATED file, which should not be manually edited.
 * Instead edit one of the following:
 *  * the grammar files grammar/php5.y or grammar/php7.y
 *  * the skeleton file grammar/parser.template
 *  * the preprocessing script grammar/rebuildParsers.php
 */
class Php7 extends \PhpParser\ParserAbstract
{
    protected $tokenToSymbolMapSize = 392;
    protected $actionTableSize = 879;
    protected $gotoTableSize = 412;

    protected $invalidSymbol = 157;
    protected $errorSymbol = 1;
    protected $defaultAction = -32766;
    protected $unexpectedTokenRule = 32767;

    protected $YY2TBLSTATE  = 328;
    protected $YYNLSTATES   = 554;

    protected $symbolToName = array(
        "EOF",
        "error",
        "T_INCLUDE",
        "T_INCLUDE_ONCE",
        "T_EVAL",
        "T_REQUIRE",
        "T_REQUIRE_ONCE",
        "','",
        "T_LOGICAL_OR",
        "T_LOGICAL_XOR",
        "T_LOGICAL_AND",
        "T_PRINT",
        "T_YIELD",
        "T_DOUBLE_ARROW",
        "T_YIELD_FROM",
        "'='",
        "T_PLUS_EQUAL",
        "T_MINUS_EQUAL",
        "T_MUL_EQUAL",
        "T_DIV_EQUAL",
        "T_CONCAT_EQUAL",
        "T_MOD_EQUAL",
        "T_AND_EQUAL",
        "T_OR_EQUAL",
        "T_XOR_EQUAL",
        "T_SL_EQUAL",
        "T_SR_EQUAL",
        "T_POW_EQUAL",
        "'?'",
        "':'",
        "T_COALESCE",
        "T_BOOLEAN_OR",
        "T_BOOLEAN_AND",
        "'|'",
        "'^'",
        "'&'",
        "T_IS_EQUAL",
        "T_IS_NOT_EQUAL",
        "T_IS_IDENTICAL",
        "T_IS_NOT_IDENTICAL",
        "T_SPACESHIP",
        "'<'",
        "T_IS_SMALLER_OR_EQUAL",
        "'>'",
        "T_IS_GREATER_OR_EQUAL",
        "T_SL",
        "T_SR",
        "'+'",
        "'-'",
        "'.'",
        "'*'",
        "'/'",
        "'%'",
        "'!'",
        "T_INSTANCEOF",
        "'~'",
        "T_INC",
        "T_DEC",
        "T_INT_CAST",
        "T_DOUBLE_CAST",
        "T_STRING_CAST",
        "T_ARRAY_CAST",
        "T_OBJECT_CAST",
        "T_BOOL_CAST",
        "T_UNSET_CAST",
        "'@'",
        "T_POW",
        "'['",
        "T_NEW",
        "T_CLONE",
        "T_EXIT",
        "T_IF",
        "T_ELSEIF",
        "T_ELSE",
        "T_ENDIF",
        "T_LNUMBER",
        "T_DNUMBER",
        "T_STRING",
        "T_STRING_VARNAME",
        "T_VARIABLE",
        "T_NUM_STRING",
        "T_INLINE_HTML",
        "T_ENCAPSED_AND_WHITESPACE",
        "T_CONSTANT_ENCAPSED_STRING",
        "T_ECHO",
        "T_DO",
        "T_WHILE",
        "T_ENDWHILE",
        "T_FOR",
        "T_ENDFOR",
        "T_FOREACH",
        "T_ENDFOREACH",
        "T_DECLARE",
        "T_ENDDECLARE",
        "T_AS",
        "T_SWITCH",
        "T_ENDSWITCH",
        "T_CASE",
        "T_DEFAULT",
        "T_BREAK",
        "T_CONTINUE",
        "T_GOTO",
        "T_FUNCTION",
        "T_CONST",
        "T_RETURN",
        "T_TRY",
        "T_CATCH",
        "T_FINALLY",
        "T_THROW",
        "T_USE",
        "T_INSTEADOF",
        "T_GLOBAL",
        "T_STATIC",
        "T_ABSTRACT",
        "T_FINAL",
        "T_PRIVATE",
        "T_PROTECTED",
        "T_PUBLIC",
        "T_VAR",
        "T_UNSET",
        "T_ISSET",
        "T_EMPTY",
        "T_HALT_COMPILER",
        "T_CLASS",
        "T_TRAIT",
        "T_INTERFACE",
        "T_EXTENDS",
        "T_IMPLEMENTS",
        "T_OBJECT_OPERATOR",
        "T_LIST",
        "T_ARRAY",
        "T_CALLABLE",
        "T_CLASS_C",
        "T_TRAIT_C",
        "T_METHOD_C",
        "T_FUNC_C",
        "T_LINE",
        "T_FILE",
        "T_START_HEREDOC",
        "T_END_HEREDOC",
        "T_DOLLAR_OPEN_CURLY_BRACES",
        "T_CURLY_OPEN",
        "T_PAAMAYIM_NEKUDOTAYIM",
        "T_NAMESPACE",
        "T_NS_C",
        "T_DIR",
        "T_NS_SEPARATOR",
        "T_ELLIPSIS",
        "';'",
        "'{'",
        "'}'",
        "'('",
        "')'",
        "'`'",
        "']'",
        "'\"'",
        "'$'"
    );

    protected $tokenToSymbol = array(
            0,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,   53,  155,  157,  156,   52,   35,  157,
          151,  152,   50,   47,    7,   48,   49,   51,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,   29,  148,
           41,   15,   43,   28,   65,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,   67,  157,  154,   34,  157,  153,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  149,   33,  150,   55,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,  157,  157,  157,  157,
          157,  157,  157,  157,  157,  157,    1,    2,    3,    4,
            5,    6,    8,    9,   10,   11,   12,   13,   14,   16,
           17,   18,   19,   20,   21,   22,   23,   24,   25,   26,
           27,   30,   31,   32,   36,   37,   38,   39,   40,   42,
           44,   45,   46,   54,   56,   57,   58,   59,   60,   61,
           62,   63,   64,   66,   68,   69,   70,   71,   72,   73,
           74,   75,   76,   77,   78,   79,   80,   81,  157,  157,
           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
          132,  133,  134,  135,  136,  137,  157,  157,  157,  157,
          157,  157,  138,  139,  140,  141,  142,  143,  144,  145,
          146,  147
    );

    protected $action = array(
          559,  560,  561,  562,  563,  704,  564,  565,  566,  602,
          603,-32766,-32766,-32766,-32767,-32767,-32767,-32767,   88,   89,
           90,   91,   92,  520,-32766,-32766,-32766,-32766,-32766,-32766,
            0,-32766,  111,-32766,-32766,-32766,-32766,-32766,-32766,-32767,
        -32767,-32767,-32767,-32767,-32766,  335,-32766,-32766,-32766,-32766,
        -32766,-32766,  567,-32766,-32766,-32766,-32766,  279,  825,  826,
          827,  824,  823,  822,-32766,-32766,  568,  569,  570,  571,
          572,  573,  574,  265,-32766,  634,-32766,-32766,-32766,-32766,
        -32766,  258,  575,  576,  577,  578,  579,  580,  581,  582,
          583,  584,  585,  605,  606,  607,  608,  609,  597,  598,
          599,  600,  601,  586,  587,  588,  589,  590,  591,  592,
          628,  629,  630,  631,  632,  633,  593,  594,  595,  596,
          626,  617,  615,  616,  612,  613,   24,  604,  610,  611,
          618,  619,  621,  620,  622,  623,   40,   41,  373,   42,
           43,  614,  625,  624,    6,   44,   45,    7,   46, -262,
          261, -418,  695,  825,  826,  827,  824,  823,  822,  817,
          113,   22,   93,   94,   95,  636,  235,  985,  236,   22,
          650,  651, 1027,-32766, 1029, 1028, -417,  949,   96,-32766,
          985,   47,   48,  509, -233,  949,  219,   49,-32766,   50,
          214,  215,   51,   52,   53,   54,   55,   56,   57,   58,
          929,   22,  229,   59,  342,-32766,-32766,-32766, -450,  950,
          951,  636, -418,  985,  330, -459,  221,  949,-32766,-32766,
        -32766,  705, -160,  390,  391,-32766, -418,-32766,-32766,-32766,
        -32766,  400,  391, -418,  344, -421,  346, -417,-32766,  209,
        -32766,-32766,-32766,  361,  267,   63,  399,   28,  359,  510,
          118, -417,  344,   63,  386,  387,  803,  267, -417,  128,
         -420,  370,  219,  390,  391,   39,  955,  956,  957,  958,
          952,  953,  237,-32766,-32766,-32766, -460,  400,  959,  954,
          344,  126,-32766,-32766,-32766,   61,  211,  247,  799,  248,
          267,  374, -122, -122, -122,   -4,  705,  375,  117,  282,
         -416,  694,-32766,  782,   32,   17,  376, -122,  377, -122,
          378, -122,  379, -122,  985,  380, -122, -122, -122,   33,
           34,  381,  343,  752,   35,  382,  251,  299,   60, -233,
         1019,  280,  281,  383,  384,-32766,-32766,-32766,-32766,  385,
          288,   21,  680,  723,  388,  389,  341,  112,   90,   91,
           92,  269,   37, -450,  354,-32766,  122,-32766,-32766,-32766,
         -459, -416, -459,  362,  537, -159,  374, -160,  707,  525,
         -122,-32766,  375,  353,  117, -416,  694,  125, -212,   32,
           17,  376, -416,  377,  222,  378,  121,  379,   25,  217,
          380,  267,-32766,   16,   33,   34,  381,  343,  336,   35,
          382,  998,  798,   60,  246,  705,  280,  281,  383,  384,
          776,  777,  447,  250,  385,-32766,   22,  642,  723,  388,
          389, -460,  115, -460,  114,  426,   70,   71,   72,  494,
          495, 1001,  949,  529,  110,  123,-32766,  109,  263, 1024,
          691,  542,  753,  707,  525,   -4,   26,  235,   73,   74,
           75,   76,   77,   78,   79,   80,   81,   82,   83,   84,
           85,   86,   87,   88,   89,   90,   91,   92,   93,   94,
           95,  116,  235,  119,  705,  374,  238,  350,  390,  391,
          527,  375,  963,  703,   96,  694,  783,-32766,   32,   17,
          376,  922,  377,  216,  378,  692,  379,  484,   18,  380,
           63,  220,  530,   33,   34,  381,  705,  716,   35,  382,
         -159,  218,   60,   38,  649,  280,  281,  124,  290,   96,
        -32766,  650,  651,  385,  802,  553,  504,  479,  480,  814,
          543,  643,  528,  439,  531,  309,  438,  425,  776,  777,
          420,  419,  351,  430,  374,  349,  637,  663,  636,-32766,
          375, 1022,  707,  525,  694,  489,  424,   32,   17,  376,
         -216,  377,  508,  378,-32766,  379,  925,  493,  380,  482,
          435,  519,   33,   34,  381,  705,  374,   35,  382,  485,
          505,   60,  375,  348,  280,  281,  694,  -80,  208,   32,
           17,  376,  385,  377,  442,  378,   10,  379,  368,  498,
          380,  262,  490,  538,   33,   34,  381,  705,  477,   35,
          382,  259,  264,   60,  965,    0,  280,  281,  725,  962,
          337,  707,  525,    0,  385,  260,  718,  724,  710,    0,
            0,    0,    0,    0,    0,  532,    0,    0,    0,    0,
            0,    0,    0,    3,    0,  374,    0,    0,    0, -376,
            9,  375,  287,  739,  525,  694,    0,  331,   32,   17,
          376,  302,  377,  314,  378,  315,  379,  319,  350,  380,
          432,  332,  328,   33,   34,  381,  705,  374,   35,  382,
          648,  693,   60,  375,  808,  280,  281,  694,  552,  551,
           32,   17,  376,  385,  377,   31,  378,  647,  379,  701,
           30,  380,  646,  807,  810,   33,   34,  381,  806,  735,
           35,  382,  737,  683,   60,  747,  746,  280,  281,  740,
          755,  685,  707,  525,  696,  385,  690,  702,  689,  688,
           27,   97,   98,   99,  100,  101,  102,  103,  104,  105,
          106,  107,  108,  809,  917,  257,  374,  256,   69,  549,
          548,  546,  375,  544,  707,  525,  694,  541,  540,   32,
           17,  376,  536,  377,  535,  378,  533,  379,  526,  329,
          380,  854,  856,  916,   33,   34,  381,  719,  712,   35,
          382, 1025, -416,   60,  815,  645,  280,  281, 1026,  721,
          653,  652,  720,  918,  385,  744,  655,  654,  722,  545,
          681, 1023,  986,  979,  991,  996,  999,  745,  644,    0,
           36, -441,  339,  334,  266,  234,  233,  232,  231,  213,
          212,  210,  129,  707,  525, -421,  910,  127, -420, -419,
          120,   20,   23,   68,   67,   29,   62,   64,   66,   65,
         -443,    0,   15, -416,   19,  242,  894,  289,  456, -213,
          473,  893,  461,  518,  897,   11,  947, -416,  939,  515,
         -212,  371,  367,  365, -416,  363,   14,  964,   13,   12,
            0, -387,    0,  483,  990, 1021,  977,  978,  948
    );

    protected $actionCheck = array(
            2,    3,    4,    5,    6,    1,    8,    9,   10,   11,
           12,    8,    9,   10,   41,   42,   43,   44,   45,   46,
           47,   48,   49,   77,    8,    9,   10,    8,    9,   10,
            0,   28,   13,   30,   31,   32,   33,   34,   35,   36,
           37,   38,   39,   40,   28,    7,   30,   31,   32,   33,
           34,   35,   54,    8,    8,    9,   10,    7,  112,  113,
          114,  115,  116,  117,    8,    9,   68,   69,   70,   71,
           72,   73,   74,    7,   28,   77,   30,   31,   32,   33,
           34,    7,   84,   85,   86,   87,   88,   89,   90,   91,
           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
          122,  123,  124,  125,  126,  127,    7,  129,  130,  131,
          132,  133,  134,  135,  136,  137,    2,    3,    4,    5,
            6,  143,  144,  145,  103,   11,   12,    7,   14,   79,
          109,   67,  148,  112,  113,  114,  115,  116,  117,  118,
            7,   67,   50,   51,   52,   77,   54,   79,    7,   67,
          102,  103,   77,  103,   79,   80,   67,   83,   66,  109,
           79,   47,   48,   77,    7,   83,   35,   53,  118,   55,
           56,   57,   58,   59,   60,   61,   62,   63,   64,   65,
          112,   67,   68,   69,   70,    8,    9,   10,    7,   75,
           76,   77,  128,   79,  146,    7,    7,   83,    8,    9,
           10,    1,    7,  129,  130,   28,  142,   30,   31,   32,
           33,  143,  130,  149,  146,  151,  102,  128,   28,   13,
           30,   31,   32,   29,  156,  151,  112,   13,    7,  143,
          149,  142,  146,  151,  120,  121,  150,  156,  149,   15,
          151,    7,   35,  129,  130,   67,  132,  133,  134,  135,
          136,  137,  138,    8,    9,   10,    7,  143,  144,  145,
          146,   15,    8,    9,   10,  151,    7,  153,  148,  155,
          156,   71,   72,   73,   74,    0,    1,   77,  147,    7,
           67,   81,   28,  152,   84,   85,   86,   87,   88,   89,
           90,   91,   92,   93,   79,   95,   96,   97,   98,   99,
          100,  101,  102,   29,  104,  105,  128,   79,  108,  152,
           82,  111,  112,  113,  114,    8,    9,   10,   79,  119,
          142,    7,  122,  123,  124,  125,    7,  149,   47,   48,
           49,   67,   67,  152,    7,   28,   67,   30,   31,   79,
          152,  128,  154,  149,   29,    7,   71,  152,  148,  149,
          150,  112,   77,    7,  147,  142,   81,   15,  152,   84,
           85,   86,  149,   88,   35,   90,   15,   92,  140,  141,
           95,  156,  112,  152,   99,  100,  101,  102,  103,  104,
          105,   77,  148,  108,  109,    1,  111,  112,  113,  114,
          130,  131,  128,  128,  119,  156,   67,  122,  123,  124,
          125,  152,   15,  154,   15,   82,    8,    9,   10,   72,
           73,  152,   83,   29,  149,   29,  156,   15,  143,  150,
          148,   29,  148,  148,  149,  150,   28,   54,   30,   31,
           32,   33,   34,   35,   36,   37,   38,   39,   40,   41,
           42,   43,   44,   45,   46,   47,   48,   49,   50,   51,
           52,   29,   54,  149,    1,   71,   29,  146,  129,  130,
          149,   77,  139,   29,   66,   81,  152,   79,   84,   85,
           86,  152,   88,   35,   90,  148,   92,   72,   73,   95,
          151,   35,   29,   99,  100,  101,    1,   35,  104,  105,
          152,   35,  108,   67,  148,  111,  112,   97,   98,   66,
          112,  102,  103,  119,  148,  149,   74,  106,  107,  148,
          149,  148,  149,   77,   29,   78,   77,   77,  130,  131,
           77,   77,   77,   82,   71,   77,   77,   77,   77,   82,
           77,   77,  148,  149,   81,   93,   79,   84,   85,   86,
          152,   88,   79,   90,  156,   92,   79,   79,   95,   79,
           86,   89,   99,  100,  101,    1,   71,  104,  105,   87,
           91,  108,   77,  102,  111,  112,   81,   94,   94,   84,
           85,   86,  119,   88,   94,   90,   94,   92,  102,   96,
           95,  110,   96,   29,   99,  100,  101,    1,  109,  104,
          105,  126,  126,  108,  139,   -1,  111,  112,  123,  139,
          123,  148,  149,   -1,  119,  127,  147,  123,  150,   -1,
           -1,   -1,   -1,   -1,   -1,   29,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,  142,   -1,   71,   -1,   -1,   -1,  142,
          142,   77,  142,  148,  149,   81,   -1,  146,   84,   85,
           86,  146,   88,  146,   90,  146,   92,  146,  146,   95,
          146,  146,  149,   99,  100,  101,    1,   71,  104,  105,
          148,  148,  108,   77,  148,  111,  112,   81,  148,  148,
           84,   85,   86,  119,   88,  148,   90,  148,   92,  148,
          148,   95,  148,  148,  148,   99,  100,  101,  148,  148,
          104,  105,  148,  148,  108,  148,  148,  111,  112,  148,
          148,  148,  148,  149,  148,  119,  148,  148,  148,  148,
           15,   16,   17,   18,   19,   20,   21,   22,   23,   24,
           25,   26,   27,  148,  150,  149,   71,  149,  149,  149,
          149,  149,   77,  149,  148,  149,   81,  149,  149,   84,
           85,   86,  149,   88,  149,   90,  149,   92,  149,  149,
           95,   56,   57,  150,   99,  100,  101,  150,  150,  104,
          105,  150,   67,  108,  150,  150,  111,  112,  150,  150,
          150,  150,  150,  150,  119,  150,  150,  150,  150,  150,
          150,  150,  150,  150,  150,  150,  150,  150,  150,   -1,
          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
          151,  151,  151,  148,  149,  151,  153,  151,  151,  151,
          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
          151,   -1,  152,  128,  152,  152,  152,  152,  152,  152,
          152,  152,  152,  152,  152,  152,  152,  142,  152,  152,
          152,  152,  152,  152,  149,  152,  152,  155,  152,  152,
           -1,  153,   -1,  154,  154,  154,  154,  154,  154
    );

    protected $actionBase = array(
            0,  220,  295,  101,  106,  536,   -2,   -2,   -2,   -2,
          -54,  473,  606,  574,  606,  404,  505,  675,  675,  675,
          151,  227,  458,  458,  458,  457,  442,  476,  466,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          294,    4,  234,  551,  693,  702,  696,  690,  703,  494,
          695,  694,  651,  652,  406,  653,  654,  655,  656,  698,
          719,  692,  701,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,   45,
           19,   56,  265,  265,  265,  265,  265,  265,  265,  265,
          265,  265,  265,  265,  265,  265,  265,  265,  265,  265,
          274,  274,  274,  327,  210,  197,   46,  715,   16,    3,
            3,    3,    3,    3,  -27,  -27,  -27,  -27,  349,  349,
           94,   94,  102,  102,  102,  102,  102,  102,  102,  102,
          102,  102,  102,  648,  639,  642,  643,  301,  301,  497,
           70,  408,  408,  408,  408,   88,  280,  343,  280,  475,
          712,   84,  109,  112,  112,  112,   68,  461,  248,  248,
          324,  324,  233,  233,  198,  233,  419,  419,  419,  259,
          259,  259,  259,  331,  259,  259,  259,  599,  467,   95,
          506,  645,  376,  503,  657,  285,  269,  208,  511,  525,
          235,  481,  235,  421,  425,  357,  507,  235,  235,  214,
          294,  381,  393,  533,  456,  366,  554,  292,  347,  284,
          383,  241,  598,  549,  700,  358,  699,  201,  279,  289,
          393,  393,  393,  334,  596,  523,  177,  226,  647,  620,
          215,  646,  641,  140,  254,  640,  339,  560,  471,  471,
          471,  471,  471,  471,  472,  471,  463,  680,  680,  459,
          490,  472,  659,  472,  471,  680,  472,  119,  472,  485,
          471,  486,  486,  463,  477,  498,  680,  680,  498,  459,
          472,  541,  540,  499,  479,  447,  447,  499,  472,  447,
          490,  447,   30,  685,  686,  454,  688,  684,  687,  661,
          683,  464,  619,  504,  495,  669,  668,  682,  460,  468,
          670,  681,  524,  532,  465,  422,  501,  446,  678,  481,
          522,  453,  453,  453,  446,  674,  453,  453,  453,  453,
          453,  453,  453,  453,  724,  510,  484,  581,  580,  579,
          409,  578,  515,  500,  407,  604,  480,  524,  524,  650,
          718,  673,  469,  667,  706,  679,  552,  153,  371,  666,
          649,  517,  470,  519,  665,  602,  704,  474,  638,  524,
          635,  453,  660,  689,  722,  723,  677,  720,  713,  161,
          521,  576,   66,  721,  658,  601,  600,  547,  717,  711,
          710,  496,   66,  573,  492,  697,  462,  662,  488,  663,
          617,  362,  266,  631,  676,  572,  716,  714,  708,  571,
          568,  615,  613,  244,  671,  335,  452,  489,  567,  487,
          483,  628,  609,  664,  565,  564,  627,  623,  707,  493,
          522,  508,  491,  502,  482,  608,  594,  709,  412,  561,
          595,  556,  478,  555,  634,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,  134,  134,   -2,   -2,   -2,
            0,    0,    0,    0,   -2,  134,  134,  134,  134,  134,
          134,  134,  134,  134,  134,  134,  134,  134,  134,  134,
          134,  134,  134,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,  418,  418,  418,  418,  418,  418,  418,  418,  418,
          418,    0,  418,  418,  418,  418,  418,  418,  112,  112,
          112,  112,   88,   88,   88,   88,   88,   88,   88,   88,
           88,   88,   88,   88,   88,   88,   88,   41,   41,   41,
           41,  112,  112,   88,   41,   88,   88,   88,   88,    0,
           88,  248,   88,  248,  248,    0,    0,    0,    0,    0,
          471,  248,    0,    0,  235,  235,    0,    0,    0,    0,
          471,  471,  471,   88,   88,   88,   88,  471,   88,   88,
           88,  235,  248,    0,  420,  420,   66,  420,  420,    0,
            0,    0,  471,  471,    0,  477,    0,    0,    0,    0,
          680,    0,    0,    0,    0,    0,  453,  153,  667,    0,
           50,    0,    0,    0,    0,    0,  469,   50,  209,    0,
          209,    0,    0,    0,  453,  453,  453,    0,  469,  469,
            0,    0,   74,  469,    0,   74,   38,    0,    0,   38,
            0,   66
    );

    protected $actionDefault = array(
            3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  453,  453,  413,32767,32767,32767,32767,  280,
          280,  280,32767,  414,  414,  414,  414,  414,  414,  414,
        32767,32767,32767,32767,32767,  358,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  458,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,  341,  342,  344,  345,  279,  415,  232,
          457,  278,  116,  241,  234,  189,  119,  277,  220,  306,
          359,  308,  357,  361,  307,  284,  288,  289,  290,  291,
          292,  293,  294,  295,  296,  297,  298,  299,  283,  360,
          338,  337,  336,  304,  305,  309,  311,  282,  310,  327,
          328,  325,  326,  329,  330,  331,  332,  333,32767,32767,
          452,  452,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,  264,  264,  264,  264,  318,  319,32767,
          265,  224,  224,  224,  224,32767,  224,32767,32767,32767,
        32767,  406,  335,  313,  314,  312,32767,  386,32767,  388,
        32767,32767,  301,  303,  381,  285,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,  383,  416,  416,32767,32767,
        32767,  375,32767,  157,  208,  210,  391,32767,32767,32767,
        32767,32767,  323,32767,32767,32767,32767,32767,32767,  466,
        32767,32767,32767,32767,32767,  416,32767,  416,32767,32767,
          315,  316,  317,32767,32767,32767,  416,  416,32767,32767,
          416,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,  161,32767,32767,  389,  389,32767,
        32767,  161,  384,  161,32767,32767,  161,  412,  161,  174,
        32767,  172,  172,32767,32767,  176,32767,  430,  176,32767,
          161,  194,  194,  367,  163,  226,  226,  367,  161,  226,
        32767,  226,32767,32767,32767,   82,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  377,32767,32767,32767,32767,  407,  428,  375,
        32767,  321,  322,  324,32767,  418,  346,  347,  348,  349,
          350,  351,  352,  354,32767,  380,32767,32767,32767,32767,
        32767,32767,   84,  108,  240,32767,  465,   84,  378,32767,
          465,32767,32767,32767,32767,32767,32767,  281,32767,32767,
        32767,   84,32767,   84,32767,32767,32767,32767,  416,  379,
        32767,  320,  392,  434,32767,32767,  417,32767,32767,  215,
           84,32767,  175,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,  177,32767,32767,  416,32767,32767,32767,32767,
        32767,32767,  276,32767,32767,32767,32767,32767,  416,32767,
        32767,32767,32767,  219,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,32767,32767,32767,32767,   82,
           60,32767,  258,32767,32767,32767,32767,32767,32767,32767,
        32767,32767,32767,32767,32767,  121,  121,    3,    3,  121,
          121,  121,  121,  121,  121,  121,  121,  121,  121,  121,
          121,  121,  121,  121,  243,  154,  243,  202,  243,  243,
          205,  194,  194,  250
    );

    protected $goto = array(
          159,  159,  132,  132,  132,  142,  144,  175,  160,  157,
          157,  157,  157,  158,  158,  158,  158,  158,  158,  158,
          153,  154,  155,  156,  172,  170,  173,  401,  402,  292,
          403,  406,  407,  408,  409,  410,  411,  412,  413,  841,
          133,  134,  135,  136,  137,  138,  139,  140,  141,  143,
          169,  171,  174,  190,  193,  194,  195,  196,  198,  199,
          200,  201,  202,  203,  204,  205,  206,  207,  227,  228,
          243,  244,  245,  310,  311,  312,  451,  176,  177,  178,
          179,  180,  181,  182,  183,  184,  185,  186,  187,  188,
          145,  189,  146,  161,  162,  163,  191,  164,  147,  148,
          149,  165,  150,  192,  130,  166,  167,  151,  168,  152,
          511,  422,  452,  813,  523,  677,  639,  503,  811,  271,
          641,  427,  427,  427,  640,  754,  453,  734,  427,  547,
            5,  416,  763,  758,  418,  421,  434,  454,  455,  457,
          467,  486,  440,  443,  427,  550,  474,  476,  497,  501,
          751,  506,  507,  765,  514,  750,  516,  522,  761,  524,
          317,  488,  307,  307,  305,  305,  252,  253,  276,  448,
          255,  316,  277,  320,  475,  404,  404,  404,  404,  404,
          404,  404,  404,  404,  404,  404,  404,  404,  404,  404,
          926,  249,  240,  427,  427,  441,  460,  427,  427,  664,
          427,  768,  768, 1005, 1005,  492,  415,  671,  502,  428,
          291,  224,  415,  225,  226,  449,  405,  405,  405,  405,
          405,  405,  405,  405,  405,  405,  405,  405,  405,  405,
          405,  664,  664,  294, 1015, 1015,  433,  521,  444,  450,
          464, 1016, 1016,  698, 1015,  469,  470,  517,  738,  927,
          364, 1016,  472,  272,  327,  785,  446,  293,    8, 1009,
          928,  981,  487, 1018,  306, 1002,  888,  781,  772,  278,
          992,  321,  300,  660,  303,  534,  658,  325,  919,  924,
          789,  657,  657,  665,  665,  665,  667,  358,  656,  668,
          466,  792,  742,  369,  829,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,  273,  274,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,  930,    0,  775,  775,  775,
          775,  930,  775,    0,  775,    0,    0,    0,    0,    0,
          821,    0,  989,    0,    0,    0,    0,    0,  989,    0,
            0,    0,    0,    0,    0,  732,  732,  732,  732,    0,
          727,  733,  500, 1000, 1000,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          987,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,  791,    0,  791,    0,    0,    0,    0,
          993,  994
    );

    protected $gotoCheck = array(
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           51,    8,    7,    7,    7,   10,   10,    7,    7,   63,
           12,    8,    8,    8,   11,   10,   77,   10,    8,   10,
           89,   10,   10,   10,   38,   38,   38,   38,   38,   38,
           35,   35,   28,    8,    8,   28,   28,   28,   28,   28,
           28,   28,   28,   28,   28,   28,   28,   28,   28,   28,
           45,   45,   45,   45,   45,   45,   45,   45,   45,   45,
           45,   45,   45,   45,   45,  110,  110,  110,  110,  110,
          110,  110,  110,  110,  110,  110,  110,  110,  110,  110,
           73,  109,  109,    8,    8,    8,    8,    8,    8,   19,
            8,   68,   68,   68,   68,   55,  106,   25,   55,    8,
           55,   59,  106,   59,   59,    8,  111,  111,  111,  111,
          111,  111,  111,  111,  111,  111,  111,  111,  111,  111,
          111,   19,   19,   52,  121,  121,   52,    5,   52,    2,
            2,  122,  122,   44,  121,   54,   54,   54,   29,   73,
           52,  122,   61,   61,   61,   75,  112,   41,   52,  120,
           73,   73,   43,  121,   42,  118,   93,   72,   70,   14,
          115,   18,    9,   21,   13,   65,   20,   17,   99,  101,
           76,   19,   19,   19,   19,   19,   19,   57,   19,   22,
           58,   78,   62,   97,   91,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   63,   63,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   51,   -1,   51,   51,   51,
           51,   51,   51,   -1,   51,   -1,   -1,   -1,   -1,   -1,
           89,   -1,   77,   -1,   -1,   -1,   -1,   -1,   77,   -1,
           -1,   -1,   -1,   -1,   -1,   51,   51,   51,   51,   -1,
           51,   51,   51,   77,   77,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           77,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
           -1,   -1,   -1,   77,   -1,   77,   -1,   -1,   -1,   -1,
           77,   77
    );

    protected $gotoBase = array(
            0,    0, -288,    0,    0,  227,    0,  109, -135,    9,
          114,  122,  118,   -4,   23,    0,    0,  -52,   14,  -47,
           -3,   15,  -64,  -20,    0,  200,    0,    0, -384,  232,
            0,    0,    0,    0,    0,  110,    0,    0,  100,    0,
            0,  225,   51,   53,  229,  -48,    0,    0,    0,    0,
            0,  106, -110,    0,   13, -161,    0,  -65,  -68, -335,
            0,   -8,  -67, -243,    0,  -15,    0,    0,   -7,    0,
           32,    0,   29,  -96,    0,  234,   -2,  123,  -63,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,  120,
            0,  -76,    0,   31,    0,    0,    0,  -74,    0,  -60,
            0,  -62,    0,    0,    0,    0,  -23,    0,    0,  -56,
          -33,    8,  233,    0,    0,   19,    0,    0,   54,    0,
          235,   -5,    2,    0
    );

    protected $gotoDefault = array(
        -32768,  372,  555,    2,  556,  627,  635,  481,  392,  423,
          736,  678,  679,  296,  333,  393,  295,  322,  318,  666,
          659,  661,  669,  131,  323,  672,    1,  674,  429,  706,
          284,  682,  285,  496,  684,  436,  686,  687,  417,  297,
          298,  437,  304,  468,  697,  197,  301,  699,  283,  700,
          709,  286,  499,  478,  458,  491,  394,  355,  465,  223,
          445,  462,  741,  270,  749,  539,  757,  760,  395,  459,
          771,  360,  779,  944,  313,  784,  790,  976,  793,  796,
          340,  324,  471,  800,  801,    4,  805,  512,  513,  820,
          230,  828,  840,  338,  907,  909,  431,  366,  920,  352,
          326,  923,  980,  345,  396,  356,  936,  254,  275,  239,
          397,  241,  414, 1008,  398,  357,  983,  308, 1003,  347,
         1010, 1017,  268,  463
    );

    protected $ruleToNonTerminal = array(
            0,    1,    3,    3,    2,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
            5,    5,    5,    6,    6,    6,    6,    6,    6,    6,
            7,    7,    8,    8,    9,    4,    4,    4,    4,    4,
            4,    4,    4,    4,    4,    4,   14,   14,   15,   15,
           15,   15,   17,   17,   13,   13,   18,   18,   19,   19,
           20,   20,   21,   21,   16,   16,   22,   24,   24,   25,
           26,   26,   28,   27,   27,   27,   27,   29,   29,   29,
           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
           29,   29,   29,   29,   29,   29,   29,   29,   29,   29,
           29,   29,   10,   10,   48,   48,   50,   49,   49,   42,
           42,   52,   52,   53,   53,   11,   12,   12,   12,   56,
           56,   56,   57,   57,   60,   60,   58,   58,   61,   61,
           36,   36,   44,   44,   47,   47,   47,   46,   46,   62,
           37,   37,   37,   37,   63,   63,   64,   64,   65,   65,
           34,   34,   30,   30,   66,   32,   32,   67,   31,   31,
           33,   33,   43,   43,   43,   54,   54,   69,   69,   70,
           70,   72,   72,   72,   71,   71,   55,   55,   73,   73,
           74,   74,   75,   75,   75,   39,   39,   76,   40,   40,
           78,   78,   59,   59,   79,   79,   79,   79,   84,   84,
           85,   85,   86,   86,   86,   86,   86,   87,   88,   88,
           83,   83,   80,   80,   82,   82,   90,   90,   89,   89,
           89,   89,   89,   89,   81,   81,   91,   91,   41,   41,
           35,   35,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   23,   23,   23,   23,   23,   23,
           23,   23,   23,   23,   98,   92,   92,   97,   97,  100,
          100,  101,  102,  102,  102,  106,  106,   51,   51,   51,
           93,   93,  104,  104,   94,   94,   96,   96,   96,   99,
           99,  110,  110,  111,  111,  111,   95,   95,   95,   95,
           95,   95,   95,   95,   95,   95,   95,   95,   95,   95,
           95,   95,  113,  113,   38,   38,  108,  108,  108,  103,
          103,  103,  114,  114,  114,  114,  114,  114,   45,   45,
           45,   77,   77,   77,  116,  107,  107,  107,  107,  107,
          107,  105,  105,  105,  115,  115,  115,   68,  117,  117,
          118,  118,  118,  112,  112,  119,  119,  120,  120,  120,
          120,  109,  109,  109,  109,  122,  121,  121,  121,  121,
          121,  121,  121,  123,  123,  123
    );

    protected $ruleToLength = array(
            1,    1,    2,    0,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
            1,    1,    1,    3,    1,    1,    1,    1,    1,    3,
            5,    4,    3,    4,    2,    3,    1,    1,    7,    8,
            6,    7,    3,    1,    3,    1,    3,    1,    1,    3,
            1,    2,    1,    2,    3,    1,    3,    3,    1,    3,
            2,    0,    1,    1,    1,    1,    1,    3,    7,   10,
            5,    7,    9,    5,    3,    3,    3,    3,    3,    3,
            1,    2,    5,    7,    9,    5,    6,    3,    3,    2,
            2,    1,    1,    1,    0,    2,    8,    0,    4,    1,
            3,    0,    1,    0,    1,   10,    7,    6,    5,    1,
            2,    2,    0,    2,    0,    2,    0,    2,    1,    3,
            1,    4,    1,    4,    1,    1,    4,    1,    3,    3,
            3,    4,    4,    5,    0,    2,    4,    3,    1,    1,
            1,    4,    0,    2,    5,    0,    2,    6,    0,    2,
            0,    3,    1,    2,    1,    1,    0,    1,    3,    4,
            6,    1,    1,    1,    0,    1,    0,    2,    2,    3,
            1,    3,    1,    2,    2,    3,    1,    1,    3,    1,
            1,    3,    2,    0,    3,    3,    9,    3,    1,    3,
            0,    2,    4,    5,    4,    4,    4,    3,    1,    1,
            1,    3,    1,    1,    0,    1,    1,    2,    1,    1,
            1,    1,    1,    1,    1,    3,    1,    3,    3,    1,
            0,    1,    1,    3,    3,    4,    1,    2,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            2,    2,    2,    2,    3,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
            3,    2,    2,    2,    2,    3,    3,    3,    3,    3,
            3,    3,    3,    3,    3,    3,    5,    4,    3,    4,
            4,    2,    2,    4,    2,    2,    2,    2,    2,    2,
            2,    2,    2,    2,    2,    1,    3,    2,    1,    2,
            4,    2,   10,   11,    7,    3,    2,    0,    4,    1,
            3,    2,    2,    2,    4,    1,    1,    1,    2,    3,
            1,    1,    1,    1,    0,    3,    0,    1,    1,    0,
            1,    1,    3,    4,    3,    1,    1,    1,    1,    1,
            1,    1,    1,    1,    1,    1,    1,    1,    3,    2,
            3,    3,    0,    1,    0,    1,    1,    3,    1,    1,
            3,    1,    1,    4,    4,    4,    1,    4,    1,    1,
            3,    1,    4,    2,    3,    1,    4,    4,    3,    3,
            3,    1,    3,    1,    1,    3,    1,    4,    3,    1,
            1,    1,    0,    0,    2,    3,    1,    3,    1,    4,
            2,    2,    2,    1,    2,    1,    1,    4,    3,    3,
            3,    6,    3,    1,    1,    1
    );

    protected function reduceRule0() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule1() {
         $this->semValue = $this->handleNamespaces($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule2() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule3() {
         $this->semValue = array();
    }

    protected function reduceRule4() {
         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule5() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule6() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule7() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule8() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule9() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule10() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule11() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule12() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule13() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule14() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule15() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule16() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule17() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule18() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule19() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule20() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule21() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule22() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule23() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule24() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule25() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule26() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule27() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule28() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule29() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule30() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule31() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule32() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule33() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule34() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule35() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule36() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule37() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule38() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule39() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule40() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule41() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule42() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule43() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule44() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule45() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule46() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule47() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule48() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule49() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule50() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule51() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule52() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule53() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule54() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule55() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule56() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule57() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule58() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule59() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule60() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule61() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule62() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule63() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule64() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule65() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule66() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule67() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule68() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule69() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule70() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule71() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule72() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule73() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule74() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule75() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule76() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule77() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule78() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule79() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule80() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule81() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule82() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule83() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule84() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule85() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule86() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule87() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule88() {
         $this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule89() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule90() {
         $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule91() {
         $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule92() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule93() {
         $this->semValue = new Stmt\Use_($this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule94() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule95() {
         $this->semValue = new Stmt\Const_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule96() {
         $this->semValue = Stmt\Use_::TYPE_FUNCTION;
    }

    protected function reduceRule97() {
         $this->semValue = Stmt\Use_::TYPE_CONSTANT;
    }

    protected function reduceRule98() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], $this->semStack[$this->stackPos-(7-2)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule99() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(8-4)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(8-7)], $this->semStack[$this->stackPos-(8-2)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule100() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule101() {
         $this->semValue = new Stmt\GroupUse(new Name($this->semStack[$this->stackPos-(7-3)], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-6)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule102() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule103() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule104() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule105() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule106() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule107() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule108() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule109() {
         $this->semValue = new Stmt\UseUse($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule110() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule111() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule112() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
    }

    protected function reduceRule113() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)]; $this->semValue->type = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule114() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule115() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule116() {
         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule117() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule118() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule119() {
         $this->semValue = new Node\Const_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule120() {
         if (is_array($this->semStack[$this->stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); } else { $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)]; };
    }

    protected function reduceRule121() {
         $this->semValue = array();
    }

    protected function reduceRule122() {
         $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $nop = null; };
            if ($nop !== null) { $this->semStack[$this->stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule123() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule124() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule125() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule126() {
         throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule127() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments']) && isset($stmts[0])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
    }

    protected function reduceRule128() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(7-3)], ['stmts' => is_array($this->semStack[$this->stackPos-(7-5)]) ? $this->semStack[$this->stackPos-(7-5)] : array($this->semStack[$this->stackPos-(7-5)]), 'elseifs' => $this->semStack[$this->stackPos-(7-6)], 'else' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule129() {
         $this->semValue = new Stmt\If_($this->semStack[$this->stackPos-(10-3)], ['stmts' => $this->semStack[$this->stackPos-(10-6)], 'elseifs' => $this->semStack[$this->stackPos-(10-7)], 'else' => $this->semStack[$this->stackPos-(10-8)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule130() {
         $this->semValue = new Stmt\While_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule131() {
         $this->semValue = new Stmt\Do_($this->semStack[$this->stackPos-(7-5)], is_array($this->semStack[$this->stackPos-(7-2)]) ? $this->semStack[$this->stackPos-(7-2)] : array($this->semStack[$this->stackPos-(7-2)]), $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule132() {
         $this->semValue = new Stmt\For_(['init' => $this->semStack[$this->stackPos-(9-3)], 'cond' => $this->semStack[$this->stackPos-(9-5)], 'loop' => $this->semStack[$this->stackPos-(9-7)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule133() {
         $this->semValue = new Stmt\Switch_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule134() {
         $this->semValue = new Stmt\Break_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule135() {
         $this->semValue = new Stmt\Continue_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule136() {
         $this->semValue = new Stmt\Return_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule137() {
         $this->semValue = new Stmt\Global_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule138() {
         $this->semValue = new Stmt\Static_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule139() {
         $this->semValue = new Stmt\Echo_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule140() {
         $this->semValue = new Stmt\InlineHTML($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule141() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule142() {
         $this->semValue = new Stmt\Unset_($this->semStack[$this->stackPos-(5-3)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule143() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(7-3)], $this->semStack[$this->stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$this->stackPos-(7-5)][1], 'stmts' => $this->semStack[$this->stackPos-(7-7)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule144() {
         $this->semValue = new Stmt\Foreach_($this->semStack[$this->stackPos-(9-3)], $this->semStack[$this->stackPos-(9-7)][0], ['keyVar' => $this->semStack[$this->stackPos-(9-5)], 'byRef' => $this->semStack[$this->stackPos-(9-7)][1], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule145() {
         $this->semValue = new Stmt\Declare_($this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule146() {
         $this->semValue = new Stmt\TryCatch($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-5)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule147() {
         $this->semValue = new Stmt\Throw_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule148() {
         $this->semValue = new Stmt\Goto_($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule149() {
         $this->semValue = new Stmt\Label($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule150() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule151() {
         $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule152() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule153() {
         $startAttributes = $this->startAttributeStack[$this->stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $this->semValue = null; };
            if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
    }

    protected function reduceRule154() {
         $this->semValue = array();
    }

    protected function reduceRule155() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule156() {
         $this->semValue = new Stmt\Catch_($this->semStack[$this->stackPos-(8-3)], substr($this->semStack[$this->stackPos-(8-4)], 1), $this->semStack[$this->stackPos-(8-7)], $this->startAttributeStack[$this->stackPos-(8-1)] + $this->endAttributes);
    }

    protected function reduceRule157() {
         $this->semValue = null;
    }

    protected function reduceRule158() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule159() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule160() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule161() {
         $this->semValue = false;
    }

    protected function reduceRule162() {
         $this->semValue = true;
    }

    protected function reduceRule163() {
         $this->semValue = false;
    }

    protected function reduceRule164() {
         $this->semValue = true;
    }

    protected function reduceRule165() {
         $this->semValue = new Stmt\Function_($this->semStack[$this->stackPos-(10-3)], ['byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-5)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule166() {
         $this->semValue = new Stmt\Class_($this->semStack[$this->stackPos-(7-2)], ['type' => $this->semStack[$this->stackPos-(7-1)], 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes);
    }

    protected function reduceRule167() {
         $this->semValue = new Stmt\Interface_($this->semStack[$this->stackPos-(6-2)], ['extends' => $this->semStack[$this->stackPos-(6-3)], 'stmts' => $this->semStack[$this->stackPos-(6-5)]], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule168() {
         $this->semValue = new Stmt\Trait_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule169() {
         $this->semValue = 0;
    }

    protected function reduceRule170() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule171() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule172() {
         $this->semValue = null;
    }

    protected function reduceRule173() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule174() {
         $this->semValue = array();
    }

    protected function reduceRule175() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule176() {
         $this->semValue = array();
    }

    protected function reduceRule177() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule178() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule179() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule180() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule181() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule182() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule183() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule184() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule185() {
         $this->semValue = null;
    }

    protected function reduceRule186() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule187() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule188() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule189() {
         $this->semValue = new Stmt\DeclareDeclare($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule190() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule191() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule192() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule193() {
         $this->semValue = $this->semStack[$this->stackPos-(5-3)];
    }

    protected function reduceRule194() {
         $this->semValue = array();
    }

    protected function reduceRule195() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule196() {
         $this->semValue = new Stmt\Case_($this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule197() {
         $this->semValue = new Stmt\Case_(null, $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule198() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule199() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule200() {
         $this->semValue = is_array($this->semStack[$this->stackPos-(1-1)]) ? $this->semStack[$this->stackPos-(1-1)] : array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule201() {
         $this->semValue = $this->semStack[$this->stackPos-(4-2)];
    }

    protected function reduceRule202() {
         $this->semValue = array();
    }

    protected function reduceRule203() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule204() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(5-3)], is_array($this->semStack[$this->stackPos-(5-5)]) ? $this->semStack[$this->stackPos-(5-5)] : array($this->semStack[$this->stackPos-(5-5)]), $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule205() {
         $this->semValue = array();
    }

    protected function reduceRule206() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule207() {
         $this->semValue = new Stmt\ElseIf_($this->semStack[$this->stackPos-(6-3)], $this->semStack[$this->stackPos-(6-6)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule208() {
         $this->semValue = null;
    }

    protected function reduceRule209() {
         $this->semValue = new Stmt\Else_(is_array($this->semStack[$this->stackPos-(2-2)]) ? $this->semStack[$this->stackPos-(2-2)] : array($this->semStack[$this->stackPos-(2-2)]), $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule210() {
         $this->semValue = null;
    }

    protected function reduceRule211() {
         $this->semValue = new Stmt\Else_($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule212() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule213() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-2)], true);
    }

    protected function reduceRule214() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)], false);
    }

    protected function reduceRule215() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule216() {
         $this->semValue = array();
    }

    protected function reduceRule217() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule218() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule219() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(4-4)], 1), null, $this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-2)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule220() {
         $this->semValue = new Node\Param(substr($this->semStack[$this->stackPos-(6-4)], 1), $this->semStack[$this->stackPos-(6-6)], $this->semStack[$this->stackPos-(6-1)], $this->semStack[$this->stackPos-(6-2)], $this->semStack[$this->stackPos-(6-3)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule221() {
         $this->semValue = $this->handleScalarTypes($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule222() {
         $this->semValue = 'array';
    }

    protected function reduceRule223() {
         $this->semValue = 'callable';
    }

    protected function reduceRule224() {
         $this->semValue = null;
    }

    protected function reduceRule225() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule226() {
         $this->semValue = null;
    }

    protected function reduceRule227() {
         $this->semValue = $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule228() {
         $this->semValue = array();
    }

    protected function reduceRule229() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule230() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule231() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule232() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(1-1)], false, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule233() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], true, false, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule234() {
         $this->semValue = new Node\Arg($this->semStack[$this->stackPos-(2-2)], false, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule235() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule236() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule237() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule238() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule239() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule240() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule241() {
         $this->semValue = new Stmt\StaticVar(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule242() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule243() {
         $this->semValue = array();
    }

    protected function reduceRule244() {
         $this->semValue = new Stmt\Property($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule245() {
         $this->semValue = new Stmt\ClassConst($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule246() {
         $this->semValue = new Stmt\ClassMethod($this->semStack[$this->stackPos-(9-4)], ['type' => $this->semStack[$this->stackPos-(9-1)], 'byRef' => $this->semStack[$this->stackPos-(9-3)], 'params' => $this->semStack[$this->stackPos-(9-6)], 'returnType' => $this->semStack[$this->stackPos-(9-8)], 'stmts' => $this->semStack[$this->stackPos-(9-9)]], $this->startAttributeStack[$this->stackPos-(9-1)] + $this->endAttributes);
    }

    protected function reduceRule247() {
         $this->semValue = new Stmt\TraitUse($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule248() {
         $this->semValue = array();
    }

    protected function reduceRule249() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule250() {
         $this->semValue = array();
    }

    protected function reduceRule251() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule252() {
         $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule253() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(5-1)][0], $this->semStack[$this->stackPos-(5-1)][1], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule254() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], $this->semStack[$this->stackPos-(4-3)], null, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule255() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule256() {
         $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$this->stackPos-(4-1)][0], $this->semStack[$this->stackPos-(4-1)][1], null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule257() {
         $this->semValue = array($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)]);
    }

    protected function reduceRule258() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule259() {
         $this->semValue = array(null, $this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule260() {
         $this->semValue = null;
    }

    protected function reduceRule261() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule262() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule263() {
         $this->semValue = 0;
    }

    protected function reduceRule264() {
         $this->semValue = 0;
    }

    protected function reduceRule265() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule266() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule267() {
         Stmt\Class_::verifyModifier($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]); $this->semValue = $this->semStack[$this->stackPos-(2-1)] | $this->semStack[$this->stackPos-(2-2)];
    }

    protected function reduceRule268() {
         $this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
    }

    protected function reduceRule269() {
         $this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
    }

    protected function reduceRule270() {
         $this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
    }

    protected function reduceRule271() {
         $this->semValue = Stmt\Class_::MODIFIER_STATIC;
    }

    protected function reduceRule272() {
         $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
    }

    protected function reduceRule273() {
         $this->semValue = Stmt\Class_::MODIFIER_FINAL;
    }

    protected function reduceRule274() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule275() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule276() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(1-1)], 1), null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule277() {
         $this->semValue = new Stmt\PropertyProperty(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule278() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule279() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule280() {
         $this->semValue = array();
    }

    protected function reduceRule281() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule282() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule283() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule284() {
         $this->semValue = new Expr\Assign($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule285() {
         $this->semValue = new Expr\AssignRef($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule286() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule287() {
         $this->semValue = new Expr\Clone_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule288() {
         $this->semValue = new Expr\AssignOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule289() {
         $this->semValue = new Expr\AssignOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule290() {
         $this->semValue = new Expr\AssignOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule291() {
         $this->semValue = new Expr\AssignOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule292() {
         $this->semValue = new Expr\AssignOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule293() {
         $this->semValue = new Expr\AssignOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule294() {
         $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule295() {
         $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule296() {
         $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule297() {
         $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule298() {
         $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule299() {
         $this->semValue = new Expr\AssignOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule300() {
         $this->semValue = new Expr\PostInc($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule301() {
         $this->semValue = new Expr\PreInc($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule302() {
         $this->semValue = new Expr\PostDec($this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule303() {
         $this->semValue = new Expr\PreDec($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule304() {
         $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule305() {
         $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule306() {
         $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule307() {
         $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule308() {
         $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule309() {
         $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule310() {
         $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule311() {
         $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule312() {
         $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule313() {
         $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule314() {
         $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule315() {
         $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule316() {
         $this->semValue = new Expr\BinaryOp\Div($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule317() {
         $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule318() {
         $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule319() {
         $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule320() {
         $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule321() {
         $this->semValue = new Expr\UnaryPlus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule322() {
         $this->semValue = new Expr\UnaryMinus($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule323() {
         $this->semValue = new Expr\BooleanNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule324() {
         $this->semValue = new Expr\BitwiseNot($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule325() {
         $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule326() {
         $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule327() {
         $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule328() {
         $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule329() {
         $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule330() {
         $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule331() {
         $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule332() {
         $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule333() {
         $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule334() {
         $this->semValue = new Expr\Instanceof_($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule335() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule336() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(5-1)], $this->semStack[$this->stackPos-(5-3)], $this->semStack[$this->stackPos-(5-5)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
    }

    protected function reduceRule337() {
         $this->semValue = new Expr\Ternary($this->semStack[$this->stackPos-(4-1)], null, $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule338() {
         $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule339() {
         $this->semValue = new Expr\Isset_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule340() {
         $this->semValue = new Expr\Empty_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule341() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule342() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule343() {
         $this->semValue = new Expr\Eval_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule344() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule345() {
         $this->semValue = new Expr\Include_($this->semStack[$this->stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule346() {
         $this->semValue = new Expr\Cast\Int_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule347() {
         $this->semValue = new Expr\Cast\Double($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule348() {
         $this->semValue = new Expr\Cast\String_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule349() {
         $this->semValue = new Expr\Cast\Array_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule350() {
         $this->semValue = new Expr\Cast\Object_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule351() {
         $this->semValue = new Expr\Cast\Bool_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule352() {
         $this->semValue = new Expr\Cast\Unset_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule353() {
         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes;
            $attrs['kind'] = strtolower($this->semStack[$this->stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
            $this->semValue = new Expr\Exit_($this->semStack[$this->stackPos-(2-2)], $attrs);
    }

    protected function reduceRule354() {
         $this->semValue = new Expr\ErrorSuppress($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule355() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule356() {
         $this->semValue = new Expr\ShellExec($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule357() {
         $this->semValue = new Expr\Print_($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule358() {
         $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule359() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(2-2)], null, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule360() {
         $this->semValue = new Expr\Yield_($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-2)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule361() {
         $this->semValue = new Expr\YieldFrom($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule362() {
         $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$this->stackPos-(10-2)], 'params' => $this->semStack[$this->stackPos-(10-4)], 'uses' => $this->semStack[$this->stackPos-(10-6)], 'returnType' => $this->semStack[$this->stackPos-(10-7)], 'stmts' => $this->semStack[$this->stackPos-(10-9)]], $this->startAttributeStack[$this->stackPos-(10-1)] + $this->endAttributes);
    }

    protected function reduceRule363() {
         $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$this->stackPos-(11-3)], 'params' => $this->semStack[$this->stackPos-(11-5)], 'uses' => $this->semStack[$this->stackPos-(11-7)], 'returnType' => $this->semStack[$this->stackPos-(11-8)], 'stmts' => $this->semStack[$this->stackPos-(11-10)]], $this->startAttributeStack[$this->stackPos-(11-1)] + $this->endAttributes);
    }

    protected function reduceRule364() {
         $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$this->stackPos-(7-3)], 'implements' => $this->semStack[$this->stackPos-(7-4)], 'stmts' => $this->semStack[$this->stackPos-(7-6)]], $this->startAttributeStack[$this->stackPos-(7-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(7-2)]);
    }

    protected function reduceRule365() {
         $this->semValue = new Expr\New_($this->semStack[$this->stackPos-(3-2)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule366() {
         list($class, $ctorArgs) = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule367() {
         $this->semValue = array();
    }

    protected function reduceRule368() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule369() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule370() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule371() {
         $this->semValue = new Expr\ClosureUse(substr($this->semStack[$this->stackPos-(2-2)], 1), $this->semStack[$this->stackPos-(2-1)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule372() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule373() {
         $this->semValue = new Expr\FuncCall($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule374() {
         $this->semValue = new Expr\StaticCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule375() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule376() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule377() {
         $this->semValue = new Name($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule378() {
         $this->semValue = new Name\FullyQualified($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule379() {
         $this->semValue = new Name\Relative($this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule380() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule381() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule382() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule383() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule384() {
         $this->semValue = null;
    }

    protected function reduceRule385() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule386() {
         $this->semValue = array();
    }

    protected function reduceRule387() {
         $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$this->stackPos-(1-1)], '`'), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes));
    }

    protected function reduceRule388() {
         foreach ($this->semStack[$this->stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule389() {
         $this->semValue = array();
    }

    protected function reduceRule390() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule391() {
         $this->semValue = new Expr\ConstFetch($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule392() {
         $this->semValue = new Expr\ClassConstFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule393() {
         $attrs = $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(4-3)], $attrs);
    }

    protected function reduceRule394() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
            $this->semValue = new Expr\Array_($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule395() {
         $attrs = $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$this->stackPos-(1-1)][0] === "'" || ($this->semStack[$this->stackPos-(1-1)][1] === "'" && ($this->semStack[$this->stackPos-(1-1)][0] === 'b' || $this->semStack[$this->stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
            $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$this->stackPos-(1-1)]), $attrs);
    }

    protected function reduceRule396() {
         $this->semValue = Scalar\LNumber::fromString($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule397() {
         $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$this->stackPos-(1-1)]), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule398() {
         $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule399() {
         $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule400() {
         $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule401() {
         $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule402() {
         $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule403() {
         $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule404() {
         $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule405() {
         $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule406() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule407() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule408() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
            $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-2)]), $attrs);
    }

    protected function reduceRule409() {
         $attrs = $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
            $this->semValue = new Scalar\String_('', $attrs);
    }

    protected function reduceRule410() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule411() {
         $attrs = $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$this->stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$this->stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
            foreach ($this->semStack[$this->stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$this->stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$this->stackPos-(3-2)], $attrs);
    }

    protected function reduceRule412() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule413() {
        $this->semValue = $this->semStack[$this->stackPos];
    }

    protected function reduceRule414() {
         $this->semValue = null;
    }

    protected function reduceRule415() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule416() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule417() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule418() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule419() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule420() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule421() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule422() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule423() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule424() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule425() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule426() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule427() {
         $this->semValue = new Expr\MethodCall($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->semStack[$this->stackPos-(4-4)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule428() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule429() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule430() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule431() {
         $this->semValue = substr($this->semStack[$this->stackPos-(1-1)], 1);
    }

    protected function reduceRule432() {
         $this->semValue = $this->semStack[$this->stackPos-(4-3)];
    }

    protected function reduceRule433() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(2-2)], $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule434() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule435() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule436() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule437() {
         $this->semValue = new Expr\ArrayDimFetch($this->semStack[$this->stackPos-(4-1)], $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule438() {
         $this->semValue = new Expr\PropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule439() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule440() {
         $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$this->stackPos-(3-1)], $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule441() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule442() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule443() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule444() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule445() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule446() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule447() {
         $this->semValue = new Expr\List_($this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule448() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule449() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule450() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule451() {
         $this->semValue = $this->semStack[$this->stackPos-(1-1)];
    }

    protected function reduceRule452() {
         $this->semValue = null;
    }

    protected function reduceRule453() {
         $this->semValue = array();
    }

    protected function reduceRule454() {
         $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule455() {
         $this->semStack[$this->stackPos-(3-1)][] = $this->semStack[$this->stackPos-(3-3)]; $this->semValue = $this->semStack[$this->stackPos-(3-1)];
    }

    protected function reduceRule456() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule457() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(3-3)], $this->semStack[$this->stackPos-(3-1)], false, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule458() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(1-1)], null, false, $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule459() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(4-4)], $this->semStack[$this->stackPos-(4-1)], true, $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule460() {
         $this->semValue = new Expr\ArrayItem($this->semStack[$this->stackPos-(2-2)], null, true, $this->startAttributeStack[$this->stackPos-(2-1)] + $this->endAttributes);
    }

    protected function reduceRule461() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule462() {
         $this->semStack[$this->stackPos-(2-1)][] = $this->semStack[$this->stackPos-(2-2)]; $this->semValue = $this->semStack[$this->stackPos-(2-1)];
    }

    protected function reduceRule463() {
         $this->semValue = array($this->semStack[$this->stackPos-(1-1)]);
    }

    protected function reduceRule464() {
         $this->semValue = array($this->semStack[$this->stackPos-(2-1)], $this->semStack[$this->stackPos-(2-2)]);
    }

    protected function reduceRule465() {
         $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule466() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule467() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(4-1)], 1), $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
    }

    protected function reduceRule468() {
         $this->semValue = new Expr\PropertyFetch(new Expr\Variable(substr($this->semStack[$this->stackPos-(3-1)], 1), $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(3-3)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule469() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule470() {
         $this->semValue = new Expr\Variable($this->semStack[$this->stackPos-(3-2)], $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
    }

    protected function reduceRule471() {
         $this->semValue = new Expr\ArrayDimFetch(new Expr\Variable($this->semStack[$this->stackPos-(6-2)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes), $this->semStack[$this->stackPos-(6-4)], $this->startAttributeStack[$this->stackPos-(6-1)] + $this->endAttributes);
    }

    protected function reduceRule472() {
         $this->semValue = $this->semStack[$this->stackPos-(3-2)];
    }

    protected function reduceRule473() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule474() {
         $this->semValue = new Scalar\String_($this->semStack[$this->stackPos-(1-1)], $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }

    protected function reduceRule475() {
         $this->semValue = new Expr\Variable(substr($this->semStack[$this->stackPos-(1-1)], 1), $this->startAttributeStack[$this->stackPos-(1-1)] + $this->endAttributes);
    }
}
<?php

namespace PhpParser\Parser;

/* GENERATED file based on grammar/tokens.y */
final class Tokens
{
    const YYERRTOK = 256;
    const T_INCLUDE = 257;
    const T_INCLUDE_ONCE = 258;
    const T_EVAL = 259;
    const T_REQUIRE = 260;
    const T_REQUIRE_ONCE = 261;
    const T_LOGICAL_OR = 262;
    const T_LOGICAL_XOR = 263;
    const T_LOGICAL_AND = 264;
    const T_PRINT = 265;
    const T_YIELD = 266;
    const T_DOUBLE_ARROW = 267;
    const T_YIELD_FROM = 268;
    const T_PLUS_EQUAL = 269;
    const T_MINUS_EQUAL = 270;
    const T_MUL_EQUAL = 271;
    const T_DIV_EQUAL = 272;
    const T_CONCAT_EQUAL = 273;
    const T_MOD_EQUAL = 274;
    const T_AND_EQUAL = 275;
    const T_OR_EQUAL = 276;
    const T_XOR_EQUAL = 277;
    const T_SL_EQUAL = 278;
    const T_SR_EQUAL = 279;
    const T_POW_EQUAL = 280;
    const T_COALESCE = 281;
    const T_BOOLEAN_OR = 282;
    const T_BOOLEAN_AND = 283;
    const T_IS_EQUAL = 284;
    const T_IS_NOT_EQUAL = 285;
    const T_IS_IDENTICAL = 286;
    const T_IS_NOT_IDENTICAL = 287;
    const T_SPACESHIP = 288;
    const T_IS_SMALLER_OR_EQUAL = 289;
    const T_IS_GREATER_OR_EQUAL = 290;
    const T_SL = 291;
    const T_SR = 292;
    const T_INSTANCEOF = 293;
    const T_INC = 294;
    const T_DEC = 295;
    const T_INT_CAST = 296;
    const T_DOUBLE_CAST = 297;
    const T_STRING_CAST = 298;
    const T_ARRAY_CAST = 299;
    const T_OBJECT_CAST = 300;
    const T_BOOL_CAST = 301;
    const T_UNSET_CAST = 302;
    const T_POW = 303;
    const T_NEW = 304;
    const T_CLONE = 305;
    const T_EXIT = 306;
    const T_IF = 307;
    const T_ELSEIF = 308;
    const T_ELSE = 309;
    const T_ENDIF = 310;
    const T_LNUMBER = 311;
    const T_DNUMBER = 312;
    const T_STRING = 313;
    const T_STRING_VARNAME = 314;
    const T_VARIABLE = 315;
    const T_NUM_STRING = 316;
    const T_INLINE_HTML = 317;
    const T_CHARACTER = 318;
    const T_BAD_CHARACTER = 319;
    const T_ENCAPSED_AND_WHITESPACE = 320;
    const T_CONSTANT_ENCAPSED_STRING = 321;
    const T_ECHO = 322;
    const T_DO = 323;
    const T_WHILE = 324;
    const T_ENDWHILE = 325;
    const T_FOR = 326;
    const T_ENDFOR = 327;
    const T_FOREACH = 328;
    const T_ENDFOREACH = 329;
    const T_DECLARE = 330;
    const T_ENDDECLARE = 331;
    const T_AS = 332;
    const T_SWITCH = 333;
    const T_ENDSWITCH = 334;
    const T_CASE = 335;
    const T_DEFAULT = 336;
    const T_BREAK = 337;
    const T_CONTINUE = 338;
    const T_GOTO = 339;
    const T_FUNCTION = 340;
    const T_CONST = 341;
    const T_RETURN = 342;
    const T_TRY = 343;
    const T_CATCH = 344;
    const T_FINALLY = 345;
    const T_THROW = 346;
    const T_USE = 347;
    const T_INSTEADOF = 348;
    const T_GLOBAL = 349;
    const T_STATIC = 350;
    const T_ABSTRACT = 351;
    const T_FINAL = 352;
    const T_PRIVATE = 353;
    const T_PROTECTED = 354;
    const T_PUBLIC = 355;
    const T_VAR = 356;
    const T_UNSET = 357;
    const T_ISSET = 358;
    const T_EMPTY = 359;
    const T_HALT_COMPILER = 360;
    const T_CLASS = 361;
    const T_TRAIT = 362;
    const T_INTERFACE = 363;
    const T_EXTENDS = 364;
    const T_IMPLEMENTS = 365;
    const T_OBJECT_OPERATOR = 366;
    const T_LIST = 367;
    const T_ARRAY = 368;
    const T_CALLABLE = 369;
    const T_CLASS_C = 370;
    const T_TRAIT_C = 371;
    const T_METHOD_C = 372;
    const T_FUNC_C = 373;
    const T_LINE = 374;
    const T_FILE = 375;
    const T_COMMENT = 376;
    const T_DOC_COMMENT = 377;
    const T_OPEN_TAG = 378;
    const T_OPEN_TAG_WITH_ECHO = 379;
    const T_CLOSE_TAG = 380;
    const T_WHITESPACE = 381;
    const T_START_HEREDOC = 382;
    const T_END_HEREDOC = 383;
    const T_DOLLAR_OPEN_CURLY_BRACES = 384;
    const T_CURLY_OPEN = 385;
    const T_PAAMAYIM_NEKUDOTAYIM = 386;
    const T_NAMESPACE = 387;
    const T_NS_C = 388;
    const T_DIR = 389;
    const T_NS_SEPARATOR = 390;
    const T_ELLIPSIS = 391;
}
<?php

namespace PhpParser;

/*
 * This parser is based on a skeleton written by Moriyoshi Koizumi, which in
 * turn is based on work by Masato Bito.
 */
use PhpParser\Node\Name;

abstract class ParserAbstract implements Parser
{
    const SYMBOL_NONE = -1;

    /*
     * The following members will be filled with generated parsing data:
     */

    /** @var int Size of $tokenToSymbol map */
    protected $tokenToSymbolMapSize;
    /** @var int Size of $action table */
    protected $actionTableSize;
    /** @var int Size of $goto table */
    protected $gotoTableSize;

    /** @var int Symbol number signifying an invalid token */
    protected $invalidSymbol;
    /** @var int Symbol number of error recovery token */
    protected $errorSymbol;
    /** @var int Action number signifying default action */
    protected $defaultAction;
    /** @var int Rule number signifying that an unexpected token was encountered */
    protected $unexpectedTokenRule;

    protected $YY2TBLSTATE;
    protected $YYNLSTATES;

    /** @var array Map of lexer tokens to internal symbols */
    protected $tokenToSymbol;
    /** @var array Map of symbols to their names */
    protected $symbolToName;
    /** @var array Names of the production rules (only necessary for debugging) */
    protected $productions;

    /** @var array Map of states to a displacement into the $action table. The corresponding action for this
     *             state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
                   action is defaulted, i.e. $actionDefault[$state] should be used instead. */
    protected $actionBase;
    /** @var array Table of actions. Indexed according to $actionBase comment. */
    protected $action;
    /** @var array Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
     *             then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
    protected $actionCheck;
    /** @var array Map of states to their default action */
    protected $actionDefault;

    /** @var array Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
     *             non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
    protected $gotoBase;
    /** @var array Table of states to goto after reduction. Indexed according to $gotoBase comment. */
    protected $goto;
    /** @var array Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
     *             then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
    protected $gotoCheck;
    /** @var array Map of non-terminals to the default state to goto after their reduction */
    protected $gotoDefault;

    /** @var array Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
     *             determining the state to goto after reduction. */
    protected $ruleToNonTerminal;
    /** @var array Map of rules to the length of their right-hand side, which is the number of elements that have to
     *             be popped from the stack(s) on reduction. */
    protected $ruleToLength;

    /*
     * The following members are part of the parser state:
     */

    /** @var Lexer Lexer that is used when parsing */
    protected $lexer;
    /** @var mixed Temporary value containing the result of last semantic action (reduction) */
    protected $semValue;
    /** @var int Position in stacks (state stack, semantic value stack, attribute stack) */
    protected $stackPos;
    /** @var array Semantic value stack (contains values of tokens and semantic action results) */
    protected $semStack;
    /** @var array[] Start attribute stack */
    protected $startAttributeStack;
    /** @var array End attributes of last *shifted* token */
    protected $endAttributes;
    /** @var array Start attributes of last *read* token */
    protected $lookaheadStartAttributes;

    /** @var bool Whether to throw on first error */
    protected $throwOnError;
    /** @var Error[] Errors collected during last parse */
    protected $errors;

    /**
     * Creates a parser instance.
     *
     * @param Lexer $lexer A lexer
     * @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
     *                       thrown on first error, or if the parser should try to continue parsing the remaining code
     *                       and build a partial AST.
     */
    public function __construct(Lexer $lexer, array $options = array()) {
        $this->lexer = $lexer;
        $this->errors = array();
        $this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
    }

    /**
     * Get array of errors that occurred during the last parse.
     *
     * This method may only return multiple errors if the 'throwOnError' option is disabled.
     *
     * @return Error[]
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * Parses PHP code into a node tree.
     *
     * @param string $code The source code to parse
     *
     * @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
     *                     unable to recover from an error).
     */
    public function parse($code) {
        $this->lexer->startLexing($code);
        $this->errors = array();

        // We start off with no lookahead-token
        $symbol = self::SYMBOL_NONE;

        // The attributes for a node are taken from the first and last token of the node.
        // From the first token only the startAttributes are taken and from the last only
        // the endAttributes. Both are merged using the array union operator (+).
        $startAttributes = '*POISON';
        $endAttributes = '*POISON';
        $this->endAttributes = $endAttributes;

        // In order to figure out the attributes for the starting token, we have to keep
        // them in a stack
        $this->startAttributeStack = array();

        // Start off in the initial state and keep a stack of previous states
        $state = 0;
        $stateStack = array($state);

        // Semantic value stack (contains values of tokens and semantic action results)
        $this->semStack = array();

        // Current position in the stack(s)
        $this->stackPos = 0;

        $errorState = 0;

        for (;;) {
            //$this->traceNewState($state, $symbol);

            if ($this->actionBase[$state] == 0) {
                $rule = $this->actionDefault[$state];
            } else {
                if ($symbol === self::SYMBOL_NONE) {
                    // Fetch the next token id from the lexer and fetch additional info by-ref.
                    // The end attributes are fetched into a temporary variable and only set once the token is really
                    // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
                    // reduced after a token was read but not yet shifted.
                    $tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);

                    // map the lexer token id to the internally used symbols
                    $symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
                        ? $this->tokenToSymbol[$tokenId]
                        : $this->invalidSymbol;

                    if ($symbol === $this->invalidSymbol) {
                        throw new \RangeException(sprintf(
                            'The lexer returned an invalid token (id=%d, value=%s)',
                            $tokenId, $tokenValue
                        ));
                    }

                    // This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
                    // the attributes of the next token, even though they don't contain it themselves.
                    $this->startAttributeStack[$this->stackPos+1] = $startAttributes;
                    $this->lookaheadStartAttributes = $startAttributes;

                    //$this->traceRead($symbol);
                }

                $idx = $this->actionBase[$state] + $symbol;
                if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
                     || ($state < $this->YY2TBLSTATE
                         && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
                         && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
                    && ($action = $this->action[$idx]) != $this->defaultAction) {
                    /*
                     * >= YYNLSTATES: shift and reduce
                     * > 0: shift
                     * = 0: accept
                     * < 0: reduce
                     * = -YYUNEXPECTED: error
                     */
                    if ($action > 0) {
                        /* shift */
                        //$this->traceShift($symbol);

                        ++$this->stackPos;
                        $stateStack[$this->stackPos] = $state = $action;
                        $this->semStack[$this->stackPos] = $tokenValue;
                        $this->startAttributeStack[$this->stackPos] = $startAttributes;
                        $this->endAttributes = $endAttributes;
                        $symbol = self::SYMBOL_NONE;

                        if ($errorState) {
                            --$errorState;
                        }

                        if ($action < $this->YYNLSTATES) {
                            continue;
                        }

                        /* $yyn >= YYNLSTATES means shift-and-reduce */
                        $rule = $action - $this->YYNLSTATES;
                    } else {
                        $rule = -$action;
                    }
                } else {
                    $rule = $this->actionDefault[$state];
                }
            }

            for (;;) {
                if ($rule === 0) {
                    /* accept */
                    //$this->traceAccept();
                    return $this->semValue;
                } elseif ($rule !== $this->unexpectedTokenRule) {
                    /* reduce */
                    //$this->traceReduce($rule);

                    try {
                        $this->{'reduceRule' . $rule}();
                    } catch (Error $e) {
                        if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
                            $e->setStartLine($startAttributes['startLine']);
                        }

                        $this->errors[] = $e;
                        if ($this->throwOnError) {
                            throw $e;
                        } else {
                            // Currently can't recover from "special" errors
                            return null;
                        }
                    }

                    /* Goto - shift nonterminal */
                    $this->stackPos -= $this->ruleToLength[$rule];
                    $nonTerminal = $this->ruleToNonTerminal[$rule];
                    $idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
                    if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
                        $state = $this->goto[$idx];
                    } else {
                        $state = $this->gotoDefault[$nonTerminal];
                    }

                    ++$this->stackPos;
                    $stateStack[$this->stackPos]     = $state;
                    $this->semStack[$this->stackPos] = $this->semValue;
                } else {
                    /* error */
                    switch ($errorState) {
                        case 0:
                            $msg = $this->getErrorMessage($symbol, $state);
                            $error = new Error($msg, $startAttributes + $endAttributes);
                            $this->errors[] = $error;
                            if ($this->throwOnError) {
                                throw $error;
                            }
                            // Break missing intentionally
                        case 1:
                        case 2:
                            $errorState = 3;

                            // Pop until error-expecting state uncovered
                            while (!(
                                (($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
                                || ($state < $this->YY2TBLSTATE
                                    && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
                                    && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
                            ) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
                                if ($this->stackPos <= 0) {
                                    // Could not recover from error
                                    return null;
                                }
                                $state = $stateStack[--$this->stackPos];
                                //$this->tracePop($state);
                            }

                            //$this->traceShift($this->errorSymbol);
                            $stateStack[++$this->stackPos] = $state = $action;
                            break;

                        case 3:
                            if ($symbol === 0) {
                                // Reached EOF without recovering from error
                                return null;
                            }

                            //$this->traceDiscard($symbol);
                            $symbol = self::SYMBOL_NONE;
                            break 2;
                    }
                }

                if ($state < $this->YYNLSTATES) {
                    break;
                }

                /* >= YYNLSTATES means shift-and-reduce */
                $rule = $state - $this->YYNLSTATES;
            }
        }

        throw new \RuntimeException('Reached end of parser loop');
    }

    protected function getErrorMessage($symbol, $state) {
        $expectedString = '';
        if ($expected = $this->getExpectedTokens($state)) {
            $expectedString = ', expecting ' . implode(' or ', $expected);
        }

        return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
    }

    protected function getExpectedTokens($state) {
        $expected = array();

        $base = $this->actionBase[$state];
        foreach ($this->symbolToName as $symbol => $name) {
            $idx = $base + $symbol;
            if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
                || $state < $this->YY2TBLSTATE
                && ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
                && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
            ) {
                if ($this->action[$idx] != $this->unexpectedTokenRule
                    && $this->action[$idx] != $this->defaultAction
                ) {
                    if (count($expected) == 4) {
                        /* Too many expected tokens */
                        return array();
                    }

                    $expected[] = $name;
                }
            }
        }

        return $expected;
    }

    /*
     * Tracing functions used for debugging the parser.
     */

    /*
    protected function traceNewState($state, $symbol) {
        echo '% State ' . $state
            . ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
    }

    protected function traceRead($symbol) {
        echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
    }

    protected function traceShift($symbol) {
        echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
    }

    protected function traceAccept() {
        echo "% Accepted.\n";
    }

    protected function traceReduce($n) {
        echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
    }

    protected function tracePop($state) {
        echo '% Recovering, uncovered state ' . $state . "\n";
    }

    protected function traceDiscard($symbol) {
        echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
    }
    */

    /*
     * Helper functions invoked by semantic actions
     */

    /**
     * Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
     *
     * @param Node[] $stmts
     * @return Node[]
     */
    protected function handleNamespaces(array $stmts) {
        $style = $this->getNamespacingStyle($stmts);
        if (null === $style) {
            // not namespaced, nothing to do
            return $stmts;
        } elseif ('brace' === $style) {
            // For braced namespaces we only have to check that there are no invalid statements between the namespaces
            $afterFirstNamespace = false;
            foreach ($stmts as $stmt) {
                if ($stmt instanceof Node\Stmt\Namespace_) {
                    $afterFirstNamespace = true;
                } elseif (!$stmt instanceof Node\Stmt\HaltCompiler && $afterFirstNamespace) {
                    throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
                }
            }
            return $stmts;
        } else {
            // For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
            $resultStmts = array();
            $targetStmts =& $resultStmts;
            foreach ($stmts as $stmt) {
                if ($stmt instanceof Node\Stmt\Namespace_) {
                    $stmt->stmts = array();
                    $targetStmts =& $stmt->stmts;
                    $resultStmts[] = $stmt;
                } elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
                    // __halt_compiler() is not moved into the namespace
                    $resultStmts[] = $stmt;
                } else {
                    $targetStmts[] = $stmt;
                }
            }
            return $resultStmts;
        }
    }

    private function getNamespacingStyle(array $stmts) {
        $style = null;
        $hasNotAllowedStmts = false;
        foreach ($stmts as $i => $stmt) {
            if ($stmt instanceof Node\Stmt\Namespace_) {
                $currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
                if (null === $style) {
                    $style = $currentStyle;
                    if ($hasNotAllowedStmts) {
                        throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
                    }
                } elseif ($style !== $currentStyle) {
                    throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
                }
                continue;
            }

            /* declare(), __halt_compiler() and nops can be used before a namespace declaration */
            if ($stmt instanceof Node\Stmt\Declare_
                || $stmt instanceof Node\Stmt\HaltCompiler
                || $stmt instanceof Node\Stmt\Nop) {
                continue;
            }

            /* There may be a hashbang line at the very start of the file */
            if ($i == 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
                continue;
            }

            /* Everything else if forbidden before namespace declarations */
            $hasNotAllowedStmts = true;
        }
        return $style;
    }

    protected function handleScalarTypes(Name $name) {
        $scalarTypes = [
            'bool'   => true,
            'int'    => true,
            'float'  => true,
            'string' => true,
        ];

        if (!$name->isUnqualified()) {
            return $name;
        }

        $lowerName = strtolower($name->toString());
        return isset($scalarTypes[$lowerName]) ? $lowerName : $name;
    }
}
<?php

namespace PhpParser;

class ParserFactory {
    const PREFER_PHP7 = 1;
    const PREFER_PHP5 = 2;
    const ONLY_PHP7 = 3;
    const ONLY_PHP5 = 4;

    /**
     * Creates a Parser instance, according to the provided kind.
     *
     * @param int        $kind  One of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5
     * @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified
     * @param array      $parserOptions Parser options. See ParserAbstract::__construct() argument
     *
     * @return Parser The parser instance
     */
    public function create($kind, Lexer $lexer = null, array $parserOptions = array()) {
        if (null === $lexer) {
            $lexer = new Lexer\Emulative();
        }
        switch ($kind) {
            case self::PREFER_PHP7:
                return new Parser\Multiple([
                    new Parser\Php7($lexer, $parserOptions), new Parser\Php5($lexer, $parserOptions)
                ]);
            case self::PREFER_PHP5:
                return new Parser\Multiple([
                    new Parser\Php5($lexer, $parserOptions), new Parser\Php7($lexer, $parserOptions)
                ]);
            case self::ONLY_PHP7:
                return new Parser\Php7($lexer, $parserOptions);
            case self::ONLY_PHP5:
                return new Parser\Php5($lexer, $parserOptions);
            default:
                throw new \LogicException(
                    'Kind must be one of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5'
                );
        }
    }
}
<?php

namespace PhpParser\PrettyPrinter;

use PhpParser\PrettyPrinterAbstract;
use PhpParser\Node;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Stmt;
use PhpParser\Node\Name;

class Standard extends PrettyPrinterAbstract
{
    // Special nodes

    public function pParam(Node\Param $node) {
        return ($node->type ? $this->pType($node->type) . ' ' : '')
             . ($node->byRef ? '&' : '')
             . ($node->variadic ? '...' : '')
             . '$' . $node->name
             . ($node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pArg(Node\Arg $node) {
        return ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '') . $this->p($node->value);
    }

    public function pConst(Node\Const_ $node) {
        return $node->name . ' = ' . $this->p($node->value);
    }

    // Names

    public function pName(Name $node) {
        return implode('\\', $node->parts);
    }

    public function pName_FullyQualified(Name\FullyQualified $node) {
        return '\\' . implode('\\', $node->parts);
    }

    public function pName_Relative(Name\Relative $node) {
        return 'namespace\\' . implode('\\', $node->parts);
    }

    // Magic Constants

    public function pScalar_MagicConst_Class(MagicConst\Class_ $node) {
        return '__CLASS__';
    }

    public function pScalar_MagicConst_Dir(MagicConst\Dir $node) {
        return '__DIR__';
    }

    public function pScalar_MagicConst_File(MagicConst\File $node) {
        return '__FILE__';
    }

    public function pScalar_MagicConst_Function(MagicConst\Function_ $node) {
        return '__FUNCTION__';
    }

    public function pScalar_MagicConst_Line(MagicConst\Line $node) {
        return '__LINE__';
    }

    public function pScalar_MagicConst_Method(MagicConst\Method $node) {
        return '__METHOD__';
    }

    public function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) {
        return '__NAMESPACE__';
    }

    public function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) {
        return '__TRAIT__';
    }

    // Scalars

    public function pScalar_String(Scalar\String_ $node) {
        $kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED);
        switch ($kind) {
            case Scalar\String_::KIND_NOWDOC:
                $label = $node->getAttribute('docLabel');
                if ($label && !$this->containsEndLabel($node->value, $label)) {
                    if ($node->value === '') {
                        return $this->pNoIndent("<<<'$label'\n$label") . $this->docStringEndToken;
                    }

                    return $this->pNoIndent("<<<'$label'\n$node->value\n$label")
                         . $this->docStringEndToken;
                }
                /* break missing intentionally */
            case Scalar\String_::KIND_SINGLE_QUOTED:
                return '\'' . $this->pNoIndent(addcslashes($node->value, '\'\\')) . '\'';
            case Scalar\String_::KIND_HEREDOC:
                $label = $node->getAttribute('docLabel');
                if ($label && !$this->containsEndLabel($node->value, $label)) {
                    if ($node->value === '') {
                        return $this->pNoIndent("<<<$label\n$label") . $this->docStringEndToken;
                    }

                    $escaped = $this->escapeString($node->value, null);
                    return $this->pNoIndent("<<<$label\n" . $escaped ."\n$label")
                         . $this->docStringEndToken;
                }
            /* break missing intentionally */
            case Scalar\String_::KIND_DOUBLE_QUOTED:
                return '"' . $this->escapeString($node->value, '"') . '"';
        }
        throw new \Exception('Invalid string kind');
    }

    public function pScalar_Encapsed(Scalar\Encapsed $node) {
        if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) {
            $label = $node->getAttribute('docLabel');
            if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) {
                if (count($node->parts) === 1
                    && $node->parts[0] instanceof Scalar\EncapsedStringPart
                    && $node->parts[0]->value === ''
                ) {
                    return $this->pNoIndent("<<<$label\n$label") . $this->docStringEndToken;
                }

                return $this->pNoIndent(
                    "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label"
                ) . $this->docStringEndToken;
            }
        }
        return '"' . $this->pEncapsList($node->parts, '"') . '"';
    }

    public function pScalar_LNumber(Scalar\LNumber $node) {
        $str = (string) $node->value;
        switch ($node->getAttribute('kind', Scalar\LNumber::KIND_DEC)) {
            case Scalar\LNumber::KIND_BIN:
                return '0b' . base_convert($str, 10, 2);
            case Scalar\LNumber::KIND_OCT:
                return '0' . base_convert($str, 10, 8);
            case Scalar\LNumber::KIND_DEC:
                return $str;
            case Scalar\LNumber::KIND_HEX:
                return '0x' . base_convert($str, 10, 16);
        }
        throw new \Exception('Invalid number kind');
    }

    public function pScalar_DNumber(Scalar\DNumber $node) {
        $stringValue = sprintf('%.16G', $node->value);
        if ($node->value !== (double) $stringValue) {
            $stringValue = sprintf('%.17G', $node->value);
        }

        // ensure that number is really printed as float
        return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
    }

    // Assignments

    public function pExpr_Assign(Expr\Assign $node) {
        return $this->pInfixOp('Expr_Assign', $node->var, ' = ', $node->expr);
    }

    public function pExpr_AssignRef(Expr\AssignRef $node) {
        return $this->pInfixOp('Expr_AssignRef', $node->var, ' =& ', $node->expr);
    }

    public function pExpr_AssignOp_Plus(AssignOp\Plus $node) {
        return $this->pInfixOp('Expr_AssignOp_Plus', $node->var, ' += ', $node->expr);
    }

    public function pExpr_AssignOp_Minus(AssignOp\Minus $node) {
        return $this->pInfixOp('Expr_AssignOp_Minus', $node->var, ' -= ', $node->expr);
    }

    public function pExpr_AssignOp_Mul(AssignOp\Mul $node) {
        return $this->pInfixOp('Expr_AssignOp_Mul', $node->var, ' *= ', $node->expr);
    }

    public function pExpr_AssignOp_Div(AssignOp\Div $node) {
        return $this->pInfixOp('Expr_AssignOp_Div', $node->var, ' /= ', $node->expr);
    }

    public function pExpr_AssignOp_Concat(AssignOp\Concat $node) {
        return $this->pInfixOp('Expr_AssignOp_Concat', $node->var, ' .= ', $node->expr);
    }

    public function pExpr_AssignOp_Mod(AssignOp\Mod $node) {
        return $this->pInfixOp('Expr_AssignOp_Mod', $node->var, ' %= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseAnd', $node->var, ' &= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseOr', $node->var, ' |= ', $node->expr);
    }

    public function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) {
        return $this->pInfixOp('Expr_AssignOp_BitwiseXor', $node->var, ' ^= ', $node->expr);
    }

    public function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) {
        return $this->pInfixOp('Expr_AssignOp_ShiftLeft', $node->var, ' <<= ', $node->expr);
    }

    public function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) {
        return $this->pInfixOp('Expr_AssignOp_ShiftRight', $node->var, ' >>= ', $node->expr);
    }

    public function pExpr_AssignOp_Pow(AssignOp\Pow $node) {
        return $this->pInfixOp('Expr_AssignOp_Pow', $node->var, ' **= ', $node->expr);
    }

    // Binary expressions

    public function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) {
        return $this->pInfixOp('Expr_BinaryOp_Plus', $node->left, ' + ', $node->right);
    }

    public function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) {
        return $this->pInfixOp('Expr_BinaryOp_Minus', $node->left, ' - ', $node->right);
    }

    public function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) {
        return $this->pInfixOp('Expr_BinaryOp_Mul', $node->left, ' * ', $node->right);
    }

    public function pExpr_BinaryOp_Div(BinaryOp\Div $node) {
        return $this->pInfixOp('Expr_BinaryOp_Div', $node->left, ' / ', $node->right);
    }

    public function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) {
        return $this->pInfixOp('Expr_BinaryOp_Concat', $node->left, ' . ', $node->right);
    }

    public function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) {
        return $this->pInfixOp('Expr_BinaryOp_Mod', $node->left, ' % ', $node->right);
    }

    public function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_BooleanAnd', $node->left, ' && ', $node->right);
    }

    public function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_BooleanOr', $node->left, ' || ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseAnd', $node->left, ' & ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseOr', $node->left, ' | ', $node->right);
    }

    public function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) {
        return $this->pInfixOp('Expr_BinaryOp_BitwiseXor', $node->left, ' ^ ', $node->right);
    }

    public function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) {
        return $this->pInfixOp('Expr_BinaryOp_ShiftLeft', $node->left, ' << ', $node->right);
    }

    public function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) {
        return $this->pInfixOp('Expr_BinaryOp_ShiftRight', $node->left, ' >> ', $node->right);
    }

    public function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) {
        return $this->pInfixOp('Expr_BinaryOp_Pow', $node->left, ' ** ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalAnd', $node->left, ' and ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalOr', $node->left, ' or ', $node->right);
    }

    public function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) {
        return $this->pInfixOp('Expr_BinaryOp_LogicalXor', $node->left, ' xor ', $node->right);
    }

    public function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) {
        return $this->pInfixOp('Expr_BinaryOp_Equal', $node->left, ' == ', $node->right);
    }

    public function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_NotEqual', $node->left, ' != ', $node->right);
    }

    public function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) {
        return $this->pInfixOp('Expr_BinaryOp_Identical', $node->left, ' === ', $node->right);
    }

    public function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) {
        return $this->pInfixOp('Expr_BinaryOp_NotIdentical', $node->left, ' !== ', $node->right);
    }

    public function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) {
        return $this->pInfixOp('Expr_BinaryOp_Spaceship', $node->left, ' <=> ', $node->right);
    }

    public function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) {
        return $this->pInfixOp('Expr_BinaryOp_Greater', $node->left, ' > ', $node->right);
    }

    public function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_GreaterOrEqual', $node->left, ' >= ', $node->right);
    }

    public function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) {
        return $this->pInfixOp('Expr_BinaryOp_Smaller', $node->left, ' < ', $node->right);
    }

    public function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) {
        return $this->pInfixOp('Expr_BinaryOp_SmallerOrEqual', $node->left, ' <= ', $node->right);
    }

    public function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) {
        return $this->pInfixOp('Expr_BinaryOp_Coalesce', $node->left, ' ?? ', $node->right);
    }

    public function pExpr_Instanceof(Expr\Instanceof_ $node) {
        return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
    }

    // Unary expressions

    public function pExpr_BooleanNot(Expr\BooleanNot $node) {
        return $this->pPrefixOp('Expr_BooleanNot', '!', $node->expr);
    }

    public function pExpr_BitwiseNot(Expr\BitwiseNot $node) {
        return $this->pPrefixOp('Expr_BitwiseNot', '~', $node->expr);
    }

    public function pExpr_UnaryMinus(Expr\UnaryMinus $node) {
        return $this->pPrefixOp('Expr_UnaryMinus', '-', $node->expr);
    }

    public function pExpr_UnaryPlus(Expr\UnaryPlus $node) {
        return $this->pPrefixOp('Expr_UnaryPlus', '+', $node->expr);
    }

    public function pExpr_PreInc(Expr\PreInc $node) {
        return $this->pPrefixOp('Expr_PreInc', '++', $node->var);
    }

    public function pExpr_PreDec(Expr\PreDec $node) {
        return $this->pPrefixOp('Expr_PreDec', '--', $node->var);
    }

    public function pExpr_PostInc(Expr\PostInc $node) {
        return $this->pPostfixOp('Expr_PostInc', $node->var, '++');
    }

    public function pExpr_PostDec(Expr\PostDec $node) {
        return $this->pPostfixOp('Expr_PostDec', $node->var, '--');
    }

    public function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) {
        return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
    }

    public function pExpr_YieldFrom(Expr\YieldFrom $node) {
        return $this->pPrefixOp('Expr_YieldFrom', 'yield from ', $node->expr);
    }

    public function pExpr_Print(Expr\Print_ $node) {
        return $this->pPrefixOp('Expr_Print', 'print ', $node->expr);
    }

    // Casts

    public function pExpr_Cast_Int(Cast\Int_ $node) {
        return $this->pPrefixOp('Expr_Cast_Int', '(int) ', $node->expr);
    }

    public function pExpr_Cast_Double(Cast\Double $node) {
        return $this->pPrefixOp('Expr_Cast_Double', '(double) ', $node->expr);
    }

    public function pExpr_Cast_String(Cast\String_ $node) {
        return $this->pPrefixOp('Expr_Cast_String', '(string) ', $node->expr);
    }

    public function pExpr_Cast_Array(Cast\Array_ $node) {
        return $this->pPrefixOp('Expr_Cast_Array', '(array) ', $node->expr);
    }

    public function pExpr_Cast_Object(Cast\Object_ $node) {
        return $this->pPrefixOp('Expr_Cast_Object', '(object) ', $node->expr);
    }

    public function pExpr_Cast_Bool(Cast\Bool_ $node) {
        return $this->pPrefixOp('Expr_Cast_Bool', '(bool) ', $node->expr);
    }

    public function pExpr_Cast_Unset(Cast\Unset_ $node) {
        return $this->pPrefixOp('Expr_Cast_Unset', '(unset) ', $node->expr);
    }

    // Function calls and similar constructs

    public function pExpr_FuncCall(Expr\FuncCall $node) {
        return $this->pCallLhs($node->name)
             . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_MethodCall(Expr\MethodCall $node) {
        return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name)
             . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_StaticCall(Expr\StaticCall $node) {
        return $this->pDereferenceLhs($node->class) . '::'
             . ($node->name instanceof Expr
                ? ($node->name instanceof Expr\Variable
                   ? $this->p($node->name)
                   : '{' . $this->p($node->name) . '}')
                : $node->name)
             . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_Empty(Expr\Empty_ $node) {
        return 'empty(' . $this->p($node->expr) . ')';
    }

    public function pExpr_Isset(Expr\Isset_ $node) {
        return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
    }

    public function pExpr_Eval(Expr\Eval_ $node) {
        return 'eval(' . $this->p($node->expr) . ')';
    }

    public function pExpr_Include(Expr\Include_ $node) {
        static $map = array(
            Expr\Include_::TYPE_INCLUDE      => 'include',
            Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once',
            Expr\Include_::TYPE_REQUIRE      => 'require',
            Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once',
        );

        return $map[$node->type] . ' ' . $this->p($node->expr);
    }

    public function pExpr_List(Expr\List_ $node) {
        $pList = array();
        foreach ($node->vars as $var) {
            if (null === $var) {
                $pList[] = '';
            } else {
                $pList[] = $this->p($var);
            }
        }

        return 'list(' . implode(', ', $pList) . ')';
    }

    // Other

    public function pExpr_Variable(Expr\Variable $node) {
        if ($node->name instanceof Expr) {
            return '${' . $this->p($node->name) . '}';
        } else {
            return '$' . $node->name;
        }
    }

    public function pExpr_Array(Expr\Array_ $node) {
        $syntax = $node->getAttribute('kind',
            $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG);
        if ($syntax === Expr\Array_::KIND_SHORT) {
            return '[' . $this->pCommaSeparated($node->items) . ']';
        } else {
            return 'array(' . $this->pCommaSeparated($node->items) . ')';
        }
    }

    public function pExpr_ArrayItem(Expr\ArrayItem $node) {
        return (null !== $node->key ? $this->p($node->key) . ' => ' : '')
             . ($node->byRef ? '&' : '') . $this->p($node->value);
    }

    public function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) {
        return $this->pDereferenceLhs($node->var)
             . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']';
    }

    public function pExpr_ConstFetch(Expr\ConstFetch $node) {
        return $this->p($node->name);
    }

    public function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
        return $this->p($node->class) . '::' . $node->name;
    }

    public function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
        return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name);
    }

    public function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
        return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
    }

    public function pExpr_ShellExec(Expr\ShellExec $node) {
        return '`' . $this->pEncapsList($node->parts, '`') . '`';
    }

    public function pExpr_Closure(Expr\Closure $node) {
        return ($node->static ? 'static ' : '')
             . 'function ' . ($node->byRef ? '&' : '')
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')': '')
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pExpr_ClosureUse(Expr\ClosureUse $node) {
        return ($node->byRef ? '&' : '') . '$' . $node->var;
    }

    public function pExpr_New(Expr\New_ $node) {
        if ($node->class instanceof Stmt\Class_) {
            $args = $node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '';
            return 'new ' . $this->pClassCommon($node->class, $args);
        }
        return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')';
    }

    public function pExpr_Clone(Expr\Clone_ $node) {
        return 'clone ' . $this->p($node->expr);
    }

    public function pExpr_Ternary(Expr\Ternary $node) {
        // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
        // this is okay because the part between ? and : never needs parentheses.
        return $this->pInfixOp('Expr_Ternary',
            $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
        );
    }

    public function pExpr_Exit(Expr\Exit_ $node) {
        $kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE);
        return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die')
             . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : '');
    }

    public function pExpr_Yield(Expr\Yield_ $node) {
        if ($node->value === null) {
            return 'yield';
        } else {
            // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary
            return '(yield '
                 . ($node->key !== null ? $this->p($node->key) . ' => ' : '')
                 . $this->p($node->value)
                 . ')';
        }
    }

    // Declarations

    public function pStmt_Namespace(Stmt\Namespace_ $node) {
        if ($this->canUseSemicolonNamespaces) {
            return 'namespace ' . $this->p($node->name) . ';' . "\n" . $this->pStmts($node->stmts, false);
        } else {
            return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '')
                 . ' {' . $this->pStmts($node->stmts) . "\n" . '}';
        }
    }

    public function pStmt_Use(Stmt\Use_ $node) {
        return 'use ' . $this->pUseType($node->type)
             . $this->pCommaSeparated($node->uses) . ';';
    }

    public function pStmt_GroupUse(Stmt\GroupUse $node) {
        return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix)
             . '\{' . $this->pCommaSeparated($node->uses) . '};';
    }

    public function pStmt_UseUse(Stmt\UseUse $node) {
        return $this->pUseType($node->type) . $this->p($node->name)
             . ($node->name->getLast() !== $node->alias ? ' as ' . $node->alias : '');
    }

    private function pUseType($type) {
        return $type === Stmt\Use_::TYPE_FUNCTION ? 'function '
            : ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : '');
    }

    public function pStmt_Interface(Stmt\Interface_ $node) {
        return 'interface ' . $node->name
             . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Class(Stmt\Class_ $node) {
        return $this->pClassCommon($node, ' ' . $node->name);
    }

    public function pStmt_Trait(Stmt\Trait_ $node) {
        return 'trait ' . $node->name
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_TraitUse(Stmt\TraitUse $node) {
        return 'use ' . $this->pCommaSeparated($node->traits)
             . (empty($node->adaptations)
                ? ';'
                : ' {' . $this->pStmts($node->adaptations) . "\n" . '}');
    }

    public function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) {
        return $this->p($node->trait) . '::' . $node->method
             . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';';
    }

    public function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) {
        return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
             . $node->method . ' as'
             . (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '')
             . (null !== $node->newName     ? ' ' . $node->newName                        : '')
             . ';';
    }

    public function pStmt_Property(Stmt\Property $node) {
        return (0 === $node->type ? 'var ' : $this->pModifiers($node->type)) . $this->pCommaSeparated($node->props) . ';';
    }

    public function pStmt_PropertyProperty(Stmt\PropertyProperty $node) {
        return '$' . $node->name
             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pStmt_ClassMethod(Stmt\ClassMethod $node) {
        return $this->pModifiers($node->type)
             . 'function ' . ($node->byRef ? '&' : '') . $node->name
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . (null !== $node->stmts
                ? "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}'
                : ';');
    }

    public function pStmt_ClassConst(Stmt\ClassConst $node) {
        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    }

    public function pStmt_Function(Stmt\Function_ $node) {
        return 'function ' . ($node->byRef ? '&' : '') . $node->name
             . '(' . $this->pCommaSeparated($node->params) . ')'
             . (null !== $node->returnType ? ' : ' . $this->pType($node->returnType) : '')
             . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Const(Stmt\Const_ $node) {
        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    }

    public function pStmt_Declare(Stmt\Declare_ $node) {
        return 'declare (' . $this->pCommaSeparated($node->declares) . ')'
             . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . "\n" . '}' : ';');
    }

    public function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) {
        return $node->key . '=' . $this->p($node->value);
    }

    // Control flow

    public function pStmt_If(Stmt\If_ $node) {
        return 'if (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}'
             . $this->pImplode($node->elseifs)
             . (null !== $node->else ? $this->p($node->else) : '');
    }

    public function pStmt_ElseIf(Stmt\ElseIf_ $node) {
        return ' elseif (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Else(Stmt\Else_ $node) {
        return ' else {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_For(Stmt\For_ $node) {
        return 'for ('
             . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '')
             . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '')
             . $this->pCommaSeparated($node->loop)
             . ') {' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Foreach(Stmt\Foreach_ $node) {
        return 'foreach (' . $this->p($node->expr) . ' as '
             . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '')
             . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_While(Stmt\While_ $node) {
        return 'while (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Do(Stmt\Do_ $node) {
        return 'do {' . $this->pStmts($node->stmts) . "\n"
             . '} while (' . $this->p($node->cond) . ');';
    }

    public function pStmt_Switch(Stmt\Switch_ $node) {
        return 'switch (' . $this->p($node->cond) . ') {'
             . $this->pStmts($node->cases) . "\n" . '}';
    }

    public function pStmt_Case(Stmt\Case_ $node) {
        return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':'
             . $this->pStmts($node->stmts);
    }

    public function pStmt_TryCatch(Stmt\TryCatch $node) {
        return 'try {' . $this->pStmts($node->stmts) . "\n" . '}'
             . $this->pImplode($node->catches)
             . ($node->finallyStmts !== null
                ? ' finally {' . $this->pStmts($node->finallyStmts) . "\n" . '}'
                : '');
    }

    public function pStmt_Catch(Stmt\Catch_ $node) {
        return ' catch (' . $this->p($node->type) . ' $' . $node->var . ') {'
             . $this->pStmts($node->stmts) . "\n" . '}';
    }

    public function pStmt_Break(Stmt\Break_ $node) {
        return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    }

    public function pStmt_Continue(Stmt\Continue_ $node) {
        return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    }

    public function pStmt_Return(Stmt\Return_ $node) {
        return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
    }

    public function pStmt_Throw(Stmt\Throw_ $node) {
        return 'throw ' . $this->p($node->expr) . ';';
    }

    public function pStmt_Label(Stmt\Label $node) {
        return $node->name . ':';
    }

    public function pStmt_Goto(Stmt\Goto_ $node) {
        return 'goto ' . $node->name . ';';
    }

    // Other

    public function pStmt_Echo(Stmt\Echo_ $node) {
        return 'echo ' . $this->pCommaSeparated($node->exprs) . ';';
    }

    public function pStmt_Static(Stmt\Static_ $node) {
        return 'static ' . $this->pCommaSeparated($node->vars) . ';';
    }

    public function pStmt_Global(Stmt\Global_ $node) {
        return 'global ' . $this->pCommaSeparated($node->vars) . ';';
    }

    public function pStmt_StaticVar(Stmt\StaticVar $node) {
        return '$' . $node->name
             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    }

    public function pStmt_Unset(Stmt\Unset_ $node) {
        return 'unset(' . $this->pCommaSeparated($node->vars) . ');';
    }

    public function pStmt_InlineHTML(Stmt\InlineHTML $node) {
        return '?>' . $this->pNoIndent("\n" . $node->value) . '<?php ';
    }

    public function pStmt_HaltCompiler(Stmt\HaltCompiler $node) {
        return '__halt_compiler();' . $node->remaining;
    }

    public function pStmt_Nop(Stmt\Nop $node) {
        return '';
    }

    // Helpers

    protected function pType($node) {
        return is_string($node) ? $node : $this->p($node);
    }

    protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
        return $this->pModifiers($node->type)
        . 'class' . $afterClassToken
        . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
        . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
        . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
    }

    protected function pObjectProperty($node) {
        if ($node instanceof Expr) {
            return '{' . $this->p($node) . '}';
        } else {
            return $node;
        }
    }

    protected function pModifiers($modifiers) {
        return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC    ? 'public '    : '')
             . ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '')
             . ($modifiers & Stmt\Class_::MODIFIER_PRIVATE   ? 'private '   : '')
             . ($modifiers & Stmt\Class_::MODIFIER_STATIC    ? 'static '    : '')
             . ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT  ? 'abstract '  : '')
             . ($modifiers & Stmt\Class_::MODIFIER_FINAL     ? 'final '     : '');
    }

    protected function pEncapsList(array $encapsList, $quote) {
        $return = '';
        foreach ($encapsList as $element) {
            if ($element instanceof Scalar\EncapsedStringPart) {
                $return .= $this->escapeString($element->value, $quote);
            } else {
                $return .= '{' . $this->p($element) . '}';
            }
        }

        return $return;
    }

    protected function escapeString($string, $quote) {
        if (null === $quote) {
            // For doc strings, don't escape newlines
            $escaped = addcslashes($string, "\t\f\v$\\");
        } else {
            $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
        }

        // Escape other control characters
        return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) {
            $oct = decoct(ord($matches[1]));
            if ($matches[2] !== '') {
                // If there is a trailing digit, use the full three character form
                return '\\' . str_pad($oct, 3, '0', STR_PAD_LEFT);
            }
            return '\\' . $oct;
        }, $escaped);
    }

    protected function containsEndLabel($string, $label, $atStart = true, $atEnd = true) {
        $start = $atStart ? '(?:^|[\r\n])' : '[\r\n]';
        $end = $atEnd ? '(?:$|[;\r\n])' : '[;\r\n]';
        return false !== strpos($string, $label)
            && preg_match('/' . $start . $label . $end . '/', $string);
    }

    protected function encapsedContainsEndLabel(array $parts, $label) {
        foreach ($parts as $i => $part) {
            $atStart = $i === 0;
            $atEnd = $i === count($parts) - 1;
            if ($part instanceof Scalar\EncapsedStringPart
                && $this->containsEndLabel($part->value, $label, $atStart, $atEnd)
            ) {
                return true;
            }
        }
        return false;
    }

    protected function pDereferenceLhs(Node $node) {
        if ($node instanceof Expr\Variable
            || $node instanceof Name
            || $node instanceof Expr\ArrayDimFetch
            || $node instanceof Expr\PropertyFetch
            || $node instanceof Expr\StaticPropertyFetch
            || $node instanceof Expr\FuncCall
            || $node instanceof Expr\MethodCall
            || $node instanceof Expr\StaticCall
            || $node instanceof Expr\Array_
            || $node instanceof Scalar\String_
            || $node instanceof Expr\ConstFetch
            || $node instanceof Expr\ClassConstFetch
        ) {
            return $this->p($node);
        } else  {
            return '(' . $this->p($node) . ')';
        }
    }

    protected function pCallLhs(Node $node) {
        if ($node instanceof Name
            || $node instanceof Expr\Variable
            || $node instanceof Expr\ArrayDimFetch
            || $node instanceof Expr\FuncCall
            || $node instanceof Expr\MethodCall
            || $node instanceof Expr\StaticCall
            || $node instanceof Expr\Array_
        ) {
            return $this->p($node);
        } else  {
            return '(' . $this->p($node) . ')';
        }
    }
}
<?php

namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;

abstract class PrettyPrinterAbstract
{
    protected $precedenceMap = array(
        // [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right
        'Expr_BinaryOp_Pow'            => array(  0,  1),
        'Expr_BitwiseNot'              => array( 10,  1),
        'Expr_PreInc'                  => array( 10,  1),
        'Expr_PreDec'                  => array( 10,  1),
        'Expr_PostInc'                 => array( 10, -1),
        'Expr_PostDec'                 => array( 10, -1),
        'Expr_UnaryPlus'               => array( 10,  1),
        'Expr_UnaryMinus'              => array( 10,  1),
        'Expr_Cast_Int'                => array( 10,  1),
        'Expr_Cast_Double'             => array( 10,  1),
        'Expr_Cast_String'             => array( 10,  1),
        'Expr_Cast_Array'              => array( 10,  1),
        'Expr_Cast_Object'             => array( 10,  1),
        'Expr_Cast_Bool'               => array( 10,  1),
        'Expr_Cast_Unset'              => array( 10,  1),
        'Expr_ErrorSuppress'           => array( 10,  1),
        'Expr_Instanceof'              => array( 20,  0),
        'Expr_BooleanNot'              => array( 30,  1),
        'Expr_BinaryOp_Mul'            => array( 40, -1),
        'Expr_BinaryOp_Div'            => array( 40, -1),
        'Expr_BinaryOp_Mod'            => array( 40, -1),
        'Expr_BinaryOp_Plus'           => array( 50, -1),
        'Expr_BinaryOp_Minus'          => array( 50, -1),
        'Expr_BinaryOp_Concat'         => array( 50, -1),
        'Expr_BinaryOp_ShiftLeft'      => array( 60, -1),
        'Expr_BinaryOp_ShiftRight'     => array( 60, -1),
        'Expr_BinaryOp_Smaller'        => array( 70,  0),
        'Expr_BinaryOp_SmallerOrEqual' => array( 70,  0),
        'Expr_BinaryOp_Greater'        => array( 70,  0),
        'Expr_BinaryOp_GreaterOrEqual' => array( 70,  0),
        'Expr_BinaryOp_Equal'          => array( 80,  0),
        'Expr_BinaryOp_NotEqual'       => array( 80,  0),
        'Expr_BinaryOp_Identical'      => array( 80,  0),
        'Expr_BinaryOp_NotIdentical'   => array( 80,  0),
        'Expr_BinaryOp_Spaceship'      => array( 80,  0),
        'Expr_BinaryOp_BitwiseAnd'     => array( 90, -1),
        'Expr_BinaryOp_BitwiseXor'     => array(100, -1),
        'Expr_BinaryOp_BitwiseOr'      => array(110, -1),
        'Expr_BinaryOp_BooleanAnd'     => array(120, -1),
        'Expr_BinaryOp_BooleanOr'      => array(130, -1),
        'Expr_BinaryOp_Coalesce'       => array(140,  1),
        'Expr_Ternary'                 => array(150, -1),
        // parser uses %left for assignments, but they really behave as %right
        'Expr_Assign'                  => array(160,  1),
        'Expr_AssignRef'               => array(160,  1),
        'Expr_AssignOp_Plus'           => array(160,  1),
        'Expr_AssignOp_Minus'          => array(160,  1),
        'Expr_AssignOp_Mul'            => array(160,  1),
        'Expr_AssignOp_Div'            => array(160,  1),
        'Expr_AssignOp_Concat'         => array(160,  1),
        'Expr_AssignOp_Mod'            => array(160,  1),
        'Expr_AssignOp_BitwiseAnd'     => array(160,  1),
        'Expr_AssignOp_BitwiseOr'      => array(160,  1),
        'Expr_AssignOp_BitwiseXor'     => array(160,  1),
        'Expr_AssignOp_ShiftLeft'      => array(160,  1),
        'Expr_AssignOp_ShiftRight'     => array(160,  1),
        'Expr_AssignOp_Pow'            => array(160,  1),
        'Expr_YieldFrom'               => array(165,  1),
        'Expr_Print'                   => array(168,  1),
        'Expr_BinaryOp_LogicalAnd'     => array(170, -1),
        'Expr_BinaryOp_LogicalXor'     => array(180, -1),
        'Expr_BinaryOp_LogicalOr'      => array(190, -1),
        'Expr_Include'                 => array(200, -1),
    );

    protected $noIndentToken;
    protected $docStringEndToken;
    protected $canUseSemicolonNamespaces;
    protected $options;

    /**
     * Creates a pretty printer instance using the given options.
     *
     * Supported options:
     *  * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array
     *                                    syntax, if the node does not specify a format.
     *
     * @param array $options Dictionary of formatting options
     */
    public function __construct(array $options = []) {
        $this->noIndentToken = '_NO_INDENT_' . mt_rand();
        $this->docStringEndToken = '_DOC_STRING_END_' . mt_rand();

        $defaultOptions = ['shortArraySyntax' => false];
        $this->options = $options + $defaultOptions;
    }

    /**
     * Pretty prints an array of statements.
     *
     * @param Node[] $stmts Array of statements
     *
     * @return string Pretty printed statements
     */
    public function prettyPrint(array $stmts) {
        $this->preprocessNodes($stmts);

        return ltrim($this->handleMagicTokens($this->pStmts($stmts, false)));
    }

    /**
     * Pretty prints an expression.
     *
     * @param Expr $node Expression node
     *
     * @return string Pretty printed node
     */
    public function prettyPrintExpr(Expr $node) {
        return $this->handleMagicTokens($this->p($node));
    }

    /**
     * Pretty prints a file of statements (includes the opening <?php tag if it is required).
     *
     * @param Node[] $stmts Array of statements
     *
     * @return string Pretty printed statements
     */
    public function prettyPrintFile(array $stmts) {
        if (!$stmts) {
            return "<?php\n\n";
        }

        $p = "<?php\n\n" . $this->prettyPrint($stmts);

        if ($stmts[0] instanceof Stmt\InlineHTML) {
            $p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
        }
        if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
            $p = preg_replace('/<\?php$/', '', rtrim($p));
        }

        return $p;
    }

    /**
     * Preprocesses the top-level nodes to initialize pretty printer state.
     *
     * @param Node[] $nodes Array of nodes
     */
    protected function preprocessNodes(array $nodes) {
        /* We can use semicolon-namespaces unless there is a global namespace declaration */
        $this->canUseSemicolonNamespaces = true;
        foreach ($nodes as $node) {
            if ($node instanceof Stmt\Namespace_ && null === $node->name) {
                $this->canUseSemicolonNamespaces = false;
            }
        }
    }

    protected function handleMagicTokens($str) {
        // Drop no-indent tokens
        $str = str_replace($this->noIndentToken, '', $str);

        // Replace doc-string-end tokens with nothing or a newline
        $str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
        $str = str_replace($this->docStringEndToken, "\n", $str);

        return $str;
    }

    /**
     * Pretty prints an array of nodes (statements) and indents them optionally.
     *
     * @param Node[] $nodes  Array of nodes
     * @param bool   $indent Whether to indent the printed nodes
     *
     * @return string Pretty printed statements
     */
    protected function pStmts(array $nodes, $indent = true) {
        $result = '';
        foreach ($nodes as $node) {
            $comments = $node->getAttribute('comments', array());
            if ($comments) {
                $result .= "\n" . $this->pComments($comments);
                if ($node instanceof Stmt\Nop) {
                    continue;
                }
            }

            $result .= "\n" . $this->p($node) . ($node instanceof Expr ? ';' : '');
        }

        if ($indent) {
            return preg_replace('~\n(?!$|' . $this->noIndentToken . ')~', "\n    ", $result);
        } else {
            return $result;
        }
    }

    /**
     * Pretty prints a node.
     *
     * @param Node $node Node to be pretty printed
     *
     * @return string Pretty printed node
     */
    protected function p(Node $node) {
        return $this->{'p' . $node->getType()}($node);
    }

    protected function pInfixOp($type, Node $leftNode, $operatorString, Node $rightNode) {
        list($precedence, $associativity) = $this->precedenceMap[$type];

        return $this->pPrec($leftNode, $precedence, $associativity, -1)
             . $operatorString
             . $this->pPrec($rightNode, $precedence, $associativity, 1);
    }

    protected function pPrefixOp($type, $operatorString, Node $node) {
        list($precedence, $associativity) = $this->precedenceMap[$type];
        return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
    }

    protected function pPostfixOp($type, Node $node, $operatorString) {
        list($precedence, $associativity) = $this->precedenceMap[$type];
        return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
    }

    /**
     * Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
     *
     * @param Node $node                Node to pretty print
     * @param int  $parentPrecedence    Precedence of the parent operator
     * @param int  $parentAssociativity Associativity of parent operator
     *                                  (-1 is left, 0 is nonassoc, 1 is right)
     * @param int  $childPosition       Position of the node relative to the operator
     *                                  (-1 is left, 1 is right)
     *
     * @return string The pretty printed node
     */
    protected function pPrec(Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
        $type = $node->getType();
        if (isset($this->precedenceMap[$type])) {
            $childPrecedence = $this->precedenceMap[$type][0];
            if ($childPrecedence > $parentPrecedence
                || ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
            ) {
                return '(' . $this->{'p' . $type}($node) . ')';
            }
        }

        return $this->{'p' . $type}($node);
    }

    /**
     * Pretty prints an array of nodes and implodes the printed values.
     *
     * @param Node[] $nodes Array of Nodes to be printed
     * @param string $glue  Character to implode with
     *
     * @return string Imploded pretty printed nodes
     */
    protected function pImplode(array $nodes, $glue = '') {
        $pNodes = array();
        foreach ($nodes as $node) {
            $pNodes[] = $this->p($node);
        }

        return implode($glue, $pNodes);
    }

    /**
     * Pretty prints an array of nodes and implodes the printed values with commas.
     *
     * @param Node[] $nodes Array of Nodes to be printed
     *
     * @return string Comma separated pretty printed nodes
     */
    protected function pCommaSeparated(array $nodes) {
        return $this->pImplode($nodes, ', ');
    }

    /**
     * Signals the pretty printer that a string shall not be indented.
     *
     * @param string $string Not to be indented string
     *
     * @return string String marked with $this->noIndentToken's.
     */
    protected function pNoIndent($string) {
        return str_replace("\n", "\n" . $this->noIndentToken, $string);
    }

    /**
     * Prints reformatted text of the passed comments.
     *
     * @param Comment[] $comments List of comments
     *
     * @return string Reformatted text of comments
     */
    protected function pComments(array $comments) {
        $formattedComments = [];

        foreach ($comments as $comment) {
            $formattedComments[] = $comment->getReformattedText();
        }

        return implode("\n", $formattedComments);
    }
}
<?php

namespace PhpParser;

interface Serializer
{
    /**
     * Serializes statements into some string format.
     *
     * @param array $nodes Statements
     *
     * @return string Serialized string
     */
    public function serialize(array $nodes);
}<?php

namespace PhpParser\Serializer;

use XMLWriter;
use PhpParser\Node;
use PhpParser\Comment;
use PhpParser\Serializer;

class XML implements Serializer
{
    protected $writer;

    /**
     * Constructs a XML serializer.
     */
    public function __construct() {
        $this->writer = new XMLWriter;
        $this->writer->openMemory();
        $this->writer->setIndent(true);
    }

    public function serialize(array $nodes) {
        $this->writer->flush();
        $this->writer->startDocument('1.0', 'UTF-8');

        $this->writer->startElement('AST');
        $this->writer->writeAttribute('xmlns:node',      'http://nikic.github.com/PHPParser/XML/node');
        $this->writer->writeAttribute('xmlns:subNode',   'http://nikic.github.com/PHPParser/XML/subNode');
        $this->writer->writeAttribute('xmlns:attribute', 'http://nikic.github.com/PHPParser/XML/attribute');
        $this->writer->writeAttribute('xmlns:scalar',    'http://nikic.github.com/PHPParser/XML/scalar');

        $this->_serialize($nodes);

        $this->writer->endElement();

        return $this->writer->outputMemory();
    }

    protected function _serialize($node) {
        if ($node instanceof Node) {
            $this->writer->startElement('node:' . $node->getType());

            foreach ($node->getAttributes() as $name => $value) {
                $this->writer->startElement('attribute:' . $name);
                $this->_serialize($value);
                $this->writer->endElement();
            }

            foreach ($node as $name => $subNode) {
                $this->writer->startElement('subNode:' . $name);
                $this->_serialize($subNode);
                $this->writer->endElement();
            }

            $this->writer->endElement();
        } elseif ($node instanceof Comment) {
            $this->writer->startElement('comment');
            $this->writer->writeAttribute('isDocComment', $node instanceof Comment\Doc ? 'true' : 'false');
            $this->writer->writeAttribute('line', (string) $node->getLine());
            $this->writer->text($node->getText());
            $this->writer->endElement();
        } elseif (is_array($node)) {
            $this->writer->startElement('scalar:array');
            foreach ($node as $subNode) {
                $this->_serialize($subNode);
            }
            $this->writer->endElement();
        } elseif (is_string($node)) {
            $this->writer->writeElement('scalar:string', $node);
        } elseif (is_int($node)) {
            $this->writer->writeElement('scalar:int', (string) $node);
        } elseif (is_float($node)) {
            // TODO Higher precision conversion?
            $this->writer->writeElement('scalar:float', (string) $node);
        } elseif (true === $node) {
            $this->writer->writeElement('scalar:true');
        } elseif (false === $node) {
            $this->writer->writeElement('scalar:false');
        } elseif (null === $node) {
            $this->writer->writeElement('scalar:null');
        } else {
            throw new \InvalidArgumentException('Unexpected node type');
        }
    }
}
<?php

namespace PhpParser;

interface Unserializer
{
    /**
     * Unserializes a string in some format into a node tree.
     *
     * @param string $string Serialized string
     *
     * @return mixed Node tree
     */
    public function unserialize($string);
}
<?php

namespace PhpParser\Unserializer;

use XMLReader;
use DomainException;
use PhpParser\Unserializer;

class XML implements Unserializer
{
    protected $reader;

    public function __construct() {
        $this->reader = new XMLReader;
    }

    public function unserialize($string) {
        $this->reader->XML($string);

        $this->reader->read();
        if ('AST' !== $this->reader->name) {
            throw new DomainException('AST root element not found');
        }

        return $this->read($this->reader->depth);
    }

    protected function read($depthLimit, $throw = true, &$nodeFound = null) {
        $nodeFound = true;
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
                continue;
            }

            if ('node' === $this->reader->prefix) {
                return $this->readNode();
            } elseif ('scalar' === $this->reader->prefix) {
                return $this->readScalar();
            } elseif ('comment' === $this->reader->name) {
                return $this->readComment();
            } else {
                throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name));
            }
        }

        $nodeFound = false;
        if ($throw) {
            throw new DomainException('Expected node or scalar');
        }
    }

    protected function readNode() {
        $className = $this->getClassNameFromType($this->reader->localName);

        // create the node without calling it's constructor
        $node = unserialize(
            sprintf(
                "O:%d:\"%s\":1:{s:13:\"\0*\0attributes\";a:0:{}}",
                strlen($className), $className
            )
        );

        $depthLimit = $this->reader->depth;
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
                continue;
            }

            $type = $this->reader->prefix;
            if ('subNode' !== $type && 'attribute' !== $type) {
                throw new DomainException(
                    sprintf('Expected sub node or attribute, got node of type "%s"', $this->reader->name)
                );
            }

            $name = $this->reader->localName;
            $value = $this->read($this->reader->depth);

            if ('subNode' === $type) {
                $node->$name = $value;
            } else {
                $node->setAttribute($name, $value);
            }
        }

        return $node;
    }

    protected function readScalar() {
        switch ($name = $this->reader->localName) {
            case 'array':
                $depth = $this->reader->depth;
                $array = array();
                while (true) {
                    $node = $this->read($depth, false, $nodeFound);
                    if (!$nodeFound) {
                        break;
                    }
                    $array[] = $node;
                }
                return $array;
            case 'string':
                return $this->reader->readString();
            case 'int':
                return $this->parseInt($this->reader->readString());
            case 'float':
                $text = $this->reader->readString();
                if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
                    throw new DomainException(sprintf('"%s" is not a valid float', $text));
                }
                return $float;
            case 'true':
            case 'false':
            case 'null':
                if (!$this->reader->isEmptyElement) {
                    throw new DomainException(sprintf('"%s" scalar must be empty', $name));
                }
                return constant($name);
            default:
                throw new DomainException(sprintf('Unknown scalar type "%s"', $name));
        }
    }

    private function parseInt($text) {
        if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
            throw new DomainException(sprintf('"%s" is not a valid integer', $text));
        }
        return $int;
    }

    protected function readComment() {
        $className = $this->reader->getAttribute('isDocComment') === 'true'
            ? 'PhpParser\Comment\Doc'
            : 'PhpParser\Comment'
        ;
        return new $className(
            $this->reader->readString(),
            $this->parseInt($this->reader->getAttribute('line'))
        );
    }

    protected function getClassNameFromType($type) {
        $className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
        if (!class_exists($className)) {
            $className .= '_';
        }
        if (!class_exists($className)) {
            throw new DomainException(sprintf('Unknown node type "%s"', $type));
        }
        return $className;
    }
}
<?php

if (!class_exists('PhpParser\Autoloader')) {
    require __DIR__ . '/PhpParser/Autoloader.php';
}
PhpParser\Autoloader::register();
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('short_open_tag', false);

if ('cli' !== php_sapi_name()) {
    die('This script is designed for running on the command line.');
}

function showHelp($error) {
    die($error . "\n\n" .
<<<OUTPUT
This script has to be called with the following signature:

    php run.php [--no-progress] testType pathToTestFiles

The test type must be one of: PHP5, PHP7 or Symfony.

The following options are available:

    --no-progress    Disables showing which file is currently tested.

OUTPUT
    );
}

$options = array();
$arguments = array();

// remove script name from argv
array_shift($argv);

foreach ($argv as $arg) {
    if ('-' === $arg[0]) {
        $options[] = $arg;
    } else {
        $arguments[] = $arg;
    }
}

if (count($arguments) !== 2) {
    showHelp('Too little arguments passed!');
}

$showProgress = true;
$verbose = false;
foreach ($options as $option) {
    if ($option === '--no-progress') {
        $showProgress = false;
    } elseif ($option === '--verbose') {
        $verbose = true;
    } else {
        showHelp('Invalid option passed!');
    }
}

$testType = $arguments[0];
$dir = $arguments[1];

switch ($testType) {
    case 'Symfony':
        $version = 'Php5';
        $fileFilter = function($path) {
            return preg_match('~\.php(?:\.cache)?$~', $path) && false === strpos($path, 'skeleton');
        };
        $codeExtractor = function($file, $code) {
            return $code;
        };
        break;
    case 'PHP5':
    case 'PHP7':
    $version = $testType === 'PHP5' ? 'Php5' : 'Php7';
        $fileFilter = function($path) {
            return preg_match('~\.phpt$~', $path);
        };
        $codeExtractor = function($file, $code) {
            if (preg_match('~(?:
# skeleton files
  ext.gmp.tests.001
| ext.skeleton.tests.001
# multibyte encoded files
| ext.mbstring.tests.zend_multibyte-01
| Zend.tests.multibyte.multibyte_encoding_001
| Zend.tests.multibyte.multibyte_encoding_004
| Zend.tests.multibyte.multibyte_encoding_005
# token_get_all bug (https://bugs.php.net/bug.php?id=60097)
| Zend.tests.bug47516
# pretty print difference due to INF vs 1e1000
| ext.standard.tests.general_functions.bug27678
| tests.lang.bug24640
# pretty print difference due to nop statements
| ext.mbstring.tests.htmlent
| ext.standard.tests.file.fread_basic
# tests using __halt_compiler as semi reserved keyword
| Zend.tests.grammar.semi_reserved_001
| Zend.tests.grammar.semi_reserved_002
| Zend.tests.grammar.semi_reserved_005
)\.phpt$~x', $file)) {
                return null;
            }

            if (!preg_match('~--FILE--\s*(.*?)--[A-Z]+--~s', $code, $matches)) {
                return null;
            }
            if (preg_match('~--EXPECT(?:F|REGEX)?--\s*(?:Parse|Fatal) error~', $code)) {
                return null;
            }

            return $matches[1];
        };
        break;
    default:
        showHelp('Test type must be one of: PHP5, PHP7 or Symfony');
}

require_once dirname(__FILE__) . '/../lib/PhpParser/Autoloader.php';
PhpParser\Autoloader::register();

$parserName    = 'PhpParser\Parser\\' . $version;
$parser        = new $parserName(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$nodeDumper    = new PhpParser\NodeDumper;

$parseFail = $ppFail = $compareFail = $count = 0;

$readTime = $parseTime = $ppTime = $reparseTime = $compareTime = 0;
$totalStartTime = microtime(true);

foreach (new RecursiveIteratorIterator(
             new RecursiveDirectoryIterator($dir),
             RecursiveIteratorIterator::LEAVES_ONLY)
         as $file) {
    if (!$fileFilter($file)) {
        continue;
    }

    $startTime = microtime(true);
    $code = file_get_contents($file);
    $readTime += microtime(true) - $startTime;

    if (null === $code = $codeExtractor($file, $code)) {
        continue;
    }

    set_time_limit(10);

    ++$count;

    if ($showProgress) {
        echo substr(str_pad('Testing file ' . $count . ': ' . substr($file, strlen($dir)), 79), 0, 79), "\r";
    }

    try {
        $startTime = microtime(true);
        $stmts = $parser->parse($code);
        $parseTime += microtime(true) - $startTime;

        $startTime = microtime(true);
        $code = '<?php' . "\n" . $prettyPrinter->prettyPrint($stmts);
        $ppTime += microtime(true) - $startTime;

        try {
            $startTime = microtime(true);
            $ppStmts = $parser->parse($code);
            $reparseTime += microtime(true) - $startTime;

            $startTime = microtime(true);
            $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts);
            $compareTime += microtime(true) - $startTime;

            if (!$same) {
                echo $file, ":\n    Result of initial parse and parse after pretty print differ\n";
                if ($verbose) {
                    echo "Pretty printer output:\n=====\n$code\n=====\n\n";
                }

                ++$compareFail;
            }
        } catch (PhpParser\Error $e) {
            echo $file, ":\n    Parse of pretty print failed with message: {$e->getMessage()}\n";
            if ($verbose) {
                echo "Pretty printer output:\n=====\n$code\n=====\n\n";
            }

            ++$ppFail;
        }
    } catch (PhpParser\Error $e) {
        echo $file, ":\n    Parse failed with message: {$e->getMessage()}\n";

        ++$parseFail;
    }
}

if (0 === $parseFail && 0 === $ppFail && 0 === $compareFail) {
    $exit = 0;
    echo "\n\n", 'All tests passed.', "\n";
} else {
    $exit = 1;
    echo "\n\n", '==========', "\n\n", 'There were: ', "\n";
    if (0 !== $parseFail) {
        echo '    ', $parseFail,   ' parse failures.',        "\n";
    }
    if (0 !== $ppFail) {
        echo '    ', $ppFail,      ' pretty print failures.', "\n";
    }
    if (0 !== $compareFail) {
        echo '    ', $compareFail, ' compare failures.',      "\n";
    }
}

echo "\n",
     'Tested files:         ', $count,        "\n",
     "\n",
     'Reading files took:   ', $readTime,    "\n",
     'Parsing took:         ', $parseTime,   "\n",
     'Pretty printing took: ', $ppTime,      "\n",
     'Reparsing took:       ', $reparseTime, "\n",
     'Comparing took:       ', $compareTime, "\n",
     "\n",
     'Total time:           ', microtime(true) - $totalStartTime, "\n",
     'Maximum memory usage: ', memory_get_peak_usage(true), "\n";

exit($exit);
<?php

namespace XdgBaseDir;

/**
 * Simple implementation of the XDG standard http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
 *
 * Based on the python implementation https://github.com/takluyver/pyxdg/blob/master/xdg/BaseDirectory.py
 *
 * Class Xdg
 * @package ShopwareCli\Application
 */
class Xdg
{
    const S_IFDIR = 040000; // directory
    const S_IRWXO = 00007;  // rwx other
    const S_IRWXG = 00056;  // rwx group
    const RUNTIME_DIR_FALLBACK = 'php-xdg-runtime-dir-fallback-';

    /**
     * @return string
     */
    public function getHomeDir()
    {
        return getenv('HOME') ?: (getenv('HOMEDRIVE') . DIRECTORY_SEPARATOR . getenv('HOMEPATH'));
    }

    /**
     * @return string
     */
    public function getHomeConfigDir()
    {
        $path = getenv('XDG_CONFIG_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.config';

        return $path;
    }

    /**
     * @return string
     */
    public function getHomeDataDir()
    {
        $path = getenv('XDG_DATA_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.local' . DIRECTORY_SEPARATOR . 'share';

        return $path;
    }

    /**
     * @return array
     */
    public function getConfigDirs()
    {
        $configDirs = getenv('XDG_CONFIG_DIRS') ? explode(':', getenv('XDG_CONFIG_DIRS')) : array('/etc/xdg');

        $paths = array_merge(array($this->getHomeConfigDir()), $configDirs);

        return $paths;
    }

    /**
     * @return array
     */
    public function getDataDirs()
    {
        $dataDirs = getenv('XDG_DATA_DIRS') ? explode(':', getenv('XDG_DATA_DIRS')) : array('/usr/local/share', '/usr/share');

        $paths = array_merge(array($this->getHomeDataDir()), $dataDirs);

        return $paths;
    }

    /**
     * @return string
     */
    public function getHomeCacheDir()
    {
        $path = getenv('XDG_CACHE_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.cache';

        return $path;

    }

    public function getRuntimeDir($strict=true)
    {
        if ($runtimeDir = getenv('XDG_RUNTIME_DIR')) {
            return $runtimeDir;
        }

        if ($strict) {
            throw new \RuntimeException('XDG_RUNTIME_DIR was not set');
        }

        $fallback = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::RUNTIME_DIR_FALLBACK . getenv('USER');

        $create = false;

        if (!is_dir($fallback)) {
            mkdir($fallback, 0700, true);
        }

        $st = lstat($fallback);

        # The fallback must be a directory
        if (!$st['mode'] & self::S_IFDIR) {
            rmdir($fallback);
            $create = true;
        } elseif ($st['uid'] != getmyuid() ||
            $st['mode'] & (self::S_IRWXG | self::S_IRWXO)
        ) {
            rmdir($fallback);
            $create = true;
        }

        if ($create) {
            mkdir($fallback, 0700, true);
        }

        return $fallback;
    }

}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

/**
 * Psy class autoloader.
 */
class Autoloader
{
    /**
     * Register autoload() as an SPL autoloader.
     *
     * @see self::autoload
     */
    public static function register()
    {
        spl_autoload_register(array(__CLASS__, 'autoload'));
    }

    /**
     * Autoload Psy classes.
     *
     * @param string $class
     */
    public static function autoload($class)
    {
        if (0 !== strpos($class, 'Psy')) {
            return;
        }

        $file = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
        if (is_file($file)) {
            require $file;
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use PhpParser\NodeTraverser;
use PhpParser\Parser;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\CodeCleaner\AbstractClassPass;
use Psy\CodeCleaner\AssignThisVariablePass;
use Psy\CodeCleaner\CalledClassPass;
use Psy\CodeCleaner\CallTimePassByReferencePass;
use Psy\CodeCleaner\ExitPass;
use Psy\CodeCleaner\FinalClassPass;
use Psy\CodeCleaner\FunctionContextPass;
use Psy\CodeCleaner\FunctionReturnInWriteContextPass;
use Psy\CodeCleaner\ImplicitReturnPass;
use Psy\CodeCleaner\InstanceOfPass;
use Psy\CodeCleaner\LeavePsyshAlonePass;
use Psy\CodeCleaner\LegacyEmptyPass;
use Psy\CodeCleaner\LoopContextPass;
use Psy\CodeCleaner\MagicConstantsPass;
use Psy\CodeCleaner\NamespacePass;
use Psy\CodeCleaner\PassableByReferencePass;
use Psy\CodeCleaner\RequirePass;
use Psy\CodeCleaner\StaticConstructorPass;
use Psy\CodeCleaner\StrictTypesPass;
use Psy\CodeCleaner\UseStatementPass;
use Psy\CodeCleaner\ValidClassNamePass;
use Psy\CodeCleaner\ValidConstantPass;
use Psy\CodeCleaner\ValidFunctionNamePass;
use Psy\Exception\ParseErrorException;

/**
 * A service to clean up user input, detect parse errors before they happen,
 * and generally work around issues with the PHP code evaluation experience.
 */
class CodeCleaner
{
    private $parser;
    private $printer;
    private $traverser;
    private $namespace;

    /**
     * CodeCleaner constructor.
     *
     * @param Parser        $parser    A PhpParser Parser instance. One will be created if not explicitly supplied
     * @param Printer       $printer   A PhpParser Printer instance. One will be created if not explicitly supplied
     * @param NodeTraverser $traverser A PhpParser NodeTraverser instance. One will be created if not explicitly supplied
     */
    public function __construct(Parser $parser = null, Printer $printer = null, NodeTraverser $traverser = null)
    {
        if ($parser === null) {
            $parserFactory = new ParserFactory();
            $parser        = $parserFactory->createParser();
        }

        $this->parser    = $parser;
        $this->printer   = $printer ?: new Printer();
        $this->traverser = $traverser ?: new NodeTraverser();

        foreach ($this->getDefaultPasses() as $pass) {
            $this->traverser->addVisitor($pass);
        }
    }

    /**
     * Get default CodeCleaner passes.
     *
     * @return array
     */
    private function getDefaultPasses()
    {
        return array(
            // Validation passes
            new AbstractClassPass(),
            new AssignThisVariablePass(),
            new CalledClassPass(),
            new CallTimePassByReferencePass(),
            new FinalClassPass(),
            new FunctionContextPass(),
            new FunctionReturnInWriteContextPass(),
            new InstanceOfPass(),
            new LeavePsyshAlonePass(),
            new LegacyEmptyPass(),
            new LoopContextPass(),
            new PassableByReferencePass(),
            new StaticConstructorPass(),

            // Rewriting shenanigans
            new UseStatementPass(),   // must run before the namespace pass
            new ExitPass(),
            new ImplicitReturnPass(),
            new MagicConstantsPass(),
            new NamespacePass($this), // must run after the implicit return pass
            new RequirePass(),
            new StrictTypesPass(),

            // Namespace-aware validation (which depends on aforementioned shenanigans)
            new ValidClassNamePass(),
            new ValidConstantPass(),
            new ValidFunctionNamePass(),
        );
    }

    /**
     * Clean the given array of code.
     *
     * @throws ParseErrorException if the code is invalid PHP, and cannot be coerced into valid PHP
     *
     * @param array $codeLines
     * @param bool  $requireSemicolons
     *
     * @return string|false Cleaned PHP code, False if the input is incomplete
     */
    public function clean(array $codeLines, $requireSemicolons = false)
    {
        $stmts = $this->parse('<?php ' . implode(PHP_EOL, $codeLines) . PHP_EOL, $requireSemicolons);
        if ($stmts === false) {
            return false;
        }

        // Catch fatal errors before they happen
        $stmts = $this->traverser->traverse($stmts);

        // Work around https://github.com/nikic/PHP-Parser/issues/399
        $oldLocale = setlocale(LC_NUMERIC, 0);
        setlocale(LC_NUMERIC, 'C');

        $code = $this->printer->prettyPrint($stmts);

        // Now put the locale back
        setlocale(LC_NUMERIC, $oldLocale);

        return $code;
    }

    /**
     * Set the current local namespace.
     *
     * @param null|array $namespace (default: null)
     *
     * @return null|array
     */
    public function setNamespace(array $namespace = null)
    {
        $this->namespace = $namespace;
    }

    /**
     * Get the current local namespace.
     *
     * @return null|array
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * Lex and parse a block of code.
     *
     * @see Parser::parse
     *
     * @throws ParseErrorException for parse errors that can't be resolved by
     *                             waiting a line to see what comes next
     *
     * @param string $code
     * @param bool   $requireSemicolons
     *
     * @return array|false A set of statements, or false if incomplete
     */
    protected function parse($code, $requireSemicolons = false)
    {
        try {
            return $this->parser->parse($code);
        } catch (\PhpParser\Error $e) {
            if ($this->parseErrorIsUnclosedString($e, $code)) {
                return false;
            }

            if ($this->parseErrorIsUnterminatedComment($e, $code)) {
                return false;
            }

            if ($this->parseErrorIsTrailingComma($e, $code)) {
                return false;
            }

            if (!$this->parseErrorIsEOF($e)) {
                throw ParseErrorException::fromParseError($e);
            }

            if ($requireSemicolons) {
                return false;
            }

            try {
                // Unexpected EOF, try again with an implicit semicolon
                return $this->parser->parse($code . ';');
            } catch (\PhpParser\Error $e) {
                return false;
            }
        }
    }

    private function parseErrorIsEOF(\PhpParser\Error $e)
    {
        $msg = $e->getRawMessage();

        return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false);
    }

    /**
     * A special test for unclosed single-quoted strings.
     *
     * Unlike (all?) other unclosed statements, single quoted strings have
     * their own special beautiful snowflake syntax error just for
     * themselves.
     *
     * @param \PhpParser\Error $e
     * @param string           $code
     *
     * @return bool
     */
    private function parseErrorIsUnclosedString(\PhpParser\Error $e, $code)
    {
        if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') {
            return false;
        }

        try {
            $this->parser->parse($code . "';");
        } catch (\Exception $e) {
            return false;
        }

        return true;
    }

    private function parseErrorIsUnterminatedComment(\PhpParser\Error $e, $code)
    {
        return $e->getRawMessage() === 'Unterminated comment';
    }

    private function parseErrorIsTrailingComma(\PhpParser\Error $e, $code)
    {
        return ($e->getRawMessage() === 'A trailing comma is not allowed here') && (substr(rtrim($code), -1) === ',');
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Psy\Exception\FatalErrorException;

/**
 * The abstract class pass handles abstract classes and methods, complaining if there are too few or too many of either.
 */
class AbstractClassPass extends CodeCleanerPass
{
    private $class;
    private $abstractMethods;

    /**
     * @throws RuntimeException if the node is an abstract function with a body
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Class_) {
            $this->class = $node;
            $this->abstractMethods = array();
        } elseif ($node instanceof ClassMethod) {
            if ($node->isAbstract()) {
                $name = sprintf('%s::%s', $this->class->name, $node->name);
                $this->abstractMethods[] = $name;

                if ($node->stmts !== null) {
                    $msg = sprintf('Abstract function %s cannot contain body', $name);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                }
            }
        }
    }

    /**
     * @throws RuntimeException if the node is a non-abstract class with abstract methods
     *
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof Class_) {
            $count = count($this->abstractMethods);
            if ($count > 0 && !$node->isAbstract()) {
                $msg = sprintf(
                    'Class %s contains %d abstract method%s must therefore be declared abstract or implement the remaining methods (%s)',
                    $node->name,
                    $count,
                    ($count === 1) ? '' : 's',
                    implode(', ', $this->abstractMethods)
                );
                throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\FatalErrorException;

/**
 * Validate that the user input does not assign the `$this` variable.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class AssignThisVariablePass extends CodeCleanerPass
{
    /**
     * Validate that the user input does not assign the `$this` variable.
     *
     * @throws RuntimeException if the user assign the `$this` variable
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Assign && $node->var instanceof Variable && $node->var->name === 'this') {
            throw new FatalErrorException('Cannot re-assign $this', 0, E_ERROR, null, $node->getLine());
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use Psy\Exception\FatalErrorException;

/**
 * Validate that the user did not use the call-time pass-by-reference that causes a fatal error.
 *
 * As of PHP 5.4.0, call-time pass-by-reference was removed, so using it will raise a fatal error.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class CallTimePassByReferencePass extends CodeCleanerPass
{
    const EXCEPTION_MESSAGE = 'Call-time pass-by-reference has been removed';

    /**
     * Validate of use call-time pass-by-reference.
     *
     * @throws RuntimeException if the user used call-time pass-by-reference in PHP >= 5.4.0
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if (version_compare(PHP_VERSION, '5.4', '<')) {
            return;
        }

        if (!$node instanceof FuncCall && !$node instanceof MethodCall && !$node instanceof StaticCall) {
            return;
        }

        foreach ($node->args as $arg) {
            if ($arg->byRef) {
                throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Trait_;
use Psy\Exception\ErrorException;

/**
 * The called class pass throws warnings for get_class() and get_called_class()
 * outside a class context.
 */
class CalledClassPass extends CodeCleanerPass
{
    private $inClass;

    /**
     * @param array $nodes
     */
    public function beforeTraverse(array $nodes)
    {
        $this->inClass = false;
    }

    /**
     * @throws ErrorException if get_class or get_called_class is called without an object from outside a class
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Class_ || $node instanceof Trait_) {
            $this->inClass = true;
        } elseif ($node instanceof FuncCall && !$this->inClass) {
            // We'll give any args at all (besides null) a pass.
            // Technically we should be checking whether the args are objects, but this will do for now.
            //
            // @todo switch this to actually validate args when we get context-aware code cleaner passes.
            if (!empty($node->args) && !$this->isNull($node->args[0])) {
                return;
            }

            // We'll ignore name expressions as well (things like `$foo()`)
            if (!($node->name instanceof Name)) {
                return;
            }

            $name = strtolower($node->name);
            if (in_array($name, array('get_class', 'get_called_class'))) {
                $msg = sprintf('%s() called without object from outside a class', $name);
                throw new ErrorException($msg, 0, E_USER_WARNING, null, $node->getLine());
            }
        }
    }

    /**
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof Class_) {
            $this->inClass = false;
        }
    }

    private function isNull(Node $node)
    {
        return $node->value instanceof ConstFetch && strtolower($node->value->name) === 'null';
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\NodeVisitorAbstract;

/**
 * A CodeCleaner pass is a PhpParser Node Visitor.
 */
abstract class CodeCleanerPass extends NodeVisitorAbstract
{
    // Wheee!
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;

class ExitPass extends CodeCleanerPass
{
    /**
     * Converts exit calls to BreakExceptions.
     *
     * @param \PhpParser\Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof Exit_) {
            return new StaticCall(new FullyQualifiedName('Psy\Exception\BreakException'), 'exitShell');
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Psy\Exception\FatalErrorException;

/**
 * The final class pass handles final classes.
 */
class FinalClassPass extends CodeCleanerPass
{
    private $finalClasses;

    /**
     * @param array $nodes
     */
    public function beforeTraverse(array $nodes)
    {
        $this->finalClasses = array();
    }

    /**
     * @throws RuntimeException if the node is a class that extends a final class
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Class_) {
            if ($node->extends) {
                $extends = (string) $node->extends;
                if ($this->isFinalClass($extends)) {
                    $msg = sprintf('Class %s may not inherit from final class (%s)', $node->name, $extends);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                }
            }

            if ($node->isFinal()) {
                $this->finalClasses[strtolower($node->name)] = true;
            }
        }
    }

    /**
     * @param string $name Class name
     *
     * @return bool
     */
    private function isFinalClass($name)
    {
        if (!class_exists($name)) {
            return isset($this->finalClasses[strtolower($name)]);
        }

        $refl = new \ReflectionClass($name);

        return $refl->isFinal();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
use Psy\Exception\FatalErrorException;

class FunctionContextPass extends CodeCleanerPass
{
    /** @var int */
    private $functionDepth;

    /**
     * @param array $nodes
     */
    public function beforeTraverse(array $nodes)
    {
        $this->functionDepth = 0;
    }

    public function enterNode(Node $node)
    {
        if ($node instanceof FunctionLike) {
            $this->functionDepth++;

            return;
        }

        // node is inside function context
        if ($this->functionDepth !== 0) {
            return;
        }

        // It causes fatal error.
        if ($node instanceof Yield_) {
            $msg = 'The "yield" expression can only be used inside a function';
            throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
        }
    }

    /**
     * @param \PhpParser\Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof FunctionLike) {
            $this->functionDepth--;
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Unset_;
use Psy\Exception\FatalErrorException;

/**
 * Validate that the functions are used correctly.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class FunctionReturnInWriteContextPass extends CodeCleanerPass
{
    const PHP55_MESSAGE = 'Cannot use isset() on the result of a function call (you can use "null !== func()" instead)';
    const EXCEPTION_MESSAGE = "Can't use function return value in write context";

    private $isPhp55;

    public function __construct()
    {
        $this->isPhp55 = version_compare(PHP_VERSION, '5.5', '>=');
    }

    /**
     * Validate that the functions are used correctly.
     *
     * @throws FatalErrorException if a function is passed as an argument reference
     * @throws FatalErrorException if a function is used as an argument in the isset
     * @throws FatalErrorException if a function is used as an argument in the empty, only for PHP < 5.5
     * @throws FatalErrorException if a value is assigned to a function
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Array_ || $this->isCallNode($node)) {
            $items = $node instanceof Array_ ? $node->items : $node->args;
            foreach ($items as $item) {
                if ($item && $item->byRef && $this->isCallNode($item->value)) {
                    throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
                }
            }
        } elseif ($node instanceof Isset_ || $node instanceof Unset_) {
            foreach ($node->vars as $var) {
                if (!$this->isCallNode($var)) {
                    continue;
                }

                $msg = ($node instanceof Isset_ && $this->isPhp55) ? self::PHP55_MESSAGE : self::EXCEPTION_MESSAGE;
                throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
            }
        } elseif ($node instanceof Empty_ && !$this->isPhp55 && $this->isCallNode($node->expr)) {
            throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
        } elseif ($node instanceof Assign && $this->isCallNode($node->var)) {
            throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
        }
    }

    private function isCallNode(Node $node)
    {
        return $node instanceof FuncCall || $node instanceof MethodCall || $node instanceof StaticCall;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Exit_;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Break_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Switch_;

/**
 * Add an implicit "return" to the last statement, provided it can be returned.
 */
class ImplicitReturnPass extends CodeCleanerPass
{
    /**
     * @param array $nodes
     *
     * @return array
     */
    public function beforeTraverse(array $nodes)
    {
        return $this->addImplicitReturn($nodes);
    }

    /**
     * @param array $nodes
     *
     * @return array
     */
    private function addImplicitReturn(array $nodes)
    {
        // If nodes is empty, it can't have a return value.
        if (empty($nodes)) {
            return array(new Return_(new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue'))));
        }

        $last = end($nodes);

        // Special case a few types of statements to add an implicit return
        // value (even though they technically don't have any return value)
        // because showing a return value in these instances is useful and not
        // very surprising.
        if ($last instanceof If_) {
            $last->stmts = $this->addImplicitReturn($last->stmts);

            foreach ($last->elseifs as $elseif) {
                $elseif->stmts = $this->addImplicitReturn($elseif->stmts);
            }

            if ($last->else) {
                $last->else->stmts = $this->addImplicitReturn($last->else->stmts);
            }
        } elseif ($last instanceof Switch_) {
            foreach ($last->cases as $case) {
                // only add an implicit return to cases which end in break
                $caseLast = end($case->stmts);
                if ($caseLast instanceof Break_) {
                    $case->stmts = $this->addImplicitReturn(array_slice($case->stmts, 0, -1));
                    $case->stmts[] = $caseLast;
                }
            }
        } elseif ($last instanceof Expr && !($last instanceof Exit_)) {
            $nodes[count($nodes) - 1] = new Return_($last, array(
                'startLine' => $last->getLine(),
                'endLine'   => $last->getLine(),
            ));
        } elseif ($last instanceof Namespace_) {
            $last->stmts = $this->addImplicitReturn($last->stmts);
        }

        // Return a "no return value" for all non-expression statements, so that
        // PsySH can suppress the `null` that `eval()` returns otherwise.
        //
        // Note that statements special cased above (if/elseif/else, switch)
        // _might_ implicitly return a value before this catch-all return is
        // reached.
        //
        // We're not adding a fallback return after namespace statements,
        // because code outside namespace statements doesn't really work, and
        // there's already an implicit return in the namespace statement anyway.
        if ($last instanceof Stmt && !$last instanceof Return_ && !$last instanceof Namespace_) {
            $nodes[] = new Return_(new New_(new FullyQualifiedName('Psy\CodeCleaner\NoReturnValue')));
        }

        return $nodes;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\Encapsed;
use Psy\Exception\FatalErrorException;

/**
 * Validate that the instanceof statement does not receive a scalar value or a non-class constant.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class InstanceOfPass extends CodeCleanerPass
{
    const EXCEPTION_MSG = 'instanceof expects an object instance, constant given';

    /**
     * Validate that the instanceof statement does not receive a scalar value or a non-class constant.
     *
     * @throws FatalErrorException if a scalar or a non-class constant is given
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if (!$node instanceof Instanceof_) {
            return;
        }

        if (($node->expr instanceof Scalar && !$node->expr instanceof Encapsed) || $node->expr instanceof ConstFetch) {
            throw new FatalErrorException(self::EXCEPTION_MSG, 0, E_ERROR, null, $node->getLine());
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\RuntimeException;

/**
 * Validate that the user input does not reference the `$__psysh__` variable.
 */
class LeavePsyshAlonePass extends CodeCleanerPass
{
    /**
     * Validate that the user input does not reference the `$__psysh__` variable.
     *
     * @throws RuntimeException if the user is messing with $__psysh__
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Variable && $node->name === '__psysh__') {
            throw new RuntimeException('Don\'t mess with $__psysh__. Bad things will happen.');
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\ParseErrorException;

/**
 * Validate that the user did not call the language construct `empty()` on a
 * statement in PHP < 5.5.
 */
class LegacyEmptyPass extends CodeCleanerPass
{
    /**
     * Validate use of empty in PHP < 5.5.
     *
     * @throws ParseErrorException if the user used empty with anything but a variable
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if (version_compare(PHP_VERSION, '5.5', '>=')) {
            return;
        }

        if (!$node instanceof Empty_) {
            return;
        }

        if (!$node->expr instanceof Variable) {
            $msg = sprintf('syntax error, unexpected %s', $this->getUnexpectedThing($node->expr));

            throw new ParseErrorException($msg, $node->expr->getLine());
        }
    }

    private function getUnexpectedThing(Node $node)
    {
        switch ($node->getType()) {
            case 'Scalar_String':
            case 'Scalar_LNumber':
            case 'Scalar_DNumber':
                return json_encode($node->value);

            case 'Expr_ConstFetch':
                return (string) $node->name;

            default:
                return $node->getType();
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Scalar\DNumber;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Break_;
use PhpParser\Node\Stmt\Continue_;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\While_;
use Psy\Exception\FatalErrorException;

/**
 * The loop context pass handles invalid `break` and `continue` statements.
 */
class LoopContextPass extends CodeCleanerPass
{
    private $isPHP54;
    private $loopDepth;

    public function __construct()
    {
        $this->isPHP54 = version_compare(PHP_VERSION, '5.4.0', '>=');
    }

    /**
     * {@inheritdoc}
     */
    public function beforeTraverse(array $nodes)
    {
        $this->loopDepth = 0;
    }

    /**
     * @throws FatalErrorException if the node is a break or continue in a non-loop or switch context
     * @throws FatalErrorException if the node is trying to break out of more nested structures than exist
     * @throws FatalErrorException if the node is a break or continue and has a non-numeric argument
     * @throws FatalErrorException if the node is a break or continue and has an argument less than 1
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        switch (true) {
            case $node instanceof Do_:
            case $node instanceof For_:
            case $node instanceof Foreach_:
            case $node instanceof Switch_:
            case $node instanceof While_:
                $this->loopDepth++;
                break;

            case $node instanceof Break_:
            case $node instanceof Continue_:
                $operator = $node instanceof Break_ ? 'break' : 'continue';

                if ($this->loopDepth === 0) {
                    $msg = sprintf("'%s' not in the 'loop' or 'switch' context", $operator);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                }

                if ($node->num instanceof LNumber || $node->num instanceof DNumber) {
                    $num = $node->num->value;
                    if ($this->isPHP54 && ($node->num instanceof DNumber || $num < 1)) {
                        $msg = sprintf("'%s' operator accepts only positive numbers", $operator);
                        throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                    }

                    if ($num > $this->loopDepth) {
                        $msg = sprintf("Cannot '%s' %d levels", $operator, $num);
                        throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                    }
                } elseif ($node->num && $this->isPHP54) {
                    $msg = sprintf("'%s' operator with non-constant operand is no longer supported", $operator);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                }
                break;
        }
    }

    /**
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        switch (true) {
            case $node instanceof Do_:
            case $node instanceof For_:
            case $node instanceof Foreach_:
            case $node instanceof Switch_:
            case $node instanceof While_:
                $this->loopDepth--;
                break;
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\MagicConst\Dir;
use PhpParser\Node\Scalar\MagicConst\File;
use PhpParser\Node\Scalar\String_;

/**
 * Swap out __DIR__ and __FILE__ magic constants with our best guess?
 */
class MagicConstantsPass extends CodeCleanerPass
{
    /**
     * Swap out __DIR__ and __FILE__ constants, because the default ones when
     * calling eval() don't make sense.
     *
     * @param Node $node
     *
     * @return null|FuncCall|String_
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Dir) {
            return new FuncCall(new Name('getcwd'), array(), $node->getAttributes());
        } elseif ($node instanceof File) {
            return new String_('', $node->getAttributes());
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\Namespace_;

/**
 * Abstract namespace-aware code cleaner pass.
 */
abstract class NamespaceAwarePass extends CodeCleanerPass
{
    protected $namespace;
    protected $currentScope;

    /**
     * @todo should this be final? Extending classes should be sure to either
     * use afterTraverse or call parent::beforeTraverse() when overloading.
     *
     * Reset the namespace and the current scope before beginning analysis
     */
    public function beforeTraverse(array $nodes)
    {
        $this->namespace    = array();
        $this->currentScope = array();
    }

    /**
     * @todo should this be final? Extending classes should be sure to either use
     * leaveNode or call parent::enterNode() when overloading
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Namespace_) {
            $this->namespace = isset($node->name) ? $node->name->parts : array();
        }
    }

    /**
     * Get a fully-qualified name (class, function, interface, etc).
     *
     * @param mixed $name
     *
     * @return string
     */
    protected function getFullyQualifiedName($name)
    {
        if ($name instanceof FullyQualifiedName) {
            return implode('\\', $name->parts);
        } elseif ($name instanceof Name) {
            $name = $name->parts;
        } elseif (!is_array($name)) {
            $name = array($name);
        }

        return implode('\\', array_merge($this->namespace, $name));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Namespace_;
use Psy\CodeCleaner;

/**
 * Provide implicit namespaces for subsequent execution.
 *
 * The namespace pass remembers the last standalone namespace line encountered:
 *
 *     namespace Foo\Bar;
 *
 * ... which it then applies implicitly to all future evaluated code, until the
 * namespace is replaced by another namespace. To reset to the top level
 * namespace, enter `namespace {}`. This is a bit ugly, but it does the trick :)
 */
class NamespacePass extends CodeCleanerPass
{
    private $namespace = null;
    private $cleaner;

    /**
     * @param CodeCleaner $cleaner
     */
    public function __construct(CodeCleaner $cleaner)
    {
        $this->cleaner = $cleaner;
    }

    /**
     * If this is a standalone namespace line, remember it for later.
     *
     * Otherwise, apply remembered namespaces to the code until a new namespace
     * is encountered.
     *
     * @param array $nodes
     */
    public function beforeTraverse(array $nodes)
    {
        if (empty($nodes)) {
            return $nodes;
        }

        $last = end($nodes);
        if (!$last instanceof Namespace_) {
            return $this->namespace ? array(new Namespace_($this->namespace, $nodes)) : $nodes;
        }

        $this->setNamespace($last->name);

        return $nodes;
    }

    /**
     * Remember the namespace and (re)set the namespace on the CodeCleaner as
     * well.
     *
     * @param null|Name $namespace
     */
    private function setNamespace($namespace)
    {
        $this->namespace = $namespace;
        $this->cleaner->setNamespace($namespace === null ? null : $namespace->parts);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

/**
 * A class used internally by CodeCleaner to represent input, such as
 * non-expression statements, with no return value.
 *
 * Note that user code returning an instance of this class will act like it
 * has no return value, so you prolly shouldn't do that.
 */
class NoReturnValue
{
    // this space intentionally left blank
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use Psy\Exception\FatalErrorException;

/**
 * Validate that only variables (and variable-like things) are passed by reference.
 */
class PassableByReferencePass extends CodeCleanerPass
{
    const EXCEPTION_MESSAGE = 'Only variables can be passed by reference';

    /**
     * @throws FatalErrorException if non-variables are passed by reference
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        // @todo support MethodCall and StaticCall as well.
        if ($node instanceof FuncCall) {
            // if function name is an expression or a variable, give it a pass for now.
            if ($node->name instanceof Expr || $node->name instanceof Variable) {
                return;
            }

            $name = (string) $node->name;

            if ($name === 'array_multisort') {
                return $this->validateArrayMultisort($node);
            }

            try {
                $refl = new \ReflectionFunction($name);
            } catch (\ReflectionException $e) {
                // Well, we gave it a shot!
                return;
            }

            foreach ($refl->getParameters() as $key => $param) {
                if (array_key_exists($key, $node->args)) {
                    $arg = $node->args[$key];
                    if ($param->isPassedByReference() && !$this->isPassableByReference($arg)) {
                        throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
                    }
                }
            }
        }
    }

    private function isPassableByReference(Node $arg)
    {
        // FuncCall, MethodCall and StaticCall are all PHP _warnings_ not fatal errors, so we'll let
        // PHP handle those ones :)
        return $arg->value instanceof ClassConstFetch ||
            $arg->value instanceof PropertyFetch ||
            $arg->value instanceof Variable ||
            $arg->value instanceof FuncCall ||
            $arg->value instanceof MethodCall ||
            $arg->value instanceof StaticCall;
    }

    /**
     * Because array_multisort has a problematic signature...
     *
     * The argument order is all sorts of wonky, and whether something is passed
     * by reference or not depends on the values of the two arguments before it.
     * We'll do a good faith attempt at validating this, but err on the side of
     * permissive.
     *
     * This is why you don't design languages where core code and extensions can
     * implement APIs that wouldn't be possible in userland code.
     *
     * @throws FatalErrorException for clearly invalid arguments
     *
     * @param Node $node
     */
    private function validateArrayMultisort(Node $node)
    {
        $nonPassable = 2; // start with 2 because the first one has to be passable by reference
        foreach ($node->args as $arg) {
            if ($this->isPassableByReference($arg)) {
                $nonPassable = 0;
            } elseif (++$nonPassable > 2) {
                // There can be *at most* two non-passable-by-reference args in a row. This is about
                // as close as we can get to validating the arguments for this function :-/
                throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Scalar\LNumber;
use Psy\Exception\ErrorException;
use Psy\Exception\FatalErrorException;
use Psy\Shell;

/**
 * Add runtime validation for `require` and `require_once` calls.
 */
class RequirePass extends CodeCleanerPass
{
    private static $requireTypes = array(Include_::TYPE_REQUIRE, Include_::TYPE_REQUIRE_ONCE);

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $origNode)
    {
        if (!$this->isRequireNode($origNode)) {
            return;
        }

        $node = clone $origNode;

        /*
         * rewrite
         *
         *   $foo = require $bar
         *
         * to
         *
         *   $foo = require \Psy\CodeCleaner\RequirePass::resolve($bar)
         */
        $node->expr = new StaticCall(
            new FullyQualifiedName('Psy\CodeCleaner\RequirePass'),
            'resolve',
            array(new Arg($origNode->expr), new Arg(new LNumber($origNode->getLine()))),
            $origNode->getAttributes()
        );

        return $node;
    }

    /**
     * Runtime validation that $file can be resolved as an include path.
     *
     * If $file can be resolved, return $file. Otherwise throw a fatal error exception.
     *
     * @throws FatalErrorException when unable to resolve include path for $file
     * @throws ErrorException      if $file is empty and E_WARNING is included in error_reporting level
     *
     * @param string $file
     * @param int    $lineNumber Line number of the original require expression
     *
     * @return string Exactly the same as $file
     */
    public static function resolve($file, $lineNumber = null)
    {
        $file = (string) $file;

        if ($file === '') {
            // @todo Shell::handleError would be better here, because we could
            // fake the file and line number, but we can't call it statically.
            // So we're duplicating some of the logics here.
            if (E_WARNING & error_reporting()) {
                ErrorException::throwException(E_WARNING, 'Filename cannot be empty', null, $lineNumber);
            } else {
                // @todo trigger an error as fallback? this is pretty ugly…
                // trigger_error('Filename cannot be empty', E_USER_WARNING);
            }
        }

        if ($file === '' || !stream_resolve_include_path($file)) {
            $msg = sprintf("Failed opening required '%s'", $file);
            throw new FatalErrorException($msg, 0, E_ERROR, null, $lineNumber);
        }

        return $file;
    }

    private function isRequireNode(Node $node)
    {
        return $node instanceof Include_ && in_array($node->type, self::$requireTypes);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Namespace_;
use Psy\Exception\FatalErrorException;

/**
 * Validate that the old-style constructor function is not static.
 *
 * As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name
 * will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
 *
 * Validation of the __construct method ensures the PHP Parser.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class StaticConstructorPass extends CodeCleanerPass
{
    private $isPHP533;
    private $namespace;

    public function __construct()
    {
        $this->isPHP533 = version_compare(PHP_VERSION, '5.3.3', '>=');
    }

    public function beforeTraverse(array $nodes)
    {
        $this->namespace = array();
    }

    /**
     * Validate that the old-style constructor function is not static.
     *
     * @throws FatalErrorException if the old-style constructor function is static
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Namespace_) {
            $this->namespace = isset($node->name) ? $node->name->parts : array();
        } elseif ($node instanceof Class_) {
            // Bail early if this is PHP 5.3.3 and we have a namespaced class
            if (!empty($this->namespace) && $this->isPHP533) {
                return;
            }

            $constructor = null;
            foreach ($node->stmts as $stmt) {
                if ($stmt instanceof ClassMethod) {
                    // Bail early if we find a new-style constructor
                    if ('__construct' === strtolower($stmt->name)) {
                        return;
                    }

                    // We found a possible old-style constructor
                    // (unless there is also a __construct method)
                    if (strtolower($node->name) === strtolower($stmt->name)) {
                        $constructor = $stmt;
                    }
                }
            }

            if ($constructor && $constructor->isStatic()) {
                $msg = sprintf(
                    'Constructor %s::%s() cannot be static',
                    implode('\\', array_merge($this->namespace, (array) $node->name)),
                    $constructor->name
                );
                throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\DeclareDeclare;
use Psy\Exception\FatalErrorException;

/**
 * Provide implicit strict types declarations for for subsequent execution.
 *
 * The strict types pass remembers the last strict types declaration:
 *
 *     declare(strict_types=1);
 *
 * ... which it then applies implicitly to all future evaluated code, until it
 * is replaced by a new declaration.
 */
class StrictTypesPass extends CodeCleanerPass
{
    const EXCEPTION_MESSAGE = 'strict_types declaration must have 0 or 1 as its value';

    private $strictTypes = false;

    /**
     * If this is a standalone strict types declaration, remember it for later.
     *
     * Otherwise, apply remembered strict types declaration to to the code until
     * a new declaration is encountered.
     *
     * @throws FatalErrorException if an invalid `strict_types` declaration is found
     *
     * @param array $nodes
     */
    public function beforeTraverse(array $nodes)
    {
        if (version_compare(PHP_VERSION, '7.0', '<')) {
            return;
        }

        $prependStrictTypes = $this->strictTypes;

        foreach ($nodes as $key => $node) {
            if ($node instanceof Declare_) {
                foreach ($node->declares as $declare) {
                    if ($declare->key === 'strict_types') {
                        $value = $declare->value;
                        if (!$value instanceof LNumber || ($value->value !== 0 && $value->value !== 1)) {
                            throw new FatalErrorException(self::EXCEPTION_MESSAGE, 0, E_ERROR, null, $node->getLine());
                        }

                        $this->strictTypes = $value->value === 1;
                    }
                }
            }
        }

        if ($prependStrictTypes) {
            $first = reset($nodes);
            if (!$first instanceof Declare_) {
                $declare = new Declare_(array(new DeclareDeclare('strict_types', new LNumber(1))));
                array_unshift($nodes, $declare);
            }
        }

        return $nodes;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;

/**
 * Provide implicit use statements for subsequent execution.
 *
 * The use statement pass remembers the last use statement line encountered:
 *
 *     use Foo\Bar as Baz;
 *
 * ... which it then applies implicitly to all future evaluated code, until the
 * current namespace is replaced by another namespace.
 */
class UseStatementPass extends CodeCleanerPass
{
    private $aliases       = array();
    private $lastAliases   = array();
    private $lastNamespace = null;

    /**
     * Re-load the last set of use statements on re-entering a namespace.
     *
     * This isn't how namespaces normally work, but because PsySH has to spin
     * up a new namespace for every line of code, we do this to make things
     * work like you'd expect.
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof Namespace_) {
            // If this is the same namespace as last namespace, let's do ourselves
            // a favor and reload all the aliases...
            if (strtolower($node->name) === strtolower($this->lastNamespace)) {
                $this->aliases = $this->lastAliases;
            }
        }
    }

    /**
     * If this statement is a namespace, forget all the aliases we had.
     *
     * If it's a use statement, remember the alias for later. Otherwise, apply
     * remembered aliases to the code.
     *
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof Use_) {
            // Store a reference to every "use" statement, because we'll need
            // them in a bit.
            foreach ($node->uses as $use) {
                $this->aliases[strtolower($use->alias)] = $use->name;
            }

            return false;
        } elseif ($node instanceof GroupUse) {
            // Expand every "use" statement in the group into a full, standalone
            // "use" and store 'em with the others.
            foreach ($node->uses as $use) {
                $this->aliases[strtolower($use->alias)] = Name::concat($node->prefix, $use->name, array(
                    'startLine' => $node->prefix->getAttribute('startLine'),
                    'endLine'   => $use->name->getAttribute('endLine'),
                ));
            }

            return false;
        } elseif ($node instanceof Namespace_) {
            // Start fresh, since we're done with this namespace.
            $this->lastNamespace = $node->name;
            $this->lastAliases   = $this->aliases;
            $this->aliases       = array();
        } else {
            foreach ($node as $name => $subNode) {
                if ($subNode instanceof Name) {
                    // Implicitly thunk all aliases.
                    if ($replacement = $this->findAlias($subNode)) {
                        $node->$name = $replacement;
                    }
                }
            }

            return $node;
        }
    }

    /**
     * Find class/namespace aliases.
     *
     * @param Name $name
     *
     * @return FullyQualifiedName|null
     */
    private function findAlias(Name $name)
    {
        $that = strtolower($name);
        foreach ($this->aliases as $alias => $prefix) {
            if ($that === $alias) {
                return new FullyQualifiedName($prefix->toString());
            } elseif (substr($that, 0, strlen($alias) + 1) === $alias . '\\') {
                return new FullyQualifiedName($prefix->toString() . substr($name, strlen($alias)));
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\While_;
use Psy\Exception\FatalErrorException;

/**
 * Validate that classes exist.
 *
 * This pass throws a FatalErrorException rather than letting PHP run
 * headfirst into a real fatal error and die.
 */
class ValidClassNamePass extends NamespaceAwarePass
{
    const CLASS_TYPE     = 'class';
    const INTERFACE_TYPE = 'interface';
    const TRAIT_TYPE     = 'trait';

    protected $checkTraits;
    private $conditionalScopes = 0;

    public function __construct()
    {
        $this->checkTraits = function_exists('trait_exists');
    }

    /**
     * Validate class, interface and trait definitions.
     *
     * Validate them upon entering the node, so that we know about their
     * presence and can validate constant fetches and static calls in class or
     * trait methods.
     *
     * @param Node
     */
    public function enterNode(Node $node)
    {
        parent::enterNode($node);

        if (self::isConditional($node)) {
            $this->conditionalScopes++;
        } else {
            // @todo add an "else" here which adds a runtime check for instances where we can't tell
            // whether a class is being redefined by static analysis alone.
            if ($this->conditionalScopes === 0) {
                if ($node instanceof Class_) {
                    $this->validateClassStatement($node);
                } elseif ($node instanceof Interface_) {
                    $this->validateInterfaceStatement($node);
                } elseif ($node instanceof Trait_) {
                    $this->validateTraitStatement($node);
                }
            }
        }
    }

    /**
     * Validate `new` expressions, class constant fetches, and static calls.
     *
     * @throws FatalErrorException if a class, interface or trait is referenced which does not exist
     * @throws FatalErrorException if a class extends something that is not a class
     * @throws FatalErrorException if a class implements something that is not an interface
     * @throws FatalErrorException if an interface extends something that is not an interface
     * @throws FatalErrorException if a class, interface or trait redefines an existing class, interface or trait name
     *
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if (self::isConditional($node)) {
            $this->conditionalScopes--;
        } elseif ($node instanceof New_) {
            $this->validateNewExpression($node);
        } elseif ($node instanceof ClassConstFetch) {
            $this->validateClassConstFetchExpression($node);
        } elseif ($node instanceof StaticCall) {
            $this->validateStaticCallExpression($node);
        }
    }

    private static function isConditional(Node $node)
    {
        return $node instanceof If_ ||
            $node instanceof While_ ||
            $node instanceof Do_ ||
            $node instanceof Switch_;
    }

    /**
     * Validate a class definition statement.
     *
     * @param Class_ $stmt
     */
    protected function validateClassStatement(Class_ $stmt)
    {
        $this->ensureCanDefine($stmt);
        if (isset($stmt->extends)) {
            $this->ensureClassExists($this->getFullyQualifiedName($stmt->extends), $stmt);
        }
        $this->ensureInterfacesExist($stmt->implements, $stmt);
    }

    /**
     * Validate an interface definition statement.
     *
     * @param Interface_ $stmt
     */
    protected function validateInterfaceStatement(Interface_ $stmt)
    {
        $this->ensureCanDefine($stmt);
        $this->ensureInterfacesExist($stmt->extends, $stmt);
    }

    /**
     * Validate a trait definition statement.
     *
     * @param Trait_ $stmt
     */
    protected function validateTraitStatement(Trait_ $stmt)
    {
        $this->ensureCanDefine($stmt);
    }

    /**
     * Validate a `new` expression.
     *
     * @param New_ $stmt
     */
    protected function validateNewExpression(New_ $stmt)
    {
        // if class name is an expression or an anonymous class, give it a pass for now
        if (!$stmt->class instanceof Expr && !$stmt->class instanceof Class_) {
            $this->ensureClassExists($this->getFullyQualifiedName($stmt->class), $stmt);
        }
    }

    /**
     * Validate a class constant fetch expression's class.
     *
     * @param ClassConstFetch $stmt
     */
    protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
    {
        // there is no need to check exists for ::class const for php 5.5 or newer
        if (strtolower($stmt->name) === 'class'
            && version_compare(PHP_VERSION, '5.5', '>=')) {
            return;
        }

        // if class name is an expression, give it a pass for now
        if (!$stmt->class instanceof Expr) {
            $this->ensureClassOrInterfaceExists($this->getFullyQualifiedName($stmt->class), $stmt);
        }
    }

    /**
     * Validate a class constant fetch expression's class.
     *
     * @param StaticCall $stmt
     */
    protected function validateStaticCallExpression(StaticCall $stmt)
    {
        // if class name is an expression, give it a pass for now
        if (!$stmt->class instanceof Expr) {
            $this->ensureMethodExists($this->getFullyQualifiedName($stmt->class), $stmt->name, $stmt);
        }
    }

    /**
     * Ensure that no class, interface or trait name collides with a new definition.
     *
     * @throws FatalErrorException
     *
     * @param Stmt $stmt
     */
    protected function ensureCanDefine(Stmt $stmt)
    {
        $name = $this->getFullyQualifiedName($stmt->name);

        // check for name collisions
        $errorType = null;
        if ($this->classExists($name)) {
            $errorType = self::CLASS_TYPE;
        } elseif ($this->interfaceExists($name)) {
            $errorType = self::INTERFACE_TYPE;
        } elseif ($this->traitExists($name)) {
            $errorType = self::TRAIT_TYPE;
        }

        if ($errorType !== null) {
            throw $this->createError(sprintf('%s named %s already exists', ucfirst($errorType), $name), $stmt);
        }

        // Store creation for the rest of this code snippet so we can find local
        // issue too
        $this->currentScope[strtolower($name)] = $this->getScopeType($stmt);
    }

    /**
     * Ensure that a referenced class exists.
     *
     * @throws FatalErrorException
     *
     * @param string $name
     * @param Stmt   $stmt
     */
    protected function ensureClassExists($name, $stmt)
    {
        if (!$this->classExists($name)) {
            throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
        }
    }

    /**
     * Ensure that a referenced class _or interface_ exists.
     *
     * @throws FatalErrorException
     *
     * @param string $name
     * @param Stmt   $stmt
     */
    protected function ensureClassOrInterfaceExists($name, $stmt)
    {
        if (!$this->classExists($name) && !$this->interfaceExists($name)) {
            throw $this->createError(sprintf('Class \'%s\' not found', $name), $stmt);
        }
    }

    /**
     * Ensure that a statically called method exists.
     *
     * @throws FatalErrorException
     *
     * @param string $class
     * @param string $name
     * @param Stmt   $stmt
     */
    protected function ensureMethodExists($class, $name, $stmt)
    {
        $this->ensureClassExists($class, $stmt);

        // let's pretend all calls to self, parent and static are valid
        if (in_array(strtolower($class), array('self', 'parent', 'static'))) {
            return;
        }

        // ... and all calls to classes defined right now
        if ($this->findInScope($class) === self::CLASS_TYPE) {
            return;
        }

        // if method name is an expression, give it a pass for now
        if ($name instanceof Expr) {
            return;
        }

        if (!method_exists($class, $name) && !method_exists($class, '__callStatic')) {
            throw $this->createError(sprintf('Call to undefined method %s::%s()', $class, $name), $stmt);
        }
    }

    /**
     * Ensure that a referenced interface exists.
     *
     * @throws FatalErrorException
     *
     * @param $interfaces
     * @param Stmt $stmt
     */
    protected function ensureInterfacesExist($interfaces, $stmt)
    {
        foreach ($interfaces as $interface) {
            /** @var string $name */
            $name = $this->getFullyQualifiedName($interface);
            if (!$this->interfaceExists($name)) {
                throw $this->createError(sprintf('Interface \'%s\' not found', $name), $stmt);
            }
        }
    }

    /**
     * Get a symbol type key for storing in the scope name cache.
     *
     * @param Stmt $stmt
     *
     * @return string
     */
    protected function getScopeType(Stmt $stmt)
    {
        if ($stmt instanceof Class_) {
            return self::CLASS_TYPE;
        } elseif ($stmt instanceof Interface_) {
            return self::INTERFACE_TYPE;
        } elseif ($stmt instanceof Trait_) {
            return self::TRAIT_TYPE;
        }
    }

    /**
     * Check whether a class exists, or has been defined in the current code snippet.
     *
     * Gives `self`, `static` and `parent` a free pass.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function classExists($name)
    {
        // Give `self`, `static` and `parent` a pass. This will actually let
        // some errors through, since we're not checking whether the keyword is
        // being used in a class scope.
        if (in_array(strtolower($name), array('self', 'static', 'parent'))) {
            return true;
        }

        return class_exists($name) || $this->findInScope($name) === self::CLASS_TYPE;
    }

    /**
     * Check whether an interface exists, or has been defined in the current code snippet.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function interfaceExists($name)
    {
        return interface_exists($name) || $this->findInScope($name) === self::INTERFACE_TYPE;
    }

    /**
     * Check whether a trait exists, or has been defined in the current code snippet.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function traitExists($name)
    {
        return $this->checkTraits && (trait_exists($name) || $this->findInScope($name) === self::TRAIT_TYPE);
    }

    /**
     * Find a symbol in the current code snippet scope.
     *
     * @param string $name
     *
     * @return string|null
     */
    protected function findInScope($name)
    {
        $name = strtolower($name);
        if (isset($this->currentScope[$name])) {
            return $this->currentScope[$name];
        }
    }

    /**
     * Error creation factory.
     *
     * @param string $msg
     * @param Stmt   $stmt
     *
     * @return FatalErrorException
     */
    protected function createError($msg, $stmt)
    {
        return new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use Psy\Exception\FatalErrorException;

/**
 * Validate that namespaced constant references will succeed.
 *
 * This pass throws a FatalErrorException rather than letting PHP run
 * headfirst into a real fatal error and die.
 *
 * @todo Detect constants defined in the current code snippet?
 *       ... Might not be worth it, since it would need to both be defining and
 *       referencing a namespaced constant, which doesn't seem like that big of
 *       a target for failure
 */
class ValidConstantPass extends NamespaceAwarePass
{
    /**
     * Validate that namespaced constant references will succeed.
     *
     * Note that this does not (yet) detect constants defined in the current code
     * snippet. It won't happen very often, so we'll punt for now.
     *
     * @throws FatalErrorException if a constant reference is not defined
     *
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if ($node instanceof ConstFetch && count($node->name->parts) > 1) {
            $name = $this->getFullyQualifiedName($node->name);
            if (!defined($name)) {
                $msg = sprintf('Undefined constant %s', $name);
                throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
            }
        } elseif ($node instanceof ClassConstFetch) {
            $this->validateClassConstFetchExpression($node);
        }
    }

    /**
     * Validate a class constant fetch expression.
     *
     * @throws FatalErrorException if a class constant is not defined
     *
     * @param ClassConstFetch $stmt
     */
    protected function validateClassConstFetchExpression(ClassConstFetch $stmt)
    {
        // give the `class` pseudo-constant a pass
        if ($stmt->name === 'class') {
            return;
        }

        // if class name is an expression, give it a pass for now
        if (!$stmt->class instanceof Expr) {
            $className = $this->getFullyQualifiedName($stmt->class);

            // if the class doesn't exist, don't throw an exception… it might be
            // defined in the same line it's used or something stupid like that.
            if (class_exists($className) || interface_exists($className)) {
                $refl = new \ReflectionClass($className);
                if (!$refl->hasConstant($stmt->name)) {
                    $constType = class_exists($className) ? 'Class' : 'Interface';
                    $msg = sprintf('%s constant \'%s::%s\' not found', $constType, $className, $stmt->name);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $stmt->getLine());
                }
            }
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\CodeCleaner;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\Switch_;
use PhpParser\Node\Stmt\While_;
use Psy\Exception\FatalErrorException;

/**
 * Validate that function calls will succeed.
 *
 * This pass throws a FatalErrorException rather than letting PHP run
 * headfirst into a real fatal error and die.
 */
class ValidFunctionNamePass extends NamespaceAwarePass
{
    private $conditionalScopes = 0;

    /**
     * Store newly defined function names on the way in, to allow recursion.
     *
     * @param Node $node
     */
    public function enterNode(Node $node)
    {
        parent::enterNode($node);

        if (self::isConditional($node)) {
            $this->conditionalScopes++;
        } elseif ($node instanceof Function_) {
            $name = $this->getFullyQualifiedName($node->name);

            // @todo add an "else" here which adds a runtime check for instances where we can't tell
            // whether a function is being redefined by static analysis alone.
            if ($this->conditionalScopes === 0) {
                if (function_exists($name) ||
                    isset($this->currentScope[strtolower($name)])) {
                    $msg = sprintf('Cannot redeclare %s()', $name);
                    throw new FatalErrorException($msg, 0, E_ERROR, null, $node->getLine());
                }
            }

            $this->currentScope[strtolower($name)] = true;
        }
    }

    /**
     * Validate that function calls will succeed.
     *
     * @throws FatalErrorException if a function is redefined
     * @throws FatalErrorException if the function name is a string (not an expression) and is not defined
     *
     * @param Node $node
     */
    public function leaveNode(Node $node)
    {
        if (self::isConditional($node)) {
            $this->conditionalScopes--;
        } elseif ($node instanceof FuncCall) {
            // if function name is an expression or a variable, give it a pass for now.
            $name = $node->name;
            if (!$name instanceof Expr && !$name instanceof Variable) {
                $shortName = implode('\\', $name->parts);
                $fullName  = $this->getFullyQualifiedName($name);
                $inScope = isset($this->currentScope[strtolower($fullName)]);
                if (!$inScope && !function_exists($shortName) && !function_exists($fullName)) {
                    $message = sprintf('Call to undefined function %s()', $name);
                    throw new FatalErrorException($message, 0, E_ERROR, null, $node->getLine());
                }
            }
        }
    }

    private static function isConditional(Node $node)
    {
        return $node instanceof If_ ||
            $node instanceof While_ ||
            $node instanceof Do_ ||
            $node instanceof Switch_;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Interact with the current code buffer.
 *
 * Shows and clears the buffer for the current multi-line expression.
 */
class BufferCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('buffer')
            ->setAliases(array('buf'))
            ->setDefinition(array(
                new InputOption('clear', '', InputOption::VALUE_NONE, 'Clear the current buffer.'),
            ))
            ->setDescription('Show (or clear) the contents of the code input buffer.')
            ->setHelp(
                <<<'HELP'
Show the contents of the code buffer for the current multi-line expression.

Optionally, clear the buffer by passing the <info>--clear</info> option.
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $buf = $this->getApplication()->getCodeBuffer();
        if ($input->getOption('clear')) {
            $this->getApplication()->resetCodeBuffer();
            $output->writeln($this->formatLines($buf, 'urgent'), ShellOutput::NUMBER_LINES);
        } else {
            $output->writeln($this->formatLines($buf), ShellOutput::NUMBER_LINES);
        }
    }

    /**
     * A helper method for wrapping buffer lines in `<urgent>` and `<return>` formatter strings.
     *
     * @param array  $lines
     * @param string $type  (default: 'return')
     *
     * @return array Formatted strings
     */
    protected function formatLines(array $lines, $type = 'return')
    {
        $template = sprintf('<%s>%%s</%s>', $type, $type);

        return array_map(function ($line) use ($template) {
            return sprintf($template, $line);
        }, $lines);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Clear the Psy Shell.
 *
 * Just what it says on the tin.
 */
class ClearCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('clear')
            ->setDefinition(array())
            ->setDescription('Clear the Psy Shell screen.')
            ->setHelp(
                <<<'HELP'
Clear the Psy Shell screen.

Pro Tip: If your PHP has readline support, you should be able to use ctrl+l too!
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->write(sprintf('%c[2J%c[0;0f', 27, 27));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Shell;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Helper\TableStyle;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * The Psy Shell base command.
 */
abstract class Command extends BaseCommand
{
    /**
     * Sets the application instance for this command.
     *
     * @param Application $application An Application instance
     *
     * @api
     */
    public function setApplication(Application $application = null)
    {
        if ($application !== null && !$application instanceof Shell) {
            throw new \InvalidArgumentException('PsySH Commands require an instance of Psy\Shell.');
        }

        return parent::setApplication($application);
    }

    /**
     * {@inheritdoc}
     */
    public function asText()
    {
        $messages = array(
            '<comment>Usage:</comment>',
            ' ' . $this->getSynopsis(),
            '',
        );

        if ($this->getAliases()) {
            $messages[] = $this->aliasesAsText();
        }

        if ($this->getArguments()) {
            $messages[] = $this->argumentsAsText();
        }

        if ($this->getOptions()) {
            $messages[] = $this->optionsAsText();
        }

        if ($help = $this->getProcessedHelp()) {
            $messages[] = '<comment>Help:</comment>';
            $messages[] = ' ' . str_replace("\n", "\n ", $help) . "\n";
        }

        return implode("\n", $messages);
    }

    /**
     * {@inheritdoc}
     */
    private function getArguments()
    {
        $hidden = $this->getHiddenArguments();

        return array_filter($this->getNativeDefinition()->getArguments(), function ($argument) use ($hidden) {
            return !in_array($argument->getName(), $hidden);
        });
    }

    /**
     * These arguments will be excluded from help output.
     *
     * @return array
     */
    protected function getHiddenArguments()
    {
        return array('command');
    }

    /**
     * {@inheritdoc}
     */
    private function getOptions()
    {
        $hidden = $this->getHiddenOptions();

        return array_filter($this->getNativeDefinition()->getOptions(), function ($option) use ($hidden) {
            return !in_array($option->getName(), $hidden);
        });
    }

    /**
     * These options will be excluded from help output.
     *
     * @return array
     */
    protected function getHiddenOptions()
    {
        return array('verbose');
    }

    /**
     * Format command aliases as text..
     *
     * @return string
     */
    private function aliasesAsText()
    {
        return '<comment>Aliases:</comment> <info>' . implode(', ', $this->getAliases()) . '</info>' . PHP_EOL;
    }

    /**
     * Format command arguments as text.
     *
     * @return string
     */
    private function argumentsAsText()
    {
        $max = $this->getMaxWidth();
        $messages = array();

        $arguments = $this->getArguments();
        if (!empty($arguments)) {
            $messages[] = '<comment>Arguments:</comment>';
            foreach ($arguments as $argument) {
                if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
                    $default = sprintf('<comment> (default: %s)</comment>', $this->formatDefaultValue($argument->getDefault()));
                } else {
                    $default = '';
                }

                $description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $argument->getDescription());

                $messages[] = sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $description, $default);
            }

            $messages[] = '';
        }

        return implode(PHP_EOL, $messages);
    }

    /**
     * Format options as text.
     *
     * @return string
     */
    private function optionsAsText()
    {
        $max = $this->getMaxWidth();
        $messages = array();

        $options = $this->getOptions();
        if ($options) {
            $messages[] = '<comment>Options:</comment>';

            foreach ($options as $option) {
                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 = '';
                }

                $multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '';
                $description = str_replace("\n", "\n" . str_pad('', $max + 2, ' '), $option->getDescription());

                $optionMax = $max - strlen($option->getName()) - 2;
                $messages[] = sprintf(
                    " <info>%s</info> %-${optionMax}s%s%s%s",
                    '--' . $option->getName(),
                    $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
                    $description,
                    $default,
                    $multiple
                );
            }

            $messages[] = '';
        }

        return implode(PHP_EOL, $messages);
    }

    /**
     * Calculate the maximum padding width for a set of lines.
     *
     * @return int
     */
    private function getMaxWidth()
    {
        $max = 0;

        foreach ($this->getOptions() as $option) {
            $nameLength = strlen($option->getName()) + 2;
            if ($option->getShortcut()) {
                $nameLength += strlen($option->getShortcut()) + 3;
            }

            $max = max($max, $nameLength);
        }

        foreach ($this->getArguments() as $argument) {
            $max = max($max, strlen($argument->getName()));
        }

        return ++$max;
    }

    /**
     * Format an option default as text.
     *
     * @param mixed $default
     *
     * @return string
     */
    private function formatDefaultValue($default)
    {
        if (is_array($default) && $default === array_values($default)) {
            return sprintf("array('%s')", implode("', '", $default));
        }

        return str_replace("\n", '', var_export($default, true));
    }

    /**
     * Get a Table instance.
     *
     * Falls back to legacy TableHelper.
     *
     * @return Table|TableHelper
     */
    protected function getTable(OutputInterface $output)
    {
        if (!class_exists('Symfony\Component\Console\Helper\Table')) {
            return $this->getTableHelper();
        }

        $style = new TableStyle();
        $style
            ->setVerticalBorderChar(' ')
            ->setHorizontalBorderChar('')
            ->setCrossingChar('');

        $table = new Table($output);

        return $table
            ->setRows(array())
            ->setStyle($style);
    }

    /**
     * Legacy fallback for getTable.
     *
     * @return TableHelper
     */
    protected function getTableHelper()
    {
        $table = $this->getApplication()->getHelperSet()->get('table');

        return $table
            ->setRows(array())
            ->setLayout(TableHelper::LAYOUT_BORDERLESS)
            ->setHorizontalBorderChar('')
            ->setCrossingChar('');
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Formatter\DocblockFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Reflection\ReflectionLanguageConstruct;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Read the documentation for an object, class, constant, method or property.
 */
class DocCommand extends ReflectingCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('doc')
            ->setAliases(array('rtfm', 'man'))
            ->setDefinition(array(
                new InputArgument('value', InputArgument::REQUIRED, 'Function, class, instance, constant, method or property to document.'),
            ))
            ->setDescription('Read the documentation for an object, class, constant, method or property.')
            ->setHelp(
                <<<HELP
Read the documentation for an object, class, constant, method or property.

It's awesome for well-documented code, not quite as awesome for poorly documented code.

e.g.
<return>>>> doc preg_replace</return>
<return>>>> doc Psy\Shell</return>
<return>>>> doc Psy\Shell::debug</return>
<return>>>> \$s = new Psy\Shell</return>
<return>>>> doc \$s->run</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $value = $input->getArgument('value');
        if (ReflectionLanguageConstruct::isLanguageConstruct($value)) {
            $reflector = new ReflectionLanguageConstruct($value);
            $doc = $this->getManualDocById($value);
        } else {
            list($target, $reflector) = $this->getTargetAndReflector($value);
            $doc = $this->getManualDoc($reflector) ?: DocblockFormatter::format($reflector);
        }

        $db = $this->getApplication()->getManualDb();

        $output->page(function ($output) use ($reflector, $doc, $db) {
            $output->writeln(SignatureFormatter::format($reflector));
            $output->writeln('');

            if (empty($doc) && !$db) {
                $output->writeln('<warning>PHP manual not found</warning>');
                $output->writeln('    To document core PHP functionality, download the PHP reference manual:');
                $output->writeln('    https://github.com/bobthecow/psysh/wiki/PHP-manual');
            } else {
                $output->writeln($doc);
            }
        });

        // Set some magic local variables
        $this->setCommandScopeVariables($reflector);
    }

    private function getManualDoc($reflector)
    {
        switch (get_class($reflector)) {
            case 'ReflectionClass':
            case 'ReflectionObject':
            case 'ReflectionFunction':
                $id = $reflector->name;
                break;

            case 'ReflectionMethod':
                $id = $reflector->class . '::' . $reflector->name;
                break;

            case 'ReflectionProperty':
                $id = $reflector->class . '::$' . $reflector->name;
                break;

            default:
                return false;
        }

        return $this->getManualDocById($id);
    }

    private function getManualDocById($id)
    {
        if ($db = $this->getApplication()->getManualDb()) {
            return $db
                ->query(sprintf('SELECT doc FROM php_manual WHERE id = %s', $db->quote($id)))
                ->fetchColumn(0);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Exception\RuntimeException;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Dump an object or primitive.
 *
 * This is like var_dump but *way* awesomer.
 */
class DumpCommand extends ReflectingCommand implements PresenterAware
{
    private $presenter;

    /**
     * PresenterAware interface.
     *
     * @param Presenter $presenter
     */
    public function setPresenter(Presenter $presenter)
    {
        $this->presenter = $presenter;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('dump')
            ->setDefinition(array(
                new InputArgument('target', InputArgument::REQUIRED, 'A target object or primitive to dump.', null),
                new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
            ))
            ->setDescription('Dump an object or primitive.')
            ->setHelp(
                <<<'HELP'
Dump an object or primitive.

This is like var_dump but <strong>way</strong> awesomer.

e.g.
<return>>>> dump $_</return>
<return>>>> dump $someVar</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $depth  = $input->getOption('depth');
        $target = $this->resolveTarget($input->getArgument('target'));
        $output->page($this->presenter->present($target, $depth, $input->getOption('all') ? Presenter::VERBOSE : 0));

        if (is_object($target)) {
            $this->setCommandScopeVariables(new \ReflectionObject($target));
        }
    }

    /**
     * Resolve dump target name.
     *
     * @throws RuntimeException if target name does not exist in the current scope
     *
     * @param string $target
     *
     * @return mixed
     */
    protected function resolveTarget($target)
    {
        $matches = array();
        if (preg_match(self::SUPERGLOBAL, $target, $matches)) {
            if (!array_key_exists($matches[1], $GLOBALS)) {
                throw new RuntimeException('Unknown target: ' . $target);
            }

            return $GLOBALS[$matches[1]];
        } elseif (preg_match(self::INSTANCE, $target, $matches)) {
            return $this->getScopeVariable($matches[1]);
        } else {
            throw new RuntimeException('Unknown target: ' . $target);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Context;
use Psy\ContextAware;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class EditCommand extends Command implements ContextAware
{
    /**
     * @var string
     */
    private $runtimeDir = '';

    /**
     * @var Context
     */
    private $context;

    /**
     * Constructor.
     *
     * @param string      $runtimeDir The directory to use for temporary files
     * @param string|null $name       The name of the command; passing null means it must be set in configure()
     *
     * @throws \Symfony\Component\Console\Exception\LogicException When the command name is empty
     */
    public function __construct($runtimeDir, $name = null)
    {
        parent::__construct($name);

        $this->runtimeDir = $runtimeDir;
    }

    protected function configure()
    {
        $this
            ->setName('edit')
            ->setDefinition(array(
                new InputArgument('file', InputArgument::OPTIONAL, 'The file to open for editing. If this is not given, edits a temporary file.', null),
                new InputOption(
                    'exec',
                    'e',
                    InputOption::VALUE_NONE,
                    'Execute the file content after editing. This is the default when a file name argument is not given.',
                    null
                ),
                new InputOption(
                    'no-exec',
                    'E',
                    InputOption::VALUE_NONE,
                    'Do not execute the file content after editing. This is the default when a file name argument is given.',
                    null
                ),
            ))
            ->setDescription('Open an external editor. Afterwards, get produced code in input buffer.')
            ->setHelp('Set the EDITOR environment variable to something you\'d like to use.');
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @throws \InvalidArgumentException when both exec and no-exec flags are given or if a given variable is not found in the current context
     * @throws \UnexpectedValueException if file_get_contents on the edited file returns false instead of a string
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if ($input->getOption('exec') &&
            $input->getOption('no-exec')) {
            throw new \InvalidArgumentException('The --exec and --no-exec flags are mutually exclusive.');
        }

        $filePath = $this->extractFilePath($input->getArgument('file'));

        $execute = $this->shouldExecuteFile(
            $input->getOption('exec'),
            $input->getOption('no-exec'),
            $filePath
        );

        $shouldRemoveFile = false;

        if ($filePath === null) {
            $filePath = tempnam($this->runtimeDir, 'psysh-edit-command');
            $shouldRemoveFile = true;
        }

        $editedContent = $this->editFile($filePath, $shouldRemoveFile);

        if ($execute) {
            $this->getApplication()->addInput($editedContent);
        }
    }

    /**
     * @param bool        $execOption
     * @param bool        $noExecOption
     * @param string|null $filePath
     *
     * @return bool
     */
    private function shouldExecuteFile($execOption, $noExecOption, $filePath)
    {
        if ($execOption) {
            return true;
        }

        if ($noExecOption) {
            return false;
        }

        // By default, code that is edited is executed if there was no given input file path
        return $filePath === null;
    }

    /**
     * @param string|null $fileArgument
     *
     * @return string|null The file path to edit, null if the input was null, or the value of the referenced variable
     *
     * @throws \InvalidArgumentException If the variable is not found in the current context
     */
    private function extractFilePath($fileArgument)
    {
        // If the file argument was a variable, get it from the context
        if ($fileArgument !== null &&
            strlen($fileArgument) > 0 &&
            $fileArgument[0] === '$') {
            $fileArgument = $this->context->get(preg_replace('/^\$/', '', $fileArgument));
        }

        return $fileArgument;
    }

    /**
     * @param string $filePath
     * @param string $shouldRemoveFile
     *
     * @return string
     *
     * @throws \UnexpectedValueException if file_get_contents on $filePath returns false instead of a string
     */
    private function editFile($filePath, $shouldRemoveFile)
    {
        $escapedFilePath = escapeshellarg($filePath);

        $pipes = array();
        $proc = proc_open((getenv('EDITOR') ?: 'nano') . " {$escapedFilePath}", array(STDIN, STDOUT, STDERR), $pipes);
        proc_close($proc);

        $editedContent = @file_get_contents($filePath);

        if ($shouldRemoveFile) {
            @unlink($filePath);
        }

        if ($editedContent === false) {
            throw new \UnexpectedValueException("Reading {$filePath} returned false");
        }

        return $editedContent;
    }

    /**
     * Set the Context reference.
     *
     * @param Context $context
     */
    public function setContext(Context $context)
    {
        $this->context = $context;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Exception\BreakException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Exit the Psy Shell.
 *
 * Just what it says on the tin.
 */
class ExitCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('exit')
            ->setAliases(array('quit', 'q'))
            ->setDefinition(array())
            ->setDescription('End the current session and return to caller.')
            ->setHelp(
                <<<'HELP'
End the current session and return to caller.

e.g.
<return>>>> exit</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new BreakException('Goodbye.');
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Help command.
 *
 * Lists available commands, and gives command-specific help when asked nicely.
 */
class HelpCommand extends Command
{
    private $command;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('help')
            ->setAliases(array('?'))
            ->setDefinition(array(
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', null),
            ))
            ->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].')
            ->setHelp('My. How meta.');
    }

    /**
     * Helper for setting a subcommand to retrieve help for.
     *
     * @param Command $command
     */
    public function setCommand($command)
    {
        $this->command = $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if ($this->command !== null) {
            // help for an individual command
            $output->page($this->command->asText());
            $this->command = null;
        } elseif ($name = $input->getArgument('command_name')) {
            // help for an individual command
            $output->page($this->getApplication()->get($name)->asText());
        } else {
            // list available commands
            $commands = $this->getApplication()->all();

            $table = $this->getTable($output);

            foreach ($commands as $name => $command) {
                if ($name !== $command->getName()) {
                    continue;
                }

                if ($command->getAliases()) {
                    $aliases = sprintf('<comment>Aliases:</comment> %s', implode(', ', $command->getAliases()));
                } else {
                    $aliases = '';
                }

                $table->addRow(array(
                    sprintf('<info>%s</info>', $name),
                    $command->getDescription(),
                    $aliases,
                ));
            }

            $output->startPaging();
            if ($table instanceof TableHelper) {
                $table->render($output);
            } else {
                $table->render();
            }
            $output->stopPaging();
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Input\FilterOptions;
use Psy\Output\ShellOutput;
use Psy\Readline\Readline;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Psy Shell history command.
 *
 * Shows, searches and replays readline history. Not too shabby.
 */
class HistoryCommand extends Command
{
    private $filter;

    /**
     * {@inheritdoc}
     */
    public function __construct($name = null)
    {
        $this->filter = new FilterOptions();

        parent::__construct($name);
    }

    /**
     * Set the Shell's Readline service.
     *
     * @param Readline $readline
     */
    public function setReadline(Readline $readline)
    {
        $this->readline = $readline;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        list($grep, $insensitive, $invert) = FilterOptions::getOptions();

        $this
            ->setName('history')
            ->setAliases(array('hist'))
            ->setDefinition(array(
                new InputOption('show',        's', InputOption::VALUE_REQUIRED, 'Show the given range of lines'),
                new InputOption('head',        'H', InputOption::VALUE_REQUIRED, 'Display the first N items.'),
                new InputOption('tail',        'T', InputOption::VALUE_REQUIRED, 'Display the last N items.'),

                $grep,
                $insensitive,
                $invert,

                new InputOption('no-numbers',  'N', InputOption::VALUE_NONE,     'Omit line numbers.'),

                new InputOption('save',        '',  InputOption::VALUE_REQUIRED, 'Save history to a file.'),
                new InputOption('replay',      '',  InputOption::VALUE_NONE,     'Replay'),
                new InputOption('clear',       '',  InputOption::VALUE_NONE,     'Clear the history.'),
            ))
            ->setDescription('Show the Psy Shell history.')
            ->setHelp(
                <<<'HELP'
Show, search, save or replay the Psy Shell history.

e.g.
<return>>>> history --grep /[bB]acon/</return>
<return>>>> history --show 0..10 --replay</return>
<return>>>> history --clear</return>
<return>>>> history --tail 1000 --save somefile.txt</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->validateOnlyOne($input, array('show', 'head', 'tail'));
        $this->validateOnlyOne($input, array('save', 'replay', 'clear'));

        $history = $this->getHistorySlice(
            $input->getOption('show'),
            $input->getOption('head'),
            $input->getOption('tail')
        );
        $highlighted = false;

        $this->filter->bind($input);
        if ($this->filter->hasFilter()) {
            $matches     = array();
            $highlighted = array();
            foreach ($history as $i => $line) {
                if ($this->filter->match($line, $matches)) {
                    if (isset($matches[0])) {
                        $chunks = explode($matches[0], $history[$i]);
                        $chunks = array_map(array(__CLASS__, 'escape'), $chunks);
                        $glue   = sprintf('<urgent>%s</urgent>', self::escape($matches[0]));

                        $highlighted[$i] = implode($glue, $chunks);
                    }
                } else {
                    unset($history[$i]);
                }
            }
        }

        if ($save = $input->getOption('save')) {
            $output->writeln(sprintf('Saving history in %s...', $save));
            file_put_contents($save, implode(PHP_EOL, $history) . PHP_EOL);
            $output->writeln('<info>History saved.</info>');
        } elseif ($input->getOption('replay')) {
            if (!($input->getOption('show') || $input->getOption('head') || $input->getOption('tail'))) {
                throw new \InvalidArgumentException('You must limit history via --head, --tail or --show before replaying.');
            }

            $count = count($history);
            $output->writeln(sprintf('Replaying %d line%s of history', $count, ($count !== 1) ? 's' : ''));
            $this->getApplication()->addInput($history);
        } elseif ($input->getOption('clear')) {
            $this->clearHistory();
            $output->writeln('<info>History cleared.</info>');
        } else {
            $type = $input->getOption('no-numbers') ? 0 : ShellOutput::NUMBER_LINES;
            if (!$highlighted) {
                $type = $type | ShellOutput::OUTPUT_RAW;
            }

            $output->page($highlighted ?: $history, $type);
        }
    }

    /**
     * Extract a range from a string.
     *
     * @param string $range
     *
     * @return array [ start, end ]
     */
    private function extractRange($range)
    {
        if (preg_match('/^\d+$/', $range)) {
            return array($range, $range + 1);
        }

        $matches = array();
        if ($range !== '..' && preg_match('/^(\d*)\.\.(\d*)$/', $range, $matches)) {
            $start = $matches[1] ? intval($matches[1]) : 0;
            $end   = $matches[2] ? intval($matches[2]) + 1 : PHP_INT_MAX;

            return array($start, $end);
        }

        throw new \InvalidArgumentException('Unexpected range: ' . $range);
    }

    /**
     * Retrieve a slice of the readline history.
     *
     * @param string $show
     * @param string $head
     * @param string $tail
     *
     * @return array A slilce of history
     */
    private function getHistorySlice($show, $head, $tail)
    {
        $history = $this->readline->listHistory();

        // don't show the current `history` invocation
        array_pop($history);

        if ($show) {
            list($start, $end) = $this->extractRange($show);
            $length = $end - $start;
        } elseif ($head) {
            if (!preg_match('/^\d+$/', $head)) {
                throw new \InvalidArgumentException('Please specify an integer argument for --head.');
            }

            $start  = 0;
            $length = intval($head);
        } elseif ($tail) {
            if (!preg_match('/^\d+$/', $tail)) {
                throw new \InvalidArgumentException('Please specify an integer argument for --tail.');
            }

            $start  = count($history) - $tail;
            $length = intval($tail) + 1;
        } else {
            return $history;
        }

        return array_slice($history, $start, $length, true);
    }

    /**
     * Validate that only one of the given $options is set.
     *
     * @param InputInterface $input
     * @param array          $options
     */
    private function validateOnlyOne(InputInterface $input, array $options)
    {
        $count = 0;
        foreach ($options as $opt) {
            if ($input->getOption($opt)) {
                $count++;
            }
        }

        if ($count > 1) {
            throw new \InvalidArgumentException('Please specify only one of --' . implode(', --', $options));
        }
    }

    /**
     * Clear the readline history.
     */
    private function clearHistory()
    {
        $this->readline->clearHistory();
    }

    public static function escape($string)
    {
        return OutputFormatter::escape($string);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Command\ListCommand\ClassConstantEnumerator;
use Psy\Command\ListCommand\ClassEnumerator;
use Psy\Command\ListCommand\ConstantEnumerator;
use Psy\Command\ListCommand\FunctionEnumerator;
use Psy\Command\ListCommand\GlobalVariableEnumerator;
use Psy\Command\ListCommand\MethodEnumerator;
use Psy\Command\ListCommand\PropertyEnumerator;
use Psy\Command\ListCommand\VariableEnumerator;
use Psy\Exception\RuntimeException;
use Psy\Input\FilterOptions;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * List available local variables, object properties, etc.
 */
class ListCommand extends ReflectingCommand implements PresenterAware
{
    protected $presenter;
    protected $enumerators;

    /**
     * PresenterAware interface.
     *
     * @param Presenter $manager
     */
    public function setPresenter(Presenter $presenter)
    {
        $this->presenter = $presenter;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        list($grep, $insensitive, $invert) = FilterOptions::getOptions();

        $this
            ->setName('ls')
            ->setAliases(array('list', 'dir'))
            ->setDefinition(array(
                new InputArgument('target', InputArgument::OPTIONAL, 'A target class or object to list.', null),

                new InputOption('vars',        '',  InputOption::VALUE_NONE,     'Display variables.'),
                new InputOption('constants',   'c', InputOption::VALUE_NONE,     'Display defined constants.'),
                new InputOption('functions',   'f', InputOption::VALUE_NONE,     'Display defined functions.'),
                new InputOption('classes',     'k', InputOption::VALUE_NONE,     'Display declared classes.'),
                new InputOption('interfaces',  'I', InputOption::VALUE_NONE,     'Display declared interfaces.'),
                new InputOption('traits',      't', InputOption::VALUE_NONE,     'Display declared traits.'),

                new InputOption('no-inherit',  '',  InputOption::VALUE_NONE,     'Exclude inherited methods, properties and constants.'),

                new InputOption('properties',  'p', InputOption::VALUE_NONE,     'Display class or object properties (public properties by default).'),
                new InputOption('methods',     'm', InputOption::VALUE_NONE,     'Display class or object methods (public methods by default).'),

                $grep,
                $insensitive,
                $invert,

                new InputOption('globals',     'g', InputOption::VALUE_NONE,     'Include global variables.'),
                new InputOption('internal',    'n', InputOption::VALUE_NONE,     'Limit to internal functions and classes.'),
                new InputOption('user',        'u', InputOption::VALUE_NONE,     'Limit to user-defined constants, functions and classes.'),
                new InputOption('category',    'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),

                new InputOption('all',         'a', InputOption::VALUE_NONE,     'Include private and protected methods and properties.'),
                new InputOption('long',        'l', InputOption::VALUE_NONE,     'List in long format: includes class names and method signatures.'),
            ))
            ->setDescription('List local, instance or class variables, methods and constants.')
            ->setHelp(
                <<<'HELP'
List variables, constants, classes, interfaces, traits, functions, methods,
and properties.

Called without options, this will return a list of variables currently in scope.

If a target object is provided, list properties, constants and methods of that
target. If a class, interface or trait name is passed instead, list constants
and methods on that class.

e.g.
<return>>>> ls</return>
<return>>>> ls $foo</return>
<return>>>> ls -k --grep mongo -i</return>
<return>>>> ls -al ReflectionClass</return>
<return>>>> ls --constants --category date</return>
<return>>>> ls -l --functions --grep /^array_.*/</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->validateInput($input);
        $this->initEnumerators();

        $method = $input->getOption('long') ? 'writeLong' : 'write';

        if ($target = $input->getArgument('target')) {
            list($target, $reflector) = $this->getTargetAndReflector($target, true);
        } else {
            $reflector = null;
        }

        // @todo something cleaner than this :-/
        if ($input->getOption('long')) {
            $output->startPaging();
        }

        foreach ($this->enumerators as $enumerator) {
            $this->$method($output, $enumerator->enumerate($input, $reflector, $target));
        }

        if ($input->getOption('long')) {
            $output->stopPaging();
        }

        // Set some magic local variables
        if ($reflector !== null) {
            $this->setCommandScopeVariables($reflector);
        }
    }

    /**
     * Initialize Enumerators.
     */
    protected function initEnumerators()
    {
        if (!isset($this->enumerators)) {
            $mgr = $this->presenter;

            $this->enumerators = array(
                new ClassConstantEnumerator($mgr),
                new ClassEnumerator($mgr),
                new ConstantEnumerator($mgr),
                new FunctionEnumerator($mgr),
                new GlobalVariableEnumerator($mgr),
                new PropertyEnumerator($mgr),
                new MethodEnumerator($mgr),
                new VariableEnumerator($mgr, $this->context),
            );
        }
    }

    /**
     * Write the list items to $output.
     *
     * @param OutputInterface $output
     * @param null|array      $result List of enumerated items
     */
    protected function write(OutputInterface $output, array $result = null)
    {
        if ($result === null) {
            return;
        }

        foreach ($result as $label => $items) {
            $names = array_map(array($this, 'formatItemName'), $items);
            $output->writeln(sprintf('<strong>%s</strong>: %s', $label, implode(', ', $names)));
        }
    }

    /**
     * Write the list items to $output.
     *
     * Items are listed one per line, and include the item signature.
     *
     * @param OutputInterface $output
     * @param null|array      $result List of enumerated items
     */
    protected function writeLong(OutputInterface $output, array $result = null)
    {
        if ($result === null) {
            return;
        }

        $table = $this->getTable($output);

        foreach ($result as $label => $items) {
            $output->writeln('');
            $output->writeln(sprintf('<strong>%s:</strong>', $label));

            $table->setRows(array());
            foreach ($items as $item) {
                $table->addRow(array($this->formatItemName($item), $item['value']));
            }

            if ($table instanceof TableHelper) {
                $table->render($output);
            } else {
                $table->render();
            }
        }
    }

    /**
     * Format an item name given its visibility.
     *
     * @param array $item
     *
     * @return string
     */
    private function formatItemName($item)
    {
        return sprintf('<%s>%s</%s>', $item['style'], OutputFormatter::escape($item['name']), $item['style']);
    }

    /**
     * Validate that input options make sense, provide defaults when called without options.
     *
     * @throws RuntimeException if options are inconsistent
     *
     * @param InputInterface $input
     */
    private function validateInput(InputInterface $input)
    {
        if (!$input->getArgument('target')) {
            // if no target is passed, there can be no properties or methods
            foreach (array('properties', 'methods', 'no-inherit') as $option) {
                if ($input->getOption($option)) {
                    throw new RuntimeException('--' . $option . ' does not make sense without a specified target.');
                }
            }

            foreach (array('globals', 'vars', 'constants', 'functions', 'classes', 'interfaces', 'traits') as $option) {
                if ($input->getOption($option)) {
                    return;
                }
            }

            // default to --vars if no other options are passed
            $input->setOption('vars', true);
        } else {
            // if a target is passed, classes, functions, etc don't make sense
            foreach (array('vars', 'globals', 'functions', 'classes', 'interfaces', 'traits') as $option) {
                if ($input->getOption($option)) {
                    throw new RuntimeException('--' . $option . ' does not make sense with a specified target.');
                }
            }

            foreach (array('constants', 'properties', 'methods') as $option) {
                if ($input->getOption($option)) {
                    return;
                }
            }

            // default to --constants --properties --methods if no other options are passed
            $input->setOption('constants',  true);
            $input->setOption('properties', true);
            $input->setOption('methods',    true);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Psy\Reflection\ReflectionConstant;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Class Constant Enumerator class.
 */
class ClassConstantEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list constants when a Reflector is present.

        if ($reflector === null) {
            return;
        }

        // We can only list constants on actual class (or object) reflectors.
        if (!$reflector instanceof \ReflectionClass) {
            // @todo handle ReflectionExtension as well
            return;
        }

        // only list constants if we are specifically asked
        if (!$input->getOption('constants')) {
            return;
        }

        $noInherit = $input->getOption('no-inherit');
        $constants = $this->prepareConstants($this->getConstants($reflector, $noInherit));

        if (empty($constants)) {
            return;
        }

        $ret = array();
        $ret[$this->getKindLabel($reflector)] = $constants;

        return $ret;
    }

    /**
     * Get defined constants for the given class or object Reflector.
     *
     * @param \Reflector $reflector
     * @param bool       $noInherit Exclude inherited constants
     *
     * @return array
     */
    protected function getConstants(\Reflector $reflector, $noInherit = false)
    {
        $className = $reflector->getName();

        $constants = array();
        foreach ($reflector->getConstants() as $name => $constant) {
            $constReflector = new ReflectionConstant($reflector, $name);

            if ($noInherit && $constReflector->getDeclaringClass()->getName() !== $className) {
                continue;
            }

            $constants[$name] = $constReflector;
        }

        // @todo switch to ksort after we drop support for 5.3:
        //     ksort($constants, SORT_NATURAL | SORT_FLAG_CASE);
        uksort($constants, 'strnatcasecmp');

        return $constants;
    }

    /**
     * Prepare formatted constant array.
     *
     * @param array $constants
     *
     * @return array
     */
    protected function prepareConstants(array $constants)
    {
        // My kingdom for a generator.
        $ret = array();

        foreach ($constants as $name => $constant) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_CONSTANT,
                    'value' => $this->presentRef($constant->getValue()),
                );
            }
        }

        return $ret;
    }

    /**
     * Get a label for the particular kind of "class" represented.
     *
     * @param \ReflectionClass $reflector
     *
     * @return string
     */
    protected function getKindLabel(\ReflectionClass $reflector)
    {
        if ($reflector->isInterface()) {
            return 'Interface Constants';
        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
            return 'Trait Constants';
        } else {
            return 'Class Constants';
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Class Enumerator class.
 */
class ClassEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list classes when no Reflector is present.
        //
        // @todo make a NamespaceReflector and pass that in for commands like:
        //
        //     ls --classes Foo
        //
        // ... for listing classes in the Foo namespace

        if ($reflector !== null || $target !== null) {
            return;
        }

        $user     = $input->getOption('user');
        $internal = $input->getOption('internal');

        $ret = array();

        // only list classes, interfaces and traits if we are specifically asked

        if ($input->getOption('classes')) {
            $ret = array_merge($ret, $this->filterClasses('Classes', get_declared_classes(), $internal, $user));
        }

        if ($input->getOption('interfaces')) {
            $ret = array_merge($ret, $this->filterClasses('Interfaces', get_declared_interfaces(), $internal, $user));
        }

        if (function_exists('get_declared_traits') && $input->getOption('traits')) {
            $ret = array_merge($ret, $this->filterClasses('Traits', get_declared_traits(), $internal, $user));
        }

        return array_map(array($this, 'prepareClasses'), array_filter($ret));
    }

    /**
     * Filter a list of classes, interfaces or traits.
     *
     * If $internal or $user is defined, results will be limited to internal or
     * user-defined classes as appropriate.
     *
     * @param string $key
     * @param array  $classes
     * @param bool   $internal
     * @param bool   $user
     *
     * @return array
     */
    protected function filterClasses($key, $classes, $internal, $user)
    {
        $ret = array();

        if ($internal) {
            $ret['Internal ' . $key] = array_filter($classes, function ($class) {
                $refl = new \ReflectionClass($class);

                return $refl->isInternal();
            });
        }

        if ($user) {
            $ret['User ' . $key] = array_filter($classes, function ($class) {
                $refl = new \ReflectionClass($class);

                return !$refl->isInternal();
            });
        }

        if (!$user && !$internal) {
            $ret[$key] = $classes;
        }

        return $ret;
    }

    /**
     * Prepare formatted class array.
     *
     * @param array $class
     *
     * @return array
     */
    protected function prepareClasses(array $classes)
    {
        natcasesort($classes);

        // My kingdom for a generator.
        $ret = array();

        foreach ($classes as $name) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_CLASS,
                    'value' => $this->presentSignature($name),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Constant Enumerator class.
 */
class ConstantEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list constants when no Reflector is present.
        //
        // @todo make a NamespaceReflector and pass that in for commands like:
        //
        //     ls --constants Foo
        //
        // ... for listing constants in the Foo namespace
        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list constants if we are specifically asked
        if (!$input->getOption('constants')) {
            return;
        }

        $user     = $input->getOption('user');
        $internal = $input->getOption('internal');
        $category = $input->getOption('category');

        $ret = array();

        if ($user) {
            $ret['User Constants'] = $this->getConstants('user');
        }

        if ($internal) {
            $ret['Interal Constants'] = $this->getConstants('internal');
        }

        if ($category) {
            $label = ucfirst($category) . ' Constants';
            $ret[$label] = $this->getConstants($category);
        }

        if (!$user && !$internal && !$category) {
            $ret['Constants'] = $this->getConstants();
        }

        return array_map(array($this, 'prepareConstants'), array_filter($ret));
    }

    /**
     * Get defined constants.
     *
     * Optionally restrict constants to a given category, e.g. "date". If the
     * category is "internal", include all non-user-defined constants.
     *
     * @param string $category
     *
     * @return array
     */
    protected function getConstants($category = null)
    {
        if (!$category) {
            return get_defined_constants();
        }

        $consts = get_defined_constants(true);

        if ($category === 'internal') {
            unset($consts['user']);

            return call_user_func_array('array_merge', $consts);
        }

        return isset($consts[$category]) ? $consts[$category] : array();
    }

    /**
     * Prepare formatted constant array.
     *
     * @param array $constants
     *
     * @return array
     */
    protected function prepareConstants(array $constants)
    {
        // My kingdom for a generator.
        $ret = array();

        $names = array_keys($constants);
        natcasesort($names);

        foreach ($names as $name) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_CONSTANT,
                    'value' => $this->presentRef($constants[$name]),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Psy\Formatter\SignatureFormatter;
use Psy\Input\FilterOptions;
use Psy\Util\Mirror;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Abstract Enumerator class.
 */
abstract class Enumerator
{
    // Output styles
    const IS_PUBLIC    = 'public';
    const IS_PROTECTED = 'protected';
    const IS_PRIVATE   = 'private';
    const IS_GLOBAL    = 'global';
    const IS_CONSTANT  = 'const';
    const IS_CLASS     = 'class';
    const IS_FUNCTION  = 'function';

    private $filter;
    private $presenter;

    /**
     * Enumerator constructor.
     *
     * @param Presenter $presenter
     */
    public function __construct(Presenter $presenter)
    {
        $this->filter = new FilterOptions();
        $this->presenter = $presenter;
    }

    /**
     * Return a list of categorized things with the given input options and target.
     *
     * @param InputInterface $input
     * @param Reflector      $reflector
     * @param mixed          $target
     *
     * @return array
     */
    public function enumerate(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        $this->filter->bind($input);

        return $this->listItems($input, $reflector, $target);
    }

    /**
     * Enumerate specific items with the given input options and target.
     *
     * Implementing classes should return an array of arrays:
     *
     *     [
     *         'Constants' => [
     *             'FOO' => [
     *                 'name'  => 'FOO',
     *                 'style' => 'public',
     *                 'value' => '123',
     *             ],
     *         ],
     *     ]
     *
     * @param InputInterface $input
     * @param Reflector      $reflector
     * @param mixed          $target
     *
     * @return array
     */
    abstract protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null);

    protected function showItem($name)
    {
        return $this->filter->match($name);
    }

    protected function presentRef($value)
    {
        return $this->presenter->presentRef($value);
    }

    protected function presentSignature($target)
    {
        // This might get weird if the signature is actually for a reflector. Hrm.
        if (!$target instanceof \Reflector) {
            $target = Mirror::get($target);
        }

        return SignatureFormatter::format($target);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Function Enumerator class.
 */
class FunctionEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list functions when no Reflector is present.
        //
        // @todo make a NamespaceReflector and pass that in for commands like:
        //
        //     ls --functions Foo
        //
        // ... for listing functions in the Foo namespace

        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list functions if we are specifically asked
        if (!$input->getOption('functions')) {
            return;
        }

        if ($input->getOption('user')) {
            $label     = 'User Functions';
            $functions = $this->getFunctions('user');
        } elseif ($input->getOption('internal')) {
            $label     = 'Internal Functions';
            $functions = $this->getFunctions('internal');
        } else {
            $label     = 'Functions';
            $functions = $this->getFunctions();
        }

        $functions = $this->prepareFunctions($functions);

        if (empty($functions)) {
            return;
        }

        $ret = array();
        $ret[$label] = $functions;

        return $ret;
    }

    /**
     * Get defined functions.
     *
     * Optionally limit functions to "user" or "internal" functions.
     *
     * @param null|string $type "user" or "internal" (default: both)
     *
     * @return array
     */
    protected function getFunctions($type = null)
    {
        $funcs = get_defined_functions();

        if ($type) {
            return $funcs[$type];
        } else {
            return array_merge($funcs['internal'], $funcs['user']);
        }
    }

    /**
     * Prepare formatted function array.
     *
     * @param array $functions
     *
     * @return array
     */
    protected function prepareFunctions(array $functions)
    {
        natcasesort($functions);

        // My kingdom for a generator.
        $ret = array();

        foreach ($functions as $name) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_FUNCTION,
                    'value' => $this->presentSignature($name),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Global Variable Enumerator class.
 */
class GlobalVariableEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list globals when no Reflector is present.
        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list globals if we are specifically asked
        if (!$input->getOption('globals')) {
            return;
        }

        $globals = $this->prepareGlobals($this->getGlobals());

        if (empty($globals)) {
            return;
        }

        return array(
            'Global Variables' => $globals,
        );
    }

    /**
     * Get defined global variables.
     *
     * @return array
     */
    protected function getGlobals()
    {
        global $GLOBALS;

        $names = array_keys($GLOBALS);
        natcasesort($names);

        $ret = array();
        foreach ($names as $name) {
            $ret[$name] = $GLOBALS[$name];
        }

        return $ret;
    }

    /**
     * Prepare formatted global variable array.
     *
     * @param array $globals
     *
     * @return array
     */
    protected function prepareGlobals($globals)
    {
        // My kingdom for a generator.
        $ret = array();

        foreach ($globals as $name => $value) {
            if ($this->showItem($name)) {
                $fname = '$' . $name;
                $ret[$fname] = array(
                    'name'  => $fname,
                    'style' => self::IS_GLOBAL,
                    'value' => $this->presentRef($value),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Interface Enumerator class.
 *
 * @deprecated Nothing should use this anymore
 */
class InterfaceEnumerator extends Enumerator
{
    public function __construct(Presenter $presenter)
    {
        @trigger_error('InterfaceEnumerator is no longer used', E_USER_DEPRECATED);
    }

    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list interfaces when no Reflector is present.
        //
        // @todo make a NamespaceReflector and pass that in for commands like:
        //
        //     ls --interfaces Foo
        //
        // ... for listing interfaces in the Foo namespace

        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list interfaces if we are specifically asked
        if (!$input->getOption('interfaces')) {
            return;
        }

        $interfaces = $this->prepareInterfaces(get_declared_interfaces());

        if (empty($interfaces)) {
            return;
        }

        return array(
            'Interfaces' => $interfaces,
        );
    }

    /**
     * Prepare formatted interface array.
     *
     * @param array $interfaces
     *
     * @return array
     */
    protected function prepareInterfaces(array $interfaces)
    {
        natcasesort($interfaces);

        // My kingdom for a generator.
        $ret = array();

        foreach ($interfaces as $name) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_CLASS,
                    'value' => $this->presentSignature($name),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Method Enumerator class.
 */
class MethodEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list methods when a Reflector is present.

        if ($reflector === null) {
            return;
        }

        // We can only list methods on actual class (or object) reflectors.
        if (!$reflector instanceof \ReflectionClass) {
            return;
        }

        // only list methods if we are specifically asked
        if (!$input->getOption('methods')) {
            return;
        }

        $showAll = $input->getOption('all');
        $noInherit = $input->getOption('no-inherit');
        $methods = $this->prepareMethods($this->getMethods($showAll, $reflector, $noInherit));

        if (empty($methods)) {
            return;
        }

        $ret = array();
        $ret[$this->getKindLabel($reflector)] = $methods;

        return $ret;
    }

    /**
     * Get defined methods for the given class or object Reflector.
     *
     * @param bool       $showAll   Include private and protected methods
     * @param \Reflector $reflector
     * @param bool       $noInherit Exclude inherited methods
     *
     * @return array
     */
    protected function getMethods($showAll, \Reflector $reflector, $noInherit = false)
    {
        $className = $reflector->getName();

        $methods = array();
        foreach ($reflector->getMethods() as $name => $method) {
            if ($noInherit && $method->getDeclaringClass()->getName() !== $className) {
                continue;
            }

            if ($showAll || $method->isPublic()) {
                $methods[$method->getName()] = $method;
            }
        }

        // @todo switch to ksort after we drop support for 5.3:
        //     ksort($methods, SORT_NATURAL | SORT_FLAG_CASE);
        uksort($methods, 'strnatcasecmp');

        return $methods;
    }

    /**
     * Prepare formatted method array.
     *
     * @param array $methods
     *
     * @return array
     */
    protected function prepareMethods(array $methods)
    {
        // My kingdom for a generator.
        $ret = array();

        foreach ($methods as $name => $method) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => $this->getVisibilityStyle($method),
                    'value' => $this->presentSignature($method),
                );
            }
        }

        return $ret;
    }

    /**
     * Get a label for the particular kind of "class" represented.
     *
     * @param \ReflectionClass $reflector
     *
     * @return string
     */
    protected function getKindLabel(\ReflectionClass $reflector)
    {
        if ($reflector->isInterface()) {
            return 'Interface Methods';
        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
            return 'Trait Methods';
        } else {
            return 'Class Methods';
        }
    }

    /**
     * Get output style for the given method's visibility.
     *
     * @param \ReflectionMethod $method
     *
     * @return string
     */
    private function getVisibilityStyle(\ReflectionMethod $method)
    {
        if ($method->isPublic()) {
            return self::IS_PUBLIC;
        } elseif ($method->isProtected()) {
            return self::IS_PROTECTED;
        } else {
            return self::IS_PRIVATE;
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Symfony\Component\Console\Input\InputInterface;

/**
 * Property Enumerator class.
 */
class PropertyEnumerator extends Enumerator
{
    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list properties when a Reflector is present.

        if ($reflector === null) {
            return;
        }

        // We can only list properties on actual class (or object) reflectors.
        if (!$reflector instanceof \ReflectionClass) {
            return;
        }

        // only list properties if we are specifically asked
        if (!$input->getOption('properties')) {
            return;
        }

        $showAll = $input->getOption('all');
        $noInherit = $input->getOption('no-inherit');
        $properties = $this->prepareProperties($this->getProperties($showAll, $reflector, $noInherit), $target);

        if (empty($properties)) {
            return;
        }

        $ret = array();
        $ret[$this->getKindLabel($reflector)] = $properties;

        return $ret;
    }

    /**
     * Get defined properties for the given class or object Reflector.
     *
     * @param bool       $showAll   Include private and protected properties
     * @param \Reflector $reflector
     * @param bool       $noInherit Exclude inherited properties
     *
     * @return array
     */
    protected function getProperties($showAll, \Reflector $reflector, $noInherit = false)
    {
        $className = $reflector->getName();

        $properties = array();
        foreach ($reflector->getProperties() as $property) {
            if ($noInherit && $property->getDeclaringClass()->getName() !== $className) {
                continue;
            }

            if ($showAll || $property->isPublic()) {
                $properties[$property->getName()] = $property;
            }
        }

        // @todo switch to ksort after we drop support for 5.3:
        //     ksort($properties, SORT_NATURAL | SORT_FLAG_CASE);
        uksort($properties, 'strnatcasecmp');

        return $properties;
    }

    /**
     * Prepare formatted property array.
     *
     * @param array $properties
     *
     * @return array
     */
    protected function prepareProperties(array $properties, $target = null)
    {
        // My kingdom for a generator.
        $ret = array();

        foreach ($properties as $name => $property) {
            if ($this->showItem($name)) {
                $fname = '$' . $name;
                $ret[$fname] = array(
                    'name'  => $fname,
                    'style' => $this->getVisibilityStyle($property),
                    'value' => $this->presentValue($property, $target),
                );
            }
        }

        return $ret;
    }

    /**
     * Get a label for the particular kind of "class" represented.
     *
     * @param \ReflectionClass $reflector
     *
     * @return string
     */
    protected function getKindLabel(\ReflectionClass $reflector)
    {
        if ($reflector->isInterface()) {
            return 'Interface Properties';
        } elseif (method_exists($reflector, 'isTrait') && $reflector->isTrait()) {
            return 'Trait Properties';
        } else {
            return 'Class Properties';
        }
    }

    /**
     * Get output style for the given property's visibility.
     *
     * @param \ReflectionProperty $property
     *
     * @return string
     */
    private function getVisibilityStyle(\ReflectionProperty $property)
    {
        if ($property->isPublic()) {
            return self::IS_PUBLIC;
        } elseif ($property->isProtected()) {
            return self::IS_PROTECTED;
        } else {
            return self::IS_PRIVATE;
        }
    }

    /**
     * Present the $target's current value for a reflection property.
     *
     * @param \ReflectionProperty $property
     * @param mixed               $target
     *
     * @return string
     */
    protected function presentValue(\ReflectionProperty $property, $target)
    {
        // If $target is a class, trait or interface (try to) get the default
        // value for the property.
        if (!is_object($target)) {
            try {
                $refl = new \ReflectionClass($target);
                $props = $refl->getDefaultProperties();
                if (array_key_exists($property->name, $props)) {
                    $suffix = $property->isStatic() ? '' : ' <aside>(default)</aside>';

                    return $this->presentRef($props[$property->name]) . $suffix;
                }
            } catch (\Exception $e) {
                // Well, we gave it a shot.
            }

            return '';
        }

        $property->setAccessible(true);
        $value = $property->getValue($target);

        return $this->presentRef($value);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Trait Enumerator class.
 *
 * @deprecated Nothing should use this anymore
 */
class TraitEnumerator extends Enumerator
{
    public function __construct(Presenter $presenter)
    {
        @trigger_error('TraitEnumerator is no longer used', E_USER_DEPRECATED);
    }

    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // bail early if current PHP doesn't know about traits.
        if (!function_exists('trait_exists')) {
            return;
        }

        // only list traits when no Reflector is present.
        //
        // @todo make a NamespaceReflector and pass that in for commands like:
        //
        //     ls --traits Foo
        //
        // ... for listing traits in the Foo namespace

        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list traits if we are specifically asked
        if (!$input->getOption('traits')) {
            return;
        }

        $traits = $this->prepareTraits(get_declared_traits());

        if (empty($traits)) {
            return;
        }

        return array(
            'Traits' => $traits,
        );
    }

    /**
     * Prepare formatted trait array.
     *
     * @param array $traits
     *
     * @return array
     */
    protected function prepareTraits(array $traits)
    {
        natcasesort($traits);

        // My kingdom for a generator.
        $ret = array();

        foreach ($traits as $name) {
            if ($this->showItem($name)) {
                $ret[$name] = array(
                    'name'  => $name,
                    'style' => self::IS_CLASS,
                    'value' => $this->presentSignature($name),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command\ListCommand;

use Psy\Context;
use Psy\VarDumper\Presenter;
use Symfony\Component\Console\Input\InputInterface;

/**
 * Variable Enumerator class.
 */
class VariableEnumerator extends Enumerator
{
    // n.b. this array is the order in which special variables will be listed
    private static $specialNames = array(
        '_', '_e', '__out', '__function', '__method', '__class', '__namespace', '__file', '__line', '__dir',
    );

    private $context;

    /**
     * Variable Enumerator constructor.
     *
     * Unlike most other enumerators, the Variable Enumerator needs access to
     * the current scope variables, so we need to pass it a Context instance.
     *
     * @param Presenter $presenter
     * @param Context   $context
     */
    public function __construct(Presenter $presenter, Context $context)
    {
        $this->context = $context;
        parent::__construct($presenter);
    }

    /**
     * {@inheritdoc}
     */
    protected function listItems(InputInterface $input, \Reflector $reflector = null, $target = null)
    {
        // only list variables when no Reflector is present.
        if ($reflector !== null || $target !== null) {
            return;
        }

        // only list variables if we are specifically asked
        if (!$input->getOption('vars')) {
            return;
        }

        $showAll   = $input->getOption('all');
        $variables = $this->prepareVariables($this->getVariables($showAll));

        if (empty($variables)) {
            return;
        }

        return array(
            'Variables' => $variables,
        );
    }

    /**
     * Get scope variables.
     *
     * @param bool $showAll Include special variables (e.g. $_)
     *
     * @return array
     */
    protected function getVariables($showAll)
    {
        // self:: doesn't work inside closures in PHP 5.3 :-/
        $specialNames = self::$specialNames;

        $scopeVars = $this->context->getAll();
        uksort($scopeVars, function ($a, $b) use ($specialNames) {
            $aIndex = array_search($a, $specialNames);
            $bIndex = array_search($b, $specialNames);

            if ($aIndex !== false) {
                if ($bIndex !== false) {
                    return $aIndex - $bIndex;
                }

                return 1;
            }

            if ($bIndex !== false) {
                return -1;
            }

            return strnatcasecmp($a, $b);
        });

        $ret = array();
        foreach ($scopeVars as $name => $val) {
            if (!$showAll && in_array($name, self::$specialNames)) {
                continue;
            }

            $ret[$name] = $val;
        }

        return $ret;
    }

    /**
     * Prepare formatted variable array.
     *
     * @param array $variables
     *
     * @return array
     */
    protected function prepareVariables(array $variables)
    {
        // My kingdom for a generator.
        $ret = array();
        foreach ($variables as $name => $val) {
            if ($this->showItem($name)) {
                $fname = '$' . $name;
                $ret[$fname] = array(
                    'name'  => $fname,
                    'style' => in_array($name, self::$specialNames) ? self::IS_PRIVATE : self::IS_PUBLIC,
                    'value' => $this->presentRef($val),
                );
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use PhpParser\Node;
use PhpParser\Parser;
use Psy\Input\CodeArgument;
use Psy\ParserFactory;
use Psy\VarDumper\Presenter;
use Psy\VarDumper\PresenterAware;
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\VarDumper\Caster\Caster;

/**
 * Parse PHP code and show the abstract syntax tree.
 */
class ParseCommand extends Command implements PresenterAware
{
    private $presenter;
    private $parserFactory;
    private $parsers;

    /**
     * {@inheritdoc}
     */
    public function __construct($name = null)
    {
        $this->parserFactory = new ParserFactory();
        $this->parsers = array();

        parent::__construct($name);
    }

    /**
     * PresenterAware interface.
     *
     * @param Presenter $presenter
     */
    public function setPresenter(Presenter $presenter)
    {
        $this->presenter = clone $presenter;
        $this->presenter->addCasters(array(
            'PhpParser\Node' => function (Node $node, array $a) {
                $a = array(
                    Caster::PREFIX_VIRTUAL . 'type'       => $node->getType(),
                    Caster::PREFIX_VIRTUAL . 'attributes' => $node->getAttributes(),
                );

                foreach ($node->getSubNodeNames() as $name) {
                    $a[Caster::PREFIX_VIRTUAL . $name] = $node->$name;
                }

                return $a;
            },
        ));
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $definition = array(
            new CodeArgument('code', InputArgument::REQUIRED, 'PHP code to parse.'),
            new InputOption('depth', '', InputOption::VALUE_REQUIRED, 'Depth to parse', 10),
        );

        if ($this->parserFactory->hasKindsSupport()) {
            $msg = 'One of PhpParser\\ParserFactory constants: '
                . implode(', ', ParserFactory::getPossibleKinds())
                . " (default is based on current interpreter's version)";
            $defaultKind = $this->parserFactory->getDefaultKind();

            $definition[] = new InputOption('kind', '', InputOption::VALUE_REQUIRED, $msg, $defaultKind);
        }

        $this
            ->setName('parse')
            ->setDefinition($definition)
            ->setDescription('Parse PHP code and show the abstract syntax tree.')
            ->setHelp(
                <<<'HELP'
Parse PHP code and show the abstract syntax tree.

This command is used in the development of PsySH. Given a string of PHP code,
it pretty-prints the PHP Parser parse tree.

See https://github.com/nikic/PHP-Parser

It prolly won't be super useful for most of you, but it's here if you want to play.
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $code = $input->getArgument('code');
        if (strpos('<?', $code) === false) {
            $code = '<?php ' . $code;
        }

        $parserKind = $this->parserFactory->hasKindsSupport() ? $input->getOption('kind') : null;
        $depth      = $input->getOption('depth');
        $nodes      = $this->parse($this->getParser($parserKind), $code);
        $output->page($this->presenter->present($nodes, $depth));
    }

    /**
     * Lex and parse a string of code into statements.
     *
     * @param Parser $parser
     * @param string $code
     *
     * @return array Statements
     */
    private function parse(Parser $parser, $code)
    {
        try {
            return $parser->parse($code);
        } catch (\PhpParser\Error $e) {
            if (strpos($e->getMessage(), 'unexpected EOF') === false) {
                throw $e;
            }

            // If we got an unexpected EOF, let's try it again with a semicolon.
            return $parser->parse($code . ';');
        }
    }

    /**
     * Get (or create) the Parser instance.
     *
     * @param string|null $kind One of Psy\ParserFactory constants (only for PHP parser 2.0 and above)
     *
     * @return Parser
     */
    private function getParser($kind = null)
    {
        if (!array_key_exists($kind, $this->parsers)) {
            $this->parsers[$kind] = $this->parserFactory->createParser($kind);
        }

        return $this->parsers[$kind];
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * A dumb little command for printing out the current Psy Shell version.
 */
class PsyVersionCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('version')
            ->setDefinition(array())
            ->setDescription('Show Psy Shell version.')
            ->setHelp('Show Psy Shell version.');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln($this->getApplication()->getVersion());
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\RuntimeException;
use Psy\Util\Mirror;

/**
 * An abstract command with helpers for inspecting the current context.
 */
abstract class ReflectingCommand extends Command implements ContextAware
{
    const CLASS_OR_FUNC   = '/^[\\\\\w]+$/';
    const INSTANCE        = '/^\$(\w+)$/';
    const CLASS_MEMBER    = '/^([\\\\\w]+)::(\w+)$/';
    const CLASS_STATIC    = '/^([\\\\\w]+)::\$(\w+)$/';
    const INSTANCE_MEMBER = '/^\$(\w+)(::|->)(\w+)$/';
    const INSTANCE_STATIC = '/^\$(\w+)::\$(\w+)$/';
    const SUPERGLOBAL     = '/^\$(GLOBALS|_(?:SERVER|ENV|FILES|COOKIE|POST|GET|SESSION))$/';

    /**
     * Context instance (for ContextAware interface).
     *
     * @var Context
     */
    protected $context;

    /**
     * ContextAware interface.
     *
     * @param Context $context
     */
    public function setContext(Context $context)
    {
        $this->context = $context;
    }

    /**
     * Get the target for a value.
     *
     * @throws \InvalidArgumentException when the value specified can't be resolved
     *
     * @param string $valueName Function, class, variable, constant, method or property name
     * @param bool   $classOnly True if the name should only refer to a class, function or instance
     *
     * @return array (class or instance name, member name, kind)
     */
    protected function getTarget($valueName, $classOnly = false)
    {
        $valueName = trim($valueName);
        $matches   = array();
        switch (true) {
            case preg_match(self::SUPERGLOBAL, $valueName, $matches):
                // @todo maybe do something interesting with these at some point?
                if (array_key_exists($matches[1], $GLOBALS)) {
                    throw new RuntimeException('Unable to inspect a non-object');
                } else {
                    throw new RuntimeException('Unknown target: ' . $valueName);
                }

            case preg_match(self::CLASS_OR_FUNC, $valueName, $matches):
                return array($this->resolveName($matches[0], true), null, 0);

            case preg_match(self::INSTANCE, $valueName, $matches):
                return array($this->resolveInstance($matches[1]), null, 0);

            case !$classOnly && preg_match(self::CLASS_MEMBER, $valueName, $matches):
                return array($this->resolveName($matches[1]), $matches[2], Mirror::CONSTANT | Mirror::METHOD);

            case !$classOnly && preg_match(self::CLASS_STATIC, $valueName, $matches):
                return array($this->resolveName($matches[1]), $matches[2], Mirror::STATIC_PROPERTY | Mirror::PROPERTY);

            case !$classOnly && preg_match(self::INSTANCE_MEMBER, $valueName, $matches):
                if ($matches[2] === '->') {
                    $kind = Mirror::METHOD | Mirror::PROPERTY;
                } else {
                    $kind = Mirror::CONSTANT | Mirror::METHOD;
                }

                return array($this->resolveInstance($matches[1]), $matches[3], $kind);

            case !$classOnly && preg_match(self::INSTANCE_STATIC, $valueName, $matches):
                return array($this->resolveInstance($matches[1]), $matches[2], Mirror::STATIC_PROPERTY);

            default:
                throw new RuntimeException('Unknown target: ' . $valueName);
        }
    }

    /**
     * Resolve a class or function name (with the current shell namespace).
     *
     * @param string $name
     * @param bool   $includeFunctions (default: false)
     *
     * @return string
     */
    protected function resolveName($name, $includeFunctions = false)
    {
        if (substr($name, 0, 1) === '\\') {
            return $name;
        }

        if ($namespace = $this->getApplication()->getNamespace()) {
            $fullName = $namespace . '\\' . $name;

            if (class_exists($fullName) || interface_exists($fullName) || ($includeFunctions && function_exists($fullName))) {
                return $fullName;
            }
        }

        return $name;
    }

    /**
     * Get a Reflector and documentation for a function, class or instance, constant, method or property.
     *
     * @param string $valueName Function, class, variable, constant, method or property name
     * @param bool   $classOnly True if the name should only refer to a class, function or instance
     *
     * @return array (value, Reflector)
     */
    protected function getTargetAndReflector($valueName, $classOnly = false)
    {
        list($value, $member, $kind) = $this->getTarget($valueName, $classOnly);

        return array($value, Mirror::get($value, $member, $kind));
    }

    /**
     * Return a variable instance from the current scope.
     *
     * @throws \InvalidArgumentException when the requested variable does not exist in the current scope
     *
     * @param string $name
     *
     * @return mixed Variable instance
     */
    protected function resolveInstance($name)
    {
        $value = $this->getScopeVariable($name);
        if (!is_object($value)) {
            throw new RuntimeException('Unable to inspect a non-object');
        }

        return $value;
    }

    /**
     * Get a variable from the current shell scope.
     *
     * @param string $name
     *
     * @return mixed
     */
    protected function getScopeVariable($name)
    {
        return $this->context->get($name);
    }

    /**
     * Get all scope variables from the current shell scope.
     *
     * @return array
     */
    protected function getScopeVariables()
    {
        return $this->context->getAll();
    }

    /**
     * Given a Reflector instance, set command-scope variables in the shell
     * execution context. This is used to inject magic $__class, $__method and
     * $__file variables (as well as a handful of others).
     *
     * @param \Reflector $reflector
     */
    protected function setCommandScopeVariables(\Reflector $reflector)
    {
        $vars = array();

        switch (get_class($reflector)) {
            case 'ReflectionClass':
            case 'ReflectionObject':
                $vars['__class'] = $reflector->name;
                if ($reflector->inNamespace()) {
                    $vars['__namespace'] = $reflector->getNamespaceName();
                }
                break;

            case 'ReflectionMethod':
                $vars['__method'] = sprintf('%s::%s', $reflector->class, $reflector->name);
                $vars['__class'] = $reflector->class;
                $classReflector = $reflector->getDeclaringClass();
                if ($classReflector->inNamespace()) {
                    $vars['__namespace'] = $classReflector->getNamespaceName();
                }
                break;

            case 'ReflectionFunction':
                $vars['__function'] = $reflector->name;
                if ($reflector->inNamespace()) {
                    $vars['__namespace'] = $reflector->getNamespaceName();
                }
                break;

            case 'ReflectionGenerator':
                $funcReflector = $reflector->getFunction();
                $vars['__function'] = $funcReflector->name;
                if ($funcReflector->inNamespace()) {
                    $vars['__namespace'] = $funcReflector->getNamespaceName();
                }
                if ($fileName = $reflector->getExecutingFile()) {
                    $vars['__file'] = $fileName;
                    $vars['__line'] = $reflector->getExecutingLine();
                    $vars['__dir']  = dirname($fileName);
                }
                break;

            case 'ReflectionProperty':
            case 'Psy\Reflection\ReflectionConstant':
                $classReflector = $reflector->getDeclaringClass();
                $vars['__class'] = $classReflector->name;
                if ($classReflector->inNamespace()) {
                    $vars['__namespace'] = $classReflector->getNamespaceName();
                }
                // no line for these, but this'll do
                if ($fileName = $reflector->getDeclaringClass()->getFileName()) {
                    $vars['__file'] = $fileName;
                    $vars['__dir']  = dirname($fileName);
                }
                break;
        }

        if ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) {
            if ($fileName = $reflector->getFileName()) {
                $vars['__file'] = $fileName;
                $vars['__line'] = $reflector->getStartLine();
                $vars['__dir']  = dirname($fileName);
            }
        }

        $this->context->setCommandScopeVariables($vars);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Exception\RuntimeException;
use Psy\Formatter\CodeFormatter;
use Psy\Formatter\SignatureFormatter;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Show the code for an object, class, constant, method or property.
 */
class ShowCommand extends ReflectingCommand
{
    private $colorMode;
    private $highlighter;
    private $lastException;
    private $lastExceptionIndex;

    /**
     * @param null|string $colorMode (default: null)
     */
    public function __construct($colorMode = null)
    {
        $this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;

        return parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('show')
            ->setDefinition(array(
                new InputArgument('value', InputArgument::OPTIONAL, 'Function, class, instance, constant, method or property to show.'),
                new InputOption('ex', null,  InputOption::VALUE_OPTIONAL, 'Show last exception context. Optionally specify a stack index.', 1),
            ))
            ->setDescription('Show the code for an object, class, constant, method or property.')
            ->setHelp(
                <<<HELP
Show the code for an object, class, constant, method or property, or the context
of the last exception.

<return>cat --ex</return> defaults to showing the lines surrounding the location of the last
exception. Invoking it more than once travels up the exception's stack trace,
and providing a number shows the context of the given index of the trace.

e.g.
<return>>>> show \$myObject</return>
<return>>>> show Psy\Shell::debug</return>
<return>>>> show --ex</return>
<return>>>> show --ex 3</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // n.b. As far as I can tell, InputInterface doesn't want to tell me
        // whether an option with an optional value was actually passed. If you
        // call `$input->getOption('ex')`, it will return the default, both when
        // `--ex` is specified with no value, and when `--ex` isn't specified at
        // all.
        //
        // So we're doing something sneaky here. If we call `getOptions`, it'll
        // return the default value when `--ex` is not present, and `null` if
        // `--ex` is passed with no value. /shrug
        $opts = $input->getOptions();

        // Strict comparison to `1` (the default value) here, because `--ex 1`
        // will come in as `"1"`. Now we can tell the difference between
        // "no --ex present", because it's the integer 1, "--ex with no value",
        // because it's `null`, and "--ex 1", because it's the string "1".
        if ($opts['ex'] !== 1) {
            if ($input->getArgument('value')) {
                throw new \InvalidArgumentException('Too many arguments (supply either "value" or "--ex")');
            }

            return $this->writeExceptionContext($input, $output);
        }

        if ($input->getArgument('value')) {
            return $this->writeCodeContext($input, $output);
        }

        throw new RuntimeException('Not enough arguments (missing: "value").');
    }

    private function writeCodeContext(InputInterface $input, OutputInterface $output)
    {
        list($value, $reflector) = $this->getTargetAndReflector($input->getArgument('value'));

        // Set some magic local variables
        $this->setCommandScopeVariables($reflector);

        try {
            $output->page(CodeFormatter::format($reflector, $this->colorMode), ShellOutput::OUTPUT_RAW);
        } catch (RuntimeException $e) {
            $output->writeln(SignatureFormatter::format($reflector));
            throw $e;
        }
    }

    private function writeExceptionContext(InputInterface $input, OutputInterface $output)
    {
        $exception = $this->context->getLastException();
        if ($exception !== $this->lastException) {
            $this->lastException = null;
            $this->lastExceptionIndex = null;
        }

        $opts = $input->getOptions();
        if ($opts['ex'] === null) {
            if ($this->lastException && $this->lastExceptionIndex !== null) {
                $index = $this->lastExceptionIndex + 1;
            } else {
                $index = 0;
            }
        } else {
            $index = max(0, intval($input->getOption('ex')) - 1);
        }

        $trace = $exception->getTrace();
        array_unshift($trace, array(
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
        ));

        if ($index >= count($trace)) {
            $index = 0;
        }

        $this->lastException = $exception;
        $this->lastExceptionIndex = $index;

        $output->writeln($this->getApplication()->formatException($exception));
        $output->writeln('--');
        $this->writeTraceLine($output, $trace, $index);
        $this->writeTraceCodeSnippet($output, $trace, $index);

        $this->setCommandScopeVariablesFromContext($trace[$index]);
    }

    private function writeTraceLine(OutputInterface $output, array $trace, $index)
    {
        $file = isset($trace[$index]['file']) ? $this->replaceCwd($trace[$index]['file']) : 'n/a';
        $line = isset($trace[$index]['line']) ? $trace[$index]['line'] : 'n/a';

        $output->writeln(sprintf(
            'From <info>%s:%d</info> at <strong>level %d</strong> of backtrace (of %d).',
            OutputFormatter::escape($file),
            OutputFormatter::escape($line),
            $index + 1,
            count($trace)
        ));
    }

    private function replaceCwd($file)
    {
        if ($cwd = getcwd()) {
            $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }

        if ($cwd === false) {
            return $file;
        } else {
            return preg_replace('/^' . preg_quote($cwd, '/') . '/', '', $file);
        }
    }

    private function writeTraceCodeSnippet(OutputInterface $output, array $trace, $index)
    {
        if (!isset($trace[$index]['file'])) {
            return;
        }

        $file = $trace[$index]['file'];
        if ($fileAndLine = $this->extractEvalFileAndLine($file)) {
            list($file, $line) = $fileAndLine;
        } else {
            if (!isset($trace[$index]['line'])) {
                return;
            }

            $line = $trace[$index]['line'];
        }

        if (is_file($file)) {
            $code = @file_get_contents($file);
        }

        if (empty($code)) {
            return;
        }

        $output->write($this->getHighlighter()->getCodeSnippet($code, $line, 5, 5), ShellOutput::OUTPUT_RAW);
    }

    private function getHighlighter()
    {
        if (!$this->highlighter) {
            $factory = new ConsoleColorFactory($this->colorMode);
            $this->highlighter = new Highlighter($factory->getConsoleColor());
        }

        return $this->highlighter;
    }

    private function setCommandScopeVariablesFromContext(array $context)
    {
        $vars = array();

        // @todo __namespace?
        if (isset($context['class'])) {
            $vars['__class'] = $context['class'];
            if (isset($context['function'])) {
                $vars['__method'] = $context['function'];
            }
        } elseif (isset($context['function'])) {
            $vars['__function'] = $context['function'];
        }

        if (isset($context['file'])) {
            $file = $context['file'];
            if ($fileAndLine = $this->extractEvalFileAndLine($file)) {
                list($file, $line) = $fileAndLine;
            } elseif (isset($context['line'])) {
                $line = $context['line'];
            }

            if (is_file($file)) {
                $vars['__file'] = $file;
                if (isset($line)) {
                    $vars['__line'] = $line;
                }
                $vars['__dir'] = dirname($file);
            }
        }

        $this->context->setCommandScopeVariables($vars);
    }

    private function extractEvalFileAndLine($file)
    {
        if (preg_match('/(.*)\\((\\d+)\\) : eval\\(\\)\'d code$/', $file, $matches)) {
            return array($matches[1], $matches[2]);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use PhpParser\NodeTraverser;
use PhpParser\PrettyPrinter\Standard as Printer;
use Psy\Input\CodeArgument;
use Psy\ParserFactory;
use Psy\Readline\Readline;
use Psy\Sudo\SudoVisitor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Evaluate PHP code, bypassing visibility restrictions.
 */
class SudoCommand extends Command
{
    private $readline;
    private $parser;
    private $traverser;
    private $printer;

    /**
     * {@inheritdoc}
     */
    public function __construct($name = null)
    {
        $parserFactory = new ParserFactory();
        $this->parser = $parserFactory->createParser();

        $this->traverser = new NodeTraverser();
        $this->traverser->addVisitor(new SudoVisitor());

        $this->printer = new Printer();

        parent::__construct($name);
    }

    /**
     * Set the Shell's Readline service.
     *
     * @param Readline $readline
     */
    public function setReadline(Readline $readline)
    {
        $this->readline = $readline;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('sudo')
            ->setDefinition(array(
                new CodeArgument('code', InputArgument::REQUIRED, 'Code to execute.'),
            ))
            ->setDescription('Evaluate PHP code, bypassing visibility restrictions.')
            ->setHelp(
                <<<'HELP'
Evaluate PHP code, bypassing visibility restrictions.

e.g.
<return>>>> $sekret->whisper("hi")</return>
<return>PHP error:  Call to private method Sekret::whisper() from context '' on line 1</return>

<return>>>> sudo $sekret->whisper("hi")</return>
<return>=> "hi"</return>

<return>>>> $sekret->word</return>
<return>PHP error:  Cannot access private property Sekret::$word on line 1</return>

<return>>>> sudo $sekret->word</return>
<return>=> "hi"</return>

<return>>>> $sekret->word = "please"</return>
<return>PHP error:  Cannot access private property Sekret::$word on line 1</return>

<return>>>> sudo $sekret->word = "please"</return>
<return>=> "please"</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $code = $input->getArgument('code');

        // special case for !!
        if ($code === '!!') {
            $history = $this->readline->listHistory();
            if (count($history) < 2) {
                throw new \InvalidArgumentException('No previous command to replay');
            }
            $code = $history[count($history) - 2];
        }

        if (strpos('<?', $code) === false) {
            $code = '<?php ' . $code;
        }

        $nodes = $this->traverser->traverse($this->parse($code));

        $sudoCode = $this->printer->prettyPrint($nodes);
        $this->getApplication()->addInput($sudoCode, true);
    }

    /**
     * Lex and parse a string of code into statements.
     *
     * @param string $code
     *
     * @return array Statements
     */
    private function parse($code)
    {
        try {
            return $this->parser->parse($code);
        } catch (\PhpParser\Error $e) {
            if (strpos($e->getMessage(), 'unexpected EOF') === false) {
                throw $e;
            }

            // If we got an unexpected EOF, let's try it again with a semicolon.
            return $this->parser->parse($code . ';');
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Context;
use Psy\ContextAware;
use Psy\Exception\ThrowUpException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Throw an exception out of the Psy Shell.
 */
class ThrowUpCommand extends Command implements ContextAware
{
    /**
     * Context instance (for ContextAware interface).
     *
     * @var Context
     */
    protected $context;

    /**
     * ContextAware interface.
     *
     * @param Context $context
     */
    public function setContext(Context $context)
    {
        $this->context = $context;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('throw-up')
            ->setDefinition(array(
                new InputArgument('exception', InputArgument::OPTIONAL, 'Exception to throw'),
            ))
            ->setDescription('Throw an exception out of the Psy Shell.')
            ->setHelp(
                <<<'HELP'
Throws an exception out of the current the Psy Shell instance.

By default it throws the most recent exception.

e.g.
<return>>>> throw-up</return>
<return>>>> throw-up $e</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     *
     * @throws InvalidArgumentException if there is no exception to throw
     * @throws ThrowUpException         because what else do you expect it to do?
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if ($name = $input->getArgument('exception')) {
            $orig = $this->context->get(preg_replace('/^\$/', '', $name));
        } else {
            $orig = $this->context->getLastException();
        }

        if (!$orig instanceof \Exception) {
            throw new \InvalidArgumentException('throw-up can only throw Exceptions');
        }

        throw new ThrowUpException($orig);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Input\FilterOptions;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Show the current stack trace.
 */
class TraceCommand extends Command
{
    protected $filter;

    /**
     * {@inheritdoc}
     */
    public function __construct($name = null)
    {
        $this->filter = new FilterOptions();

        parent::__construct($name);
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        list($grep, $insensitive, $invert) = FilterOptions::getOptions();

        $this
            ->setName('trace')
            ->setDefinition(array(
                new InputOption('include-psy', 'p', InputOption::VALUE_NONE,     'Include Psy in the call stack.'),
                new InputOption('num',         'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'),

                $grep,
                $insensitive,
                $invert,
            ))
            ->setDescription('Show the current call stack.')
            ->setHelp(
                <<<'HELP'
Show the current call stack.

Optionally, include PsySH in the call stack by passing the <info>--include-psy</info> option.

e.g.
<return>> trace -n10</return>
<return>> trace --include-psy</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->filter->bind($input);
        $trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy'));
        $output->page($trace, ShellOutput::NUMBER_LINES);
    }

    /**
     * Get a backtrace for an exception.
     *
     * Optionally limit the number of rows to include with $count, and exclude
     * Psy from the trace.
     *
     * @param \Exception $e          The exception with a backtrace
     * @param int        $count      (default: PHP_INT_MAX)
     * @param bool       $includePsy (default: true)
     *
     * @return array Formatted stacktrace lines
     */
    protected function getBacktrace(\Exception $e, $count = null, $includePsy = true)
    {
        if ($cwd = getcwd()) {
            $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }

        if ($count === null) {
            $count = PHP_INT_MAX;
        }

        $lines = array();

        $trace = $e->getTrace();
        array_unshift($trace, array(
            'function' => '',
            'file'     => $e->getFile() !== null ? $e->getFile() : 'n/a',
            'line'     => $e->getLine() !== null ? $e->getLine() : 'n/a',
            'args'     => array(),
        ));

        if (!$includePsy) {
            for ($i = count($trace) - 1; $i >= 0; $i--) {
                $thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function'];
                if (preg_match('/\\\\?Psy\\\\/', $thing)) {
                    $trace = array_slice($trace, $i + 1);
                    break;
                }
            }
        }

        for ($i = 0, $count = min($count, count($trace)); $i < $count; $i++) {
            $class    = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
            $type     = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
            $function = $trace[$i]['function'];
            $file     = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a';
            $line     = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';

            // Leave execution loop out of the `eval()'d code` lines
            if (preg_match("#/Psy/ExecutionLoop/Loop.php\(\d+\) : eval\(\)'d code$#", $file)) {
                $file = "eval()'d code";
            }

            // Skip any lines that don't match our filter options
            if (!$this->filter->match(sprintf('%s%s%s() at %s:%s', $class, $type, $function, $file, $line))) {
                continue;
            }

            $lines[] = sprintf(
                ' <class>%s</class>%s%s() at <info>%s:%s</info>',
                OutputFormatter::escape($class),
                OutputFormatter::escape($type),
                OutputFormatter::escape($function),
                OutputFormatter::escape($file),
                OutputFormatter::escape($line)
            );
        }

        return $lines;
    }

    /**
     * Replace the given directory from the start of a filepath.
     *
     * @param string $cwd
     * @param string $file
     *
     * @return string
     */
    private function replaceCwd($cwd, $file)
    {
        if ($cwd === false) {
            return $file;
        } else {
            return preg_replace('/^' . preg_quote($cwd, '/') . '/', '', $file);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Show the context of where you opened the debugger.
 */
class WhereamiCommand extends Command
{
    private $colorMode;

    /**
     * @param null|string $colorMode (default: null)
     */
    public function __construct($colorMode = null)
    {
        $this->colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;

        if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
            $this->backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        } else {
            $this->backtrace = debug_backtrace();
        }

        return parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('whereami')
            ->setDefinition(array(
                new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'),
            ))
            ->setDescription('Show where you are in the code.')
            ->setHelp(
                <<<'HELP'
Show where you are in the code.

Optionally, include how many lines before and after you want to display.

e.g.
<return>> whereami </return>
<return>> whereami -n10</return>
HELP
            );
    }

    /**
     * Obtains the correct stack frame in the full backtrace.
     *
     * @return array
     */
    protected function trace()
    {
        foreach (array_reverse($this->backtrace) as $stackFrame) {
            if ($this->isDebugCall($stackFrame)) {
                return $stackFrame;
            }
        }

        return end($this->backtrace);
    }

    private static function isDebugCall(array $stackFrame)
    {
        $class    = isset($stackFrame['class']) ? $stackFrame['class'] : null;
        $function = isset($stackFrame['function']) ? $stackFrame['function'] : null;

        return ($class === null && $function === 'Psy\debug') ||
            ($class === 'Psy\Shell' && in_array($function, array('__construct', 'debug')));
    }

    /**
     * Determine the file and line based on the specific backtrace.
     *
     * @return array
     */
    protected function fileInfo()
    {
        $stackFrame = $this->trace();
        if (preg_match('/eval\(/', $stackFrame['file'])) {
            preg_match_all('/([^\(]+)\((\d+)/', $stackFrame['file'], $matches);
            $file = $matches[1][0];
            $line = (int) $matches[2][0];
        } else {
            $file = $stackFrame['file'];
            $line = $stackFrame['line'];
        }

        return compact('file', 'line');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $info = $this->fileInfo();
        $num = $input->getOption('num');
        $factory = new ConsoleColorFactory($this->colorMode);
        $colors = $factory->getConsoleColor();
        $highlighter = new Highlighter($colors);
        $contents = file_get_contents($info['file']);

        $output->startPaging();
        $output->writeln('');
        $output->writeln(sprintf('From <info>%s:%s</info>:', $this->replaceCwd($info['file']), $info['line']));
        $output->writeln('');
        $output->write($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW);
        $output->stopPaging();
    }

    /**
     * Replace the given directory from the start of a filepath.
     *
     * @param string $file
     *
     * @return string
     */
    private function replaceCwd($file)
    {
        $cwd = getcwd();
        if ($cwd === false) {
            return $file;
        }

        $cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

        return preg_replace('/^' . preg_quote($cwd, '/') . '/', '', $file);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Command;

use Psy\Context;
use Psy\ContextAware;
use Psy\Input\FilterOptions;
use Psy\Output\ShellOutput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Show the last uncaught exception.
 */
class WtfCommand extends TraceCommand implements ContextAware
{
    /**
     * Context instance (for ContextAware interface).
     *
     * @var Context
     */
    protected $context;

    /**
     * ContextAware interface.
     *
     * @param Context $context
     */
    public function setContext(Context $context)
    {
        $this->context = $context;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        list($grep, $insensitive, $invert) = FilterOptions::getOptions();

        $this
            ->setName('wtf')
            ->setAliases(array('last-exception', 'wtf?'))
            ->setDefinition(array(
                new InputArgument('incredulity', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Number of lines to show'),
                new InputOption('all', 'a',  InputOption::VALUE_NONE, 'Show entire backtrace.'),

                $grep,
                $insensitive,
                $invert,
            ))
            ->setDescription('Show the backtrace of the most recent exception.')
            ->setHelp(
                <<<'HELP'
Shows a few lines of the backtrace of the most recent exception.

If you want to see more lines, add more question marks or exclamation marks:

e.g.
<return>>>> wtf ?</return>
<return>>>> wtf ?!???!?!?</return>

To see the entire backtrace, pass the -a/--all flag:

e.g.
<return>>>> wtf -v</return>
HELP
            );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->filter->bind($input);

        $incredulity = implode('', $input->getArgument('incredulity'));
        if (strlen(preg_replace('/[\\?!]/', '', $incredulity))) {
            throw new \InvalidArgumentException('Incredulity must include only "?" and "!".');
        }

        $exception = $this->context->getLastException();
        $count     = $input->getOption('all') ? PHP_INT_MAX : max(3, pow(2, strlen($incredulity) + 1));

        $shell = $this->getApplication();
        $output->startPaging();
        do {
            $traceCount = count($exception->getTrace());
            $showLines = $count;
            // Show the whole trace if we'd only be hiding a few lines
            if ($traceCount < max($count * 1.2, $count + 2)) {
                $showLines = PHP_INT_MAX;
            }

            $trace = $this->getBacktrace($exception, $showLines);
            $moreLines = $traceCount - count($trace);

            $output->writeln($shell->formatException($exception));
            $output->writeln('--');
            $output->write($trace, true, ShellOutput::NUMBER_LINES);
            $output->writeln('');

            if ($moreLines > 0) {
                $output->writeln(sprintf(
                    '<aside>Use <return>wtf -a</return> to see %d more lines</aside>',
                    $moreLines
                ));
                $output->writeln('');
            }
        } while ($exception = $exception->getPrevious());
        $output->stopPaging();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use Symfony\Component\Finder\Finder;

/**
 * A Psy Shell Phar compiler.
 */
class Compiler
{
    /**
     * Compiles psysh into a single phar file.
     *
     * @param string $pharFile The full path to the file to create
     */
    public function compile($pharFile = 'psysh.phar')
    {
        if (file_exists($pharFile)) {
            unlink($pharFile);
        }

        $this->version = Shell::VERSION;

        $phar = new \Phar($pharFile, 0, 'psysh.phar');
        $phar->setSignatureAlgorithm(\Phar::SHA1);

        $phar->startBuffering();

        $finder = Finder::create()
            ->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->notName('Compiler.php')
            ->notName('Autoloader.php')
            ->in(__DIR__ . '/..');

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }

        $finder = Finder::create()
            ->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->exclude('Tests')
            ->exclude('tests')
            ->exclude('Test')
            ->exclude('test')
            ->in(__DIR__ . '/../../build-vendor');

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }

        // Stubs
        $phar->setStub($this->getStub());

        $phar->stopBuffering();

        unset($phar);
    }

    /**
     * Add a file to the psysh Phar.
     *
     * @param \Phar        $phar
     * @param \SplFileInfo $file
     * @param bool         $strip (default: true)
     */
    private function addFile($phar, $file, $strip = true)
    {
        $path = str_replace(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR, '', $file->getRealPath());

        $content = file_get_contents($file);
        if ($strip) {
            $content = $this->stripWhitespace($content);
        } elseif ('LICENSE' === basename($file)) {
            $content = "\n" . $content . "\n";
        }

        $phar->addFromString($path, $content);
    }

    /**
     * Removes whitespace from a PHP source string while preserving line numbers.
     *
     * @param string $source A PHP string
     *
     * @return string The PHP string with the whitespace removed
     */
    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 .= 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;
    }

    private static function getStubLicense()
    {
        $license = file_get_contents(__DIR__ . '/../../LICENSE');
        $license = str_replace('The MIT License (MIT)', '', $license);
        $license = str_replace("\n", "\n * ", trim($license));

        return $license;
    }

    const STUB_AUTOLOAD = <<<'EOS'
    Phar::mapPhar('psysh.phar');
    require 'phar://psysh.phar/build-vendor/autoload.php';
EOS;

    /**
     * Get a Phar stub for psysh.
     *
     * This is basically the psysh bin, with the autoload require statements swapped out.
     *
     * @return string
     */
    private function getStub()
    {
        $content = file_get_contents(__DIR__ . '/../../bin/psysh');
        if (version_compare(PHP_VERSION, '5.4', '<')) {
            $content = str_replace('#!/usr/bin/env php', '#!/usr/bin/env php -d detect_unicode=Off', $content);
        }
        $content = preg_replace('{/\* <<<.*?>>> \*/}sm', self::STUB_AUTOLOAD, $content);
        $content = preg_replace('/\\(c\\) .*?with this source code./sm', self::getStubLicense(), $content);

        $content .= '__HALT_COMPILER();';

        return $content;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use XdgBaseDir\Xdg;

/**
 * A Psy Shell configuration path helper.
 */
class ConfigPaths
{
    /**
     * Get potential config directory paths.
     *
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and all
     * XDG Base Directory config directories:
     *
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
     *
     * @return string[]
     */
    public static function getConfigDirs()
    {
        $xdg = new Xdg();

        return self::getDirNames($xdg->getConfigDirs());
    }

    /**
     * Get potential home config directory paths.
     *
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the
     * XDG Base Directory home config directory:
     *
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
     *
     * @return string[]
     */
    public static function getHomeConfigDirs()
    {
        $xdg = new Xdg();

        return self::getDirNames(array($xdg->getHomeConfigDir()));
    }

    /**
     * Get the current home config directory.
     *
     * Returns the highest precedence home config directory which actually
     * exists. If none of them exists, returns the highest precedence home
     * config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
     * everywhere else).
     *
     * @see self::getHomeConfigDirs
     *
     * @return string
     */
    public static function getCurrentConfigDir()
    {
        $configDirs = self::getHomeConfigDirs();
        foreach ($configDirs as $configDir) {
            if (@is_dir($configDir)) {
                return $configDir;
            }
        }

        return $configDirs[0];
    }

    /**
     * Find real config files in config directories.
     *
     * @param string[] $names     Config file names
     * @param string   $configDir Optionally use a specific config directory
     *
     * @return string[]
     */
    public static function getConfigFiles(array $names, $configDir = null)
    {
        $dirs = ($configDir === null) ? self::getConfigDirs() : array($configDir);

        return self::getRealFiles($dirs, $names);
    }

    /**
     * Get potential data directory paths.
     *
     * If a `dataDir` option was explicitly set, returns an array containing
     * just that directory.
     *
     * Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories:
     *
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
     *
     * @return string[]
     */
    public static function getDataDirs()
    {
        $xdg = new Xdg();

        return self::getDirNames($xdg->getDataDirs());
    }

    /**
     * Find real data files in config directories.
     *
     * @param string[] $names   Config file names
     * @param string   $dataDir Optionally use a specific config directory
     *
     * @return string[]
     */
    public static function getDataFiles(array $names, $dataDir = null)
    {
        $dirs = ($dataDir === null) ? self::getDataDirs() : array($dataDir);

        return self::getRealFiles($dirs, $names);
    }

    /**
     * Get a runtime directory.
     *
     * Defaults to  `/psysh` inside the system's temp dir.
     *
     * @return string
     */
    public static function getRuntimeDir()
    {
        $xdg = new Xdg();

        try {
            // Check with XDG to see if there's an explicit one to return...
            return $xdg->getRuntimeDir(true) . '/psysh';
        } catch (\RuntimeException $e) {
            // Well. That didn't work. One of the next ones should.
        }

        // Since we don't have an explicit XDG directory, and since the XDG
        // library doesn't really work on Windows, let's throw the Windows users
        // a bone here.
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            return strtr(sys_get_temp_dir(), '\\', '/') . '/psysh';
        }

        return $xdg->getRuntimeDir(false) . '/psysh';
    }

    private static function getDirNames(array $baseDirs)
    {
        $dirs = array_map(function ($dir) {
            return strtr($dir, '\\', '/') . '/psysh';
        }, $baseDirs);

        // Add ~/.psysh
        if ($home = getenv('HOME')) {
            $dirs[] = strtr($home, '\\', '/') . '/.psysh';
        }

        // Add some Windows specific ones :)
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
            if ($appData = getenv('APPDATA')) {
                // AppData gets preference
                array_unshift($dirs, strtr($appData, '\\', '/') . '/PsySH');
            }

            $dir = strtr(getenv('HOMEDRIVE') . '/' . getenv('HOMEPATH'), '\\', '/') . '/.psysh';
            if (!in_array($dir, $dirs)) {
                $dirs[] = $dir;
            }
        }

        return $dirs;
    }

    private static function getRealFiles(array $dirNames, array $fileNames)
    {
        $files = array();
        foreach ($dirNames as $dir) {
            foreach ($fileNames as $name) {
                $file = $dir . '/' . $name;
                if (@is_file($file)) {
                    $files[] = $file;
                }
            }
        }

        return $files;
    }

    /**
     * Ensure that $file exists and is writable, make the parent directory if necessary.
     *
     * Generates E_USER_NOTICE error if either $file or its directory is not writable.
     *
     * @param string $file
     *
     * @return string|false Full path to $file, or false if file is not writable
     */
    public static function touchFileWithMkdir($file)
    {
        if (file_exists($file)) {
            if (is_writable($file)) {
                return $file;
            }

            trigger_error(sprintf('Writing to %s is not allowed.', $file), E_USER_NOTICE);

            return false;
        }

        $dir = dirname($file);

        if (!is_dir($dir)) {
            // Just try making it and see if it works
            @mkdir($dir, 0700, true);
        }

        if (!is_dir($dir) || !is_writable($dir)) {
            trigger_error(sprintf('Writing to %s is not allowed.', $dir), E_USER_NOTICE);

            return false;
        }

        touch($file);

        return $file;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use Psy\Exception\DeprecatedException;
use Psy\Exception\RuntimeException;
use Psy\ExecutionLoop\ForkingLoop;
use Psy\ExecutionLoop\Loop;
use Psy\Output\OutputPager;
use Psy\Output\ShellOutput;
use Psy\Readline\GNUReadline;
use Psy\Readline\HoaConsole;
use Psy\Readline\Libedit;
use Psy\Readline\Readline;
use Psy\Readline\Transient;
use Psy\TabCompletion\AutoCompleter;
use Psy\VarDumper\Presenter;
use Psy\VersionUpdater\Checker;
use Psy\VersionUpdater\GitHubChecker;
use Psy\VersionUpdater\IntervalChecker;
use Psy\VersionUpdater\NoopChecker;
use XdgBaseDir\Xdg;

/**
 * The Psy Shell configuration.
 */
class Configuration
{
    const COLOR_MODE_AUTO = 'auto';
    const COLOR_MODE_FORCED = 'forced';
    const COLOR_MODE_DISABLED = 'disabled';

    private static $AVAILABLE_OPTIONS = array(
        'codeCleaner',
        'colorMode',
        'configDir',
        'dataDir',
        'defaultIncludes',
        'eraseDuplicates',
        'errorLoggingLevel',
        'forceArrayIndexes',
        'historySize',
        'loop',
        'manualDbFile',
        'pager',
        'prompt',
        'requireSemicolons',
        'runtimeDir',
        'startupMessage',
        'tabCompletion',
        'updateCheck',
        'useBracketedPaste',
        'usePcntl',
        'useReadline',
        'useUnicode',
        'warnOnMultipleConfigs',
    );

    private $defaultIncludes;
    private $configDir;
    private $dataDir;
    private $runtimeDir;
    private $configFile;
    /** @var string|false */
    private $historyFile;
    private $historySize;
    private $eraseDuplicates;
    private $manualDbFile;
    private $hasReadline;
    private $useReadline;
    private $useBracketedPaste;
    private $hasPcntl;
    private $usePcntl;
    private $newCommands = array();
    private $requireSemicolons = false;
    private $useUnicode;
    private $tabCompletion;
    private $tabCompletionMatchers = array();
    private $errorLoggingLevel = E_ALL;
    private $warnOnMultipleConfigs = false;
    private $colorMode;
    private $updateCheck;
    private $startupMessage;
    private $forceArrayIndexes = false;

    // services
    private $readline;
    private $output;
    private $shell;
    private $cleaner;
    private $pager;
    private $loop;
    private $manualDb;
    private $presenter;
    private $completer;
    private $checker;
    private $prompt;

    /**
     * Construct a Configuration instance.
     *
     * Optionally, supply an array of configuration values to load.
     *
     * @param array $config Optional array of configuration values
     */
    public function __construct(array $config = array())
    {
        $this->setColorMode(self::COLOR_MODE_AUTO);

        // explicit configFile option
        if (isset($config['configFile'])) {
            $this->configFile = $config['configFile'];
        } elseif ($configFile = getenv('PSYSH_CONFIG')) {
            $this->configFile = $configFile;
        }

        // legacy baseDir option
        if (isset($config['baseDir'])) {
            $msg = "The 'baseDir' configuration option is deprecated. " .
                "Please specify 'configDir' and 'dataDir' options instead.";
            throw new DeprecatedException($msg);
        }

        unset($config['configFile'], $config['baseDir']);

        // go go gadget, config!
        $this->loadConfig($config);
        $this->init();
    }

    /**
     * Initialize the configuration.
     *
     * This checks for the presence of Readline and Pcntl extensions.
     *
     * If a config file is available, it will be loaded and merged with the current config.
     *
     * If no custom config file was specified and a local project config file
     * is available, it will be loaded and merged with the current config.
     */
    public function init()
    {
        // feature detection
        $this->hasReadline = function_exists('readline');
        $this->hasPcntl    = function_exists('pcntl_signal') && function_exists('posix_getpid');

        if ($configFile = $this->getConfigFile()) {
            $this->loadConfigFile($configFile);
        }

        if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) {
            $this->loadConfigFile($localConfig);
        }
    }

    /**
     * Get the current PsySH config file.
     *
     * If a `configFile` option was passed to the Configuration constructor,
     * this file will be returned. If not, all possible config directories will
     * be searched, and the first `config.php` or `rc.php` file which exists
     * will be returned.
     *
     * If you're trying to decide where to put your config file, pick
     *
     *     ~/.config/psysh/config.php
     *
     * @return string
     */
    public function getConfigFile()
    {
        if (isset($this->configFile)) {
            return $this->configFile;
        }

        $files = ConfigPaths::getConfigFiles(array('config.php', 'rc.php'), $this->configDir);

        if (!empty($files)) {
            if ($this->warnOnMultipleConfigs && count($files) > 1) {
                $msg = sprintf('Multiple configuration files found: %s. Using %s', implode($files, ', '), $files[0]);
                trigger_error($msg, E_USER_NOTICE);
            }

            return $files[0];
        }
    }

    /**
     * Get the local PsySH config file.
     *
     * Searches for a project specific config file `.psysh.php` in the current
     * working directory.
     *
     * @return string
     */
    public function getLocalConfigFile()
    {
        $localConfig = getcwd() . '/.psysh.php';

        if (@is_file($localConfig)) {
            return $localConfig;
        }
    }

    /**
     * Load configuration values from an array of options.
     *
     * @param array $options
     */
    public function loadConfig(array $options)
    {
        foreach (self::$AVAILABLE_OPTIONS as $option) {
            if (isset($options[$option])) {
                $method = 'set' . ucfirst($option);
                $this->$method($options[$option]);
            }
        }

        foreach (array('commands', 'tabCompletionMatchers', 'casters') as $option) {
            if (isset($options[$option])) {
                $method = 'add' . ucfirst($option);
                $this->$method($options[$option]);
            }
        }
    }

    /**
     * Load a configuration file (default: `$HOME/.config/psysh/config.php`).
     *
     * This configuration instance will be available to the config file as $config.
     * The config file may directly manipulate the configuration, or may return
     * an array of options which will be merged with the current configuration.
     *
     * @throws \InvalidArgumentException if the config file returns a non-array result
     *
     * @param string $file
     */
    public function loadConfigFile($file)
    {
        $__psysh_config_file__ = $file;
        $load = function ($config) use ($__psysh_config_file__) {
            $result = require $__psysh_config_file__;
            if ($result !== 1) {
                return $result;
            }
        };
        $result = $load($this);

        if (!empty($result)) {
            if (is_array($result)) {
                $this->loadConfig($result);
            } else {
                throw new \InvalidArgumentException('Psy Shell configuration must return an array of options');
            }
        }
    }

    /**
     * Set files to be included by default at the start of each shell session.
     *
     * @param array $includes
     */
    public function setDefaultIncludes(array $includes = array())
    {
        $this->defaultIncludes = $includes;
    }

    /**
     * Get files to be included by default at the start of each shell session.
     *
     * @return array
     */
    public function getDefaultIncludes()
    {
        return $this->defaultIncludes ?: array();
    }

    /**
     * Set the shell's config directory location.
     *
     * @param string $dir
     */
    public function setConfigDir($dir)
    {
        $this->configDir = (string) $dir;
    }

    /**
     * Get the current configuration directory, if any is explicitly set.
     *
     * @return string
     */
    public function getConfigDir()
    {
        return $this->configDir;
    }

    /**
     * Set the shell's data directory location.
     *
     * @param string $dir
     */
    public function setDataDir($dir)
    {
        $this->dataDir = (string) $dir;
    }

    /**
     * Get the current data directory, if any is explicitly set.
     *
     * @return string
     */
    public function getDataDir()
    {
        return $this->dataDir;
    }

    /**
     * Set the shell's temporary directory location.
     *
     * @param string $dir
     */
    public function setRuntimeDir($dir)
    {
        $this->runtimeDir = (string) $dir;
    }

    /**
     * Get the shell's temporary directory location.
     *
     * Defaults to  `/psysh` inside the system's temp dir unless explicitly
     * overridden.
     *
     * @return string
     */
    public function getRuntimeDir()
    {
        if (!isset($this->runtimeDir)) {
            $this->runtimeDir = ConfigPaths::getRuntimeDir();
        }

        if (!is_dir($this->runtimeDir)) {
            mkdir($this->runtimeDir, 0700, true);
        }

        return $this->runtimeDir;
    }

    /**
     * Set the readline history file path.
     *
     * @param string $file
     */
    public function setHistoryFile($file)
    {
        $this->historyFile = ConfigPaths::touchFileWithMkdir($file);
    }

    /**
     * Get the readline history file path.
     *
     * Defaults to `/history` inside the shell's base config dir unless
     * explicitly overridden.
     *
     * @return string
     */
    public function getHistoryFile()
    {
        if (isset($this->historyFile)) {
            return $this->historyFile;
        }

        // Deprecation warning for incorrect psysh_history path.
        // @todo remove this before v0.9.0
        $xdg = new Xdg();
        $oldHistory = $xdg->getHomeConfigDir() . '/psysh_history';
        if (@is_file($oldHistory)) {
            $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
            $newHistory = $dir . '/psysh_history';

            $msg = sprintf(
                "PsySH history file found at '%s'. Please delete it or move it to '%s'.",
                strtr($oldHistory, '\\', '/'),
                $newHistory
            );
            @trigger_error($msg, E_USER_DEPRECATED);
            $this->setHistoryFile($oldHistory);

            return $this->historyFile;
        }

        $files = ConfigPaths::getConfigFiles(array('psysh_history', 'history'), $this->configDir);

        if (!empty($files)) {
            if ($this->warnOnMultipleConfigs && count($files) > 1) {
                $msg = sprintf('Multiple history files found: %s. Using %s', implode($files, ', '), $files[0]);
                trigger_error($msg, E_USER_NOTICE);
            }

            $this->setHistoryFile($files[0]);
        } else {
            // fallback: create our own history file
            $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
            $this->setHistoryFile($dir . '/psysh_history');
        }

        return $this->historyFile;
    }

    /**
     * Set the readline max history size.
     *
     * @param int $value
     */
    public function setHistorySize($value)
    {
        $this->historySize = (int) $value;
    }

    /**
     * Get the readline max history size.
     *
     * @return int
     */
    public function getHistorySize()
    {
        return $this->historySize;
    }

    /**
     * Sets whether readline erases old duplicate history entries.
     *
     * @param bool $value
     */
    public function setEraseDuplicates($value)
    {
        $this->eraseDuplicates = (bool) $value;
    }

    /**
     * Get whether readline erases old duplicate history entries.
     *
     * @return bool
     */
    public function getEraseDuplicates()
    {
        return $this->eraseDuplicates;
    }

    /**
     * Get a temporary file of type $type for process $pid.
     *
     * The file will be created inside the current temporary directory.
     *
     * @see self::getRuntimeDir
     *
     * @param string $type
     * @param int    $pid
     *
     * @return string Temporary file name
     */
    public function getTempFile($type, $pid)
    {
        return tempnam($this->getRuntimeDir(), $type . '_' . $pid . '_');
    }

    /**
     * Get a filename suitable for a FIFO pipe of $type for process $pid.
     *
     * The pipe will be created inside the current temporary directory.
     *
     * @param string $type
     * @param int    $pid
     *
     * @return string Pipe name
     */
    public function getPipe($type, $pid)
    {
        return sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid);
    }

    /**
     * Check whether this PHP instance has Readline available.
     *
     * @return bool True if Readline is available
     */
    public function hasReadline()
    {
        return $this->hasReadline;
    }

    /**
     * Enable or disable Readline usage.
     *
     * @param bool $useReadline
     */
    public function setUseReadline($useReadline)
    {
        $this->useReadline = (bool) $useReadline;
    }

    /**
     * Check whether to use Readline.
     *
     * If `setUseReadline` as been set to true, but Readline is not actually
     * available, this will return false.
     *
     * @return bool True if the current Shell should use Readline
     */
    public function useReadline()
    {
        return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline;
    }

    /**
     * Set the Psy Shell readline service.
     *
     * @param Readline $readline
     */
    public function setReadline(Readline $readline)
    {
        $this->readline = $readline;
    }

    /**
     * Get the Psy Shell readline service.
     *
     * By default, this service uses (in order of preference):
     *
     *  * GNU Readline
     *  * Libedit
     *  * A transient array-based readline emulation.
     *
     * @return Readline
     */
    public function getReadline()
    {
        if (!isset($this->readline)) {
            $className = $this->getReadlineClass();
            $this->readline = new $className(
                $this->getHistoryFile(),
                $this->getHistorySize(),
                $this->getEraseDuplicates()
            );
        }

        return $this->readline;
    }

    /**
     * Get the appropriate Readline implementation class name.
     *
     * @see self::getReadline
     *
     * @return string
     */
    private function getReadlineClass()
    {
        if ($this->useReadline()) {
            if (GNUReadline::isSupported()) {
                return 'Psy\Readline\GNUReadline';
            } elseif (Libedit::isSupported()) {
                return 'Psy\Readline\Libedit';
            } elseif (HoaConsole::isSupported()) {
                return 'Psy\Readline\HoaConsole';
            }
        }

        return 'Psy\Readline\Transient';
    }

    /**
     * Enable or disable bracketed paste.
     *
     * Note that this only works with readline (not libedit) integration for now.
     *
     * @param bool $useBracketedPaste
     */
    public function setUseBracketedPaste($useBracketedPaste)
    {
        $this->useBracketedPaste = (bool) $useBracketedPaste;
    }

    /**
     * Check whether to use bracketed paste with readline.
     *
     * When this works, it's magical. Tabs in pastes don't try to autcomplete.
     * Newlines in paste don't execute code until you get to the end. It makes
     * readline act like you'd expect when pasting.
     *
     * But it often (usually?) does not work. And when it doesn't, it just spews
     * escape codes all over the place and generally makes things ugly :(
     *
     * If `useBracketedPaste` has been set to true, but the current readline
     * implementation is anything besides GNU readline, this will return false.
     *
     * @return bool True if the shell should use bracketed paste
     */
    public function useBracketedPaste()
    {
        // For now, only the GNU readline implementation supports bracketed paste.
        $supported = ($this->getReadlineClass() === 'Psy\Readline\GNUReadline');

        return $supported && $this->useBracketedPaste;

        // @todo mebbe turn this on by default some day?
        // return isset($this->useBracketedPaste) ? ($supported && $this->useBracketedPaste) : $supported;
    }

    /**
     * Check whether this PHP instance has Pcntl available.
     *
     * @return bool True if Pcntl is available
     */
    public function hasPcntl()
    {
        return $this->hasPcntl;
    }

    /**
     * Enable or disable Pcntl usage.
     *
     * @param bool $usePcntl
     */
    public function setUsePcntl($usePcntl)
    {
        $this->usePcntl = (bool) $usePcntl;
    }

    /**
     * Check whether to use Pcntl.
     *
     * If `setUsePcntl` has been set to true, but Pcntl is not actually
     * available, this will return false.
     *
     * @return bool True if the current Shell should use Pcntl
     */
    public function usePcntl()
    {
        return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl;
    }

    /**
     * Enable or disable strict requirement of semicolons.
     *
     * @see self::requireSemicolons()
     *
     * @param bool $requireSemicolons
     */
    public function setRequireSemicolons($requireSemicolons)
    {
        $this->requireSemicolons = (bool) $requireSemicolons;
    }

    /**
     * Check whether to require semicolons on all statements.
     *
     * By default, PsySH will automatically insert semicolons at the end of
     * statements if they're missing. To strictly require semicolons, set
     * `requireSemicolons` to true.
     *
     * @return bool
     */
    public function requireSemicolons()
    {
        return $this->requireSemicolons;
    }

    /**
     * Enable or disable Unicode in PsySH specific output.
     *
     * Note that this does not disable Unicode output in general, it just makes
     * it so PsySH won't output any itself.
     *
     * @param bool $useUnicode
     */
    public function setUseUnicode($useUnicode)
    {
        $this->useUnicode = (bool) $useUnicode;
    }

    /**
     * Check whether to use Unicode in PsySH specific output.
     *
     * Note that this does not disable Unicode output in general, it just makes
     * it so PsySH won't output any itself.
     *
     * @return bool
     */
    public function useUnicode()
    {
        if (isset($this->useUnicode)) {
            return $this->useUnicode;
        }

        // @todo detect `chsh` != 65001 on Windows and return false
        return true;
    }

    /**
     * Set the error logging level.
     *
     * @see self::errorLoggingLevel
     *
     * @param bool $errorLoggingLevel
     */
    public function setErrorLoggingLevel($errorLoggingLevel)
    {
        $this->errorLoggingLevel = (E_ALL | E_STRICT) & $errorLoggingLevel;
    }

    /**
     * Get the current error logging level.
     *
     * By default, PsySH will automatically log all errors, regardless of the
     * current `error_reporting` level. Additionally, if the `error_reporting`
     * level warrants, an ErrorException will be thrown.
     *
     * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it
     * to any valid error_reporting value to log only errors which match that
     * level.
     *
     *     http://php.net/manual/en/function.error-reporting.php
     *
     * @return int
     */
    public function errorLoggingLevel()
    {
        return $this->errorLoggingLevel;
    }

    /**
     * Set a CodeCleaner service instance.
     *
     * @param CodeCleaner $cleaner
     */
    public function setCodeCleaner(CodeCleaner $cleaner)
    {
        $this->cleaner = $cleaner;
    }

    /**
     * Get a CodeCleaner service instance.
     *
     * If none has been explicitly defined, this will create a new instance.
     *
     * @return CodeCleaner
     */
    public function getCodeCleaner()
    {
        if (!isset($this->cleaner)) {
            $this->cleaner = new CodeCleaner();
        }

        return $this->cleaner;
    }

    /**
     * Enable or disable tab completion.
     *
     * @param bool $tabCompletion
     */
    public function setTabCompletion($tabCompletion)
    {
        $this->tabCompletion = (bool) $tabCompletion;
    }

    /**
     * Check whether to use tab completion.
     *
     * If `setTabCompletion` has been set to true, but readline is not actually
     * available, this will return false.
     *
     * @return bool True if the current Shell should use tab completion
     */
    public function getTabCompletion()
    {
        return isset($this->tabCompletion) ? ($this->hasReadline && $this->tabCompletion) : $this->hasReadline;
    }

    /**
     * Set the Shell Output service.
     *
     * @param ShellOutput $output
     */
    public function setOutput(ShellOutput $output)
    {
        $this->output = $output;
    }

    /**
     * Get a Shell Output service instance.
     *
     * If none has been explicitly provided, this will create a new instance
     * with VERBOSITY_NORMAL and the output page supplied by self::getPager
     *
     * @see self::getPager
     *
     * @return ShellOutput
     */
    public function getOutput()
    {
        if (!isset($this->output)) {
            $this->output = new ShellOutput(
                ShellOutput::VERBOSITY_NORMAL,
                $this->getOutputDecorated(),
                null,
                $this->getPager()
            );
        }

        return $this->output;
    }

    /**
     * Get the decoration (i.e. color) setting for the Shell Output service.
     *
     * @return null|bool 3-state boolean corresponding to the current color mode
     */
    public function getOutputDecorated()
    {
        if ($this->colorMode() === self::COLOR_MODE_AUTO) {
            return;
        } elseif ($this->colorMode() === self::COLOR_MODE_FORCED) {
            return true;
        } elseif ($this->colorMode() === self::COLOR_MODE_DISABLED) {
            return false;
        }
    }

    /**
     * Set the OutputPager service.
     *
     * If a string is supplied, a ProcOutputPager will be used which shells out
     * to the specified command.
     *
     * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance
     *
     * @param string|OutputPager $pager
     */
    public function setPager($pager)
    {
        if ($pager && !is_string($pager) && !$pager instanceof OutputPager) {
            throw new \InvalidArgumentException('Unexpected pager instance.');
        }

        $this->pager = $pager;
    }

    /**
     * Get an OutputPager instance or a command for an external Proc pager.
     *
     * If no Pager has been explicitly provided, and Pcntl is available, this
     * will default to `cli.pager` ini value, falling back to `which less`.
     *
     * @return string|OutputPager
     */
    public function getPager()
    {
        if (!isset($this->pager) && $this->usePcntl()) {
            if ($pager = ini_get('cli.pager')) {
                // use the default pager (5.4+)
                $this->pager = $pager;
            } elseif ($less = exec('which less 2>/dev/null')) {
                // check for the presence of less...
                $this->pager = $less . ' -R -S -F -X';
            }
        }

        return $this->pager;
    }

    /**
     * Set the Shell evaluation Loop service.
     *
     * @param Loop $loop
     */
    public function setLoop(Loop $loop)
    {
        $this->loop = $loop;
    }

    /**
     * Get a Shell evaluation Loop service instance.
     *
     * If none has been explicitly defined, this will create a new instance.
     * If Pcntl is available and enabled, the new instance will be a ForkingLoop.
     *
     * @return Loop
     */
    public function getLoop()
    {
        if (!isset($this->loop)) {
            if ($this->usePcntl()) {
                $this->loop = new ForkingLoop($this);
            } else {
                $this->loop = new Loop($this);
            }
        }

        return $this->loop;
    }

    /**
     * Set the Shell autocompleter service.
     *
     * @param AutoCompleter $completer
     */
    public function setAutoCompleter(AutoCompleter $completer)
    {
        $this->completer = $completer;
    }

    /**
     * Get an AutoCompleter service instance.
     *
     * @return AutoCompleter
     */
    public function getAutoCompleter()
    {
        if (!isset($this->completer)) {
            $this->completer = new AutoCompleter();
        }

        return $this->completer;
    }

    /**
     * Get user specified tab completion matchers for the AutoCompleter.
     *
     * @return array
     */
    public function getTabCompletionMatchers()
    {
        return $this->tabCompletionMatchers;
    }

    /**
     * Add additional tab completion matchers to the AutoCompleter.
     *
     * @param array $matchers
     */
    public function addTabCompletionMatchers(array $matchers)
    {
        $this->tabCompletionMatchers = array_merge($this->tabCompletionMatchers, $matchers);
        if (isset($this->shell)) {
            $this->shell->addTabCompletionMatchers($this->tabCompletionMatchers);
        }
    }

    /**
     * Add commands to the Shell.
     *
     * This will buffer new commands in the event that the Shell has not yet
     * been instantiated. This allows the user to specify commands in their
     * config rc file, despite the fact that their file is needed in the Shell
     * constructor.
     *
     * @param array $commands
     */
    public function addCommands(array $commands)
    {
        $this->newCommands = array_merge($this->newCommands, $commands);
        if (isset($this->shell)) {
            $this->doAddCommands();
        }
    }

    /**
     * Internal method for adding commands. This will set any new commands once
     * a Shell is available.
     */
    private function doAddCommands()
    {
        if (!empty($this->newCommands)) {
            $this->shell->addCommands($this->newCommands);
            $this->newCommands = array();
        }
    }

    /**
     * Set the Shell backreference and add any new commands to the Shell.
     *
     * @param Shell $shell
     */
    public function setShell(Shell $shell)
    {
        $this->shell = $shell;
        $this->doAddCommands();
    }

    /**
     * Set the PHP manual database file.
     *
     * This file should be an SQLite database generated from the phpdoc source
     * with the `bin/build_manual` script.
     *
     * @param string $filename
     */
    public function setManualDbFile($filename)
    {
        $this->manualDbFile = (string) $filename;
    }

    /**
     * Get the current PHP manual database file.
     *
     * @return string Default: '~/.local/share/psysh/php_manual.sqlite'
     */
    public function getManualDbFile()
    {
        if (isset($this->manualDbFile)) {
            return $this->manualDbFile;
        }

        $files = ConfigPaths::getDataFiles(array('php_manual.sqlite'), $this->dataDir);
        if (!empty($files)) {
            if ($this->warnOnMultipleConfigs && count($files) > 1) {
                $msg = sprintf('Multiple manual database files found: %s. Using %s', implode($files, ', '), $files[0]);
                trigger_error($msg, E_USER_NOTICE);
            }

            return $this->manualDbFile = $files[0];
        }
    }

    /**
     * Get a PHP manual database connection.
     *
     * @return \PDO
     */
    public function getManualDb()
    {
        if (!isset($this->manualDb)) {
            $dbFile = $this->getManualDbFile();
            if (is_file($dbFile)) {
                try {
                    $this->manualDb = new \PDO('sqlite:' . $dbFile);
                } catch (\PDOException $e) {
                    if ($e->getMessage() === 'could not find driver') {
                        throw new RuntimeException('SQLite PDO driver not found', 0, $e);
                    } else {
                        throw $e;
                    }
                }
            }
        }

        return $this->manualDb;
    }

    /**
     * Add an array of casters definitions.
     *
     * @param array $casters
     */
    public function addCasters(array $casters)
    {
        $this->getPresenter()->addCasters($casters);
    }

    /**
     * Get the Presenter service.
     *
     * @return Presenter
     */
    public function getPresenter()
    {
        if (!isset($this->presenter)) {
            $this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes());
        }

        return $this->presenter;
    }

    /**
     * Enable or disable warnings on multiple configuration or data files.
     *
     * @see self::warnOnMultipleConfigs()
     *
     * @param bool $warnOnMultipleConfigs
     */
    public function setWarnOnMultipleConfigs($warnOnMultipleConfigs)
    {
        $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs;
    }

    /**
     * Check whether to warn on multiple configuration or data files.
     *
     * By default, PsySH will use the file with highest precedence, and will
     * silently ignore all others. With this enabled, a warning will be emitted
     * (but not an exception thrown) if multiple configuration or data files
     * are found.
     *
     * This will default to true in a future release, but is false for now.
     *
     * @return bool
     */
    public function warnOnMultipleConfigs()
    {
        return $this->warnOnMultipleConfigs;
    }

    /**
     * Set the current color mode.
     *
     * @param string $colorMode
     */
    public function setColorMode($colorMode)
    {
        $validColorModes = array(
            self::COLOR_MODE_AUTO,
            self::COLOR_MODE_FORCED,
            self::COLOR_MODE_DISABLED,
        );

        if (in_array($colorMode, $validColorModes)) {
            $this->colorMode = $colorMode;
        } else {
            throw new \InvalidArgumentException('invalid color mode: ' . $colorMode);
        }
    }

    /**
     * Get the current color mode.
     *
     * @return string
     */
    public function colorMode()
    {
        return $this->colorMode;
    }

    /**
     * Set an update checker service instance.
     *
     * @param Checker $checker
     */
    public function setChecker(Checker $checker)
    {
        $this->checker = $checker;
    }

    /**
     * Get an update checker service instance.
     *
     * If none has been explicitly defined, this will create a new instance.
     *
     * @return Checker
     */
    public function getChecker()
    {
        if (!isset($this->checker)) {
            $interval = $this->getUpdateCheck();
            switch ($interval) {
                case Checker::ALWAYS:
                    $this->checker = new GitHubChecker();
                    break;

                case Checker::DAILY:
                case Checker::WEEKLY:
                case Checker::MONTHLY:
                    $checkFile = $this->getUpdateCheckCacheFile();
                    if ($checkFile === false) {
                        $this->checker = new NoopChecker();
                    } else {
                        $this->checker = new IntervalChecker($checkFile, $interval);
                    }
                    break;

                case Checker::NEVER:
                    $this->checker = new NoopChecker();
                    break;
            }
        }

        return $this->checker;
    }

    /**
     * Get the current update check interval.
     *
     * One of 'always', 'daily', 'weekly', 'monthly' or 'never'. If none is
     * explicitly set, default to 'weekly'.
     *
     * @return string
     */
    public function getUpdateCheck()
    {
        return isset($this->updateCheck) ? $this->updateCheck : Checker::WEEKLY;
    }

    /**
     * Set the update check interval.
     *
     * @throws \InvalidArgumentDescription if the update check interval is unknown
     *
     * @param string $interval
     */
    public function setUpdateCheck($interval)
    {
        $validIntervals = array(
            Checker::ALWAYS,
            Checker::DAILY,
            Checker::WEEKLY,
            Checker::MONTHLY,
            Checker::NEVER,
        );

        if (!in_array($interval, $validIntervals)) {
            throw new \InvalidArgumentException('invalid update check interval: ' . $interval);
        }

        $this->updateCheck = $interval;
    }

    /**
     * Get a cache file path for the update checker.
     *
     * @return string|false Return false if config file/directory is not writable
     */
    public function getUpdateCheckCacheFile()
    {
        $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();

        return ConfigPaths::touchFileWithMkdir($dir . '/update_check.json');
    }

    /**
     * Set the startup message.
     *
     * @param string $message
     */
    public function setStartupMessage($message)
    {
        $this->startupMessage = $message;
    }

    /**
     * Get the startup message.
     *
     * @return string|null
     */
    public function getStartupMessage()
    {
        return $this->startupMessage;
    }

    /**
     * Set the prompt.
     *
     * @param string $prompt
     */
    public function setPrompt($prompt)
    {
        $this->prompt = $prompt;
    }

    /**
     * Get the prompt.
     *
     * @return string
     */
    public function getPrompt()
    {
        return $this->prompt;
    }

    /**
     * Get the force array indexes.
     *
     * @return bool
     */
    public function forceArrayIndexes()
    {
        return $this->forceArrayIndexes;
    }

    /**
     * Set the force array indexes.
     *
     * @param bool $forceArrayIndexes
     */
    public function setForceArrayIndexes($forceArrayIndexes)
    {
        $this->forceArrayIndexes = $forceArrayIndexes;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use JakubOnderka\PhpConsoleColor\ConsoleColor;
use JakubOnderka\PhpConsoleHighlighter\Highlighter;

/**
 * Builds `ConsoleColor` instances configured according to the given color mode.
 */
class ConsoleColorFactory
{
    private $colorMode;

    /**
     * @param string $colorMode
     */
    public function __construct($colorMode)
    {
        $this->colorMode = $colorMode;
    }

    /**
     * Get a `ConsoleColor` instance configured according to the given color
     * mode.
     *
     * @return ConsoleColor
     */
    public function getConsoleColor()
    {
        if ($this->colorMode === Configuration::COLOR_MODE_AUTO) {
            return $this->getDefaultConsoleColor();
        } elseif ($this->colorMode === Configuration::COLOR_MODE_FORCED) {
            return $this->getForcedConsoleColor();
        } elseif ($this->colorMode === Configuration::COLOR_MODE_DISABLED) {
            return $this->getDisabledConsoleColor();
        }
    }

    private function getDefaultConsoleColor()
    {
        $color = new ConsoleColor();
        $color->addTheme(Highlighter::LINE_NUMBER, array('blue'));
        $color->addTheme(Highlighter::TOKEN_KEYWORD, array('yellow'));
        $color->addTheme(Highlighter::TOKEN_STRING, array('green'));
        $color->addTheme(Highlighter::TOKEN_COMMENT, array('dark_gray'));

        return $color;
    }

    private function getForcedConsoleColor()
    {
        $color = $this->getDefaultConsoleColor();
        $color->setForceStyle(true);

        return $color;
    }

    private function getDisabledConsoleColor()
    {
        $color = new ConsoleColor();

        $color->addTheme(Highlighter::TOKEN_STRING, array('none'));
        $color->addTheme(Highlighter::TOKEN_COMMENT, array('none'));
        $color->addTheme(Highlighter::TOKEN_KEYWORD, array('none'));
        $color->addTheme(Highlighter::TOKEN_DEFAULT, array('none'));
        $color->addTheme(Highlighter::TOKEN_HTML, array('none'));
        $color->addTheme(Highlighter::ACTUAL_LINE_MARK, array('none'));
        $color->addTheme(Highlighter::LINE_NUMBER, array('none'));

        return $color;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

/**
 * The Shell execution context.
 *
 * This class encapsulates the current variables, most recent return value and
 * exception, and the current namespace.
 */
class Context
{
    private static $specialNames = array('_', '_e', '__out', '__psysh__', 'this');

    // Whitelist a very limited number of command-scope magic variable names.
    // This might be a bad idea, but future me can sort it out.
    private static $commandScopeNames = array(
        '__function', '__method', '__class', '__namespace', '__file', '__line', '__dir',
    );

    private $scopeVariables = array();
    private $commandScopeVariables = array();
    private $returnValue;
    private $lastException;
    private $lastStdout;
    private $boundObject;

    /**
     * Get a context variable.
     *
     * @throws InvalidArgumentException If the variable is not found in the current context
     *
     * @param string $name
     *
     * @return mixed
     */
    public function get($name)
    {
        switch ($name) {
            case '_':
                return $this->returnValue;

            case '_e':
                if (isset($this->lastException)) {
                    return $this->lastException;
                }
                break;

            case '__out':
                if (isset($this->lastStdout)) {
                    return $this->lastStdout;
                }
                break;

            case 'this':
                if (isset($this->boundObject)) {
                    return $this->boundObject;
                }
                break;

            case '__function':
            case '__method':
            case '__class':
            case '__namespace':
            case '__file':
            case '__line':
            case '__dir':
                if (array_key_exists($name, $this->commandScopeVariables)) {
                    return $this->commandScopeVariables[$name];
                }
                break;

            default:
                if (array_key_exists($name, $this->scopeVariables)) {
                    return $this->scopeVariables[$name];
                }
                break;
        }

        throw new \InvalidArgumentException('Unknown variable: $' . $name);
    }

    /**
     * Get all defined variables.
     *
     * @return array
     */
    public function getAll()
    {
        return array_merge($this->scopeVariables, $this->getSpecialVariables());
    }

    /**
     * Get all defined magic variables: $_, $_e, $__out, $__class, $__file, etc.
     *
     * @return array
     */
    public function getSpecialVariables()
    {
        $vars = array(
            '_' => $this->returnValue,
        );

        if (isset($this->lastException)) {
            $vars['_e'] = $this->lastException;
        }

        if (isset($this->lastStdout)) {
            $vars['__out'] = $this->lastStdout;
        }

        if (isset($this->boundObject)) {
            $vars['this'] = $this->boundObject;
        }

        return array_merge($vars, $this->commandScopeVariables);
    }

    /**
     * Set all scope variables.
     *
     * This method does *not* set any of the magic variables: $_, $_e, $__out,
     * $__class, $__file, etc.
     *
     * @param array $vars
     */
    public function setAll(array $vars)
    {
        foreach (self::$specialNames as $key) {
            unset($vars[$key]);
        }

        foreach (self::$commandScopeNames as $key) {
            unset($vars[$key]);
        }

        $this->scopeVariables = $vars;
    }

    /**
     * Set the most recent return value.
     *
     * @param mixed $value
     */
    public function setReturnValue($value)
    {
        $this->returnValue = $value;
    }

    /**
     * Get the most recent return value.
     *
     * @return mixed
     */
    public function getReturnValue()
    {
        return $this->returnValue;
    }

    /**
     * Set the most recent Exception.
     *
     * @param \Exception $e
     */
    public function setLastException(\Exception $e)
    {
        $this->lastException = $e;
    }

    /**
     * Get the most recent Exception.
     *
     * @throws InvalidArgumentException If no Exception has been caught
     *
     * @return null|Exception
     */
    public function getLastException()
    {
        if (!isset($this->lastException)) {
            throw new \InvalidArgumentException('No most-recent exception');
        }

        return $this->lastException;
    }

    /**
     * Set the most recent output from evaluated code.
     *
     * @param string $lastStdout
     */
    public function setLastStdout($lastStdout)
    {
        $this->lastStdout = $lastStdout;
    }

    /**
     * Get the most recent output from evaluated code.
     *
     * @throws InvalidArgumentException If no output has happened yet
     *
     * @return null|string
     */
    public function getLastStdout()
    {
        if (!isset($this->lastStdout)) {
            throw new \InvalidArgumentException('No most-recent output');
        }

        return $this->lastStdout;
    }

    /**
     * Set the bound object ($this variable) for the interactive shell.
     *
     * @param object|null $boundObject
     */
    public function setBoundObject($boundObject)
    {
        $this->boundObject = is_object($boundObject) ? $boundObject : null;
    }

    /**
     * Get the bound object ($this variable) for the interactive shell.
     *
     * @return object|null
     */
    public function getBoundObject()
    {
        return $this->boundObject;
    }

    /**
     * Set command-scope magic variables: $__class, $__file, etc.
     *
     * @param array $commandScopeVariables
     */
    public function setCommandScopeVariables(array $commandScopeVariables)
    {
        $vars = array();
        foreach ($commandScopeVariables as $key => $value) {
            // kind of type check
            if (is_scalar($value) && in_array($key, self::$commandScopeNames)) {
                $vars[$key] = $value;
            }
        }

        $this->commandScopeVariables = $vars;
    }

    /**
     * Get command-scope magic variables: $__class, $__file, etc.
     *
     * @return array
     */
    public function getCommandScopeVariables()
    {
        return $this->commandScopeVariables;
    }

    /**
     * Get unused command-scope magic variables names: __class, __file, etc.
     *
     * This is used by the shell to unset old command-scope variables after a
     * new batch is set.
     *
     * @return array Array of unused variable names
     */
    public function getUnusedCommandScopeVariableNames()
    {
        return array_diff(self::$commandScopeNames, array_keys($this->commandScopeVariables));
    }

    /**
     * Check whether a variable name is a magic variable.
     *
     * @param string $name
     *
     * @return bool
     */
    public static function isSpecialVariableName($name)
    {
        return in_array($name, self::$specialNames) || in_array($name, self::$commandScopeNames);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

/**
 * ContextAware interface.
 *
 * This interface is used to pass the Shell's context into commands and such
 * which require access to the current scope variables.
 */
interface ContextAware
{
    /**
     * Set the Context reference.
     *
     * @param Context $context
     */
    public function setContext(Context $context);
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A break exception, used for halting the Psy Shell.
 */
class BreakException extends \Exception implements Exception
{
    private $rawMessage;

    /**
     * {@inheritdoc}
     */
    public function __construct($message = '', $code = 0, \Exception $previous = null)
    {
        $this->rawMessage = $message;
        parent::__construct(sprintf('Exit:  %s', $message), $code, $previous);
    }

    /**
     * Return a raw (unformatted) version of the error message.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->rawMessage;
    }

    /**
     * Throws BreakException.
     *
     * Since `throw` can not be inserted into arbitrary expressions, it wraps with function call.
     *
     * @throws BreakException
     */
    public static function exitShell()
    {
        throw new self('Goodbye');
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A DeprecatedException for Psy.
 */
class DeprecatedException extends RuntimeException
{
    // This space intentionally left blank.
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A custom error Exception for Psy with a formatted $message.
 */
class ErrorException extends \ErrorException implements Exception
{
    private $rawMessage;

    /**
     * Construct a Psy ErrorException.
     *
     * @param string    $message  (default: "")
     * @param int       $code     (default: 0)
     * @param int       $severity (default: 1)
     * @param string    $filename (default: null)
     * @param int       $lineno   (default: null)
     * @param Exception $previous (default: null)
     */
    public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, $previous = null)
    {
        $this->rawMessage = $message;

        if (!empty($filename) && preg_match('{Psy[/\\\\]ExecutionLoop}', $filename)) {
            $filename = '';
        }

        switch ($severity) {
            case E_STRICT:
                $type = 'Strict error';
                break;

            case E_NOTICE:
            case E_USER_NOTICE:
                $type = 'Notice';
                break;

            case E_WARNING:
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
            case E_USER_WARNING:
                $type = 'Warning';
                break;

            case E_DEPRECATED:
            case E_USER_DEPRECATED:
                $type = 'Deprecated';
                break;

            case E_RECOVERABLE_ERROR:
                $type = 'Recoverable fatal error';
                break;

            default:
                $type = 'Error';
                break;
        }

        $message = sprintf('PHP %s:  %s%s on line %d', $type, $message, $filename ? ' in ' . $filename : '', $lineno);
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
    }

    /**
     * Get the raw (unformatted) message for this error.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->rawMessage;
    }

    /**
     * Helper for throwing an ErrorException.
     *
     * This allows us to:
     *
     *     set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
     *
     * @throws ErrorException
     *
     * @param int    $errno   Error type
     * @param string $errstr  Message
     * @param string $errfile Filename
     * @param int    $errline Line number
     */
    public static function throwException($errno, $errstr, $errfile, $errline)
    {
        throw new self($errstr, 0, $errno, $errfile, $errline);
    }

    /**
     * Create an ErrorException from an Error.
     *
     * @param \Error $e
     *
     * @return ErrorException
     */
    public static function fromError(\Error $e)
    {
        return new self($e->getMessage(), $e->getCode(), 1, $e->getFile(), $e->getLine(), $e);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * An interface for Psy Exceptions.
 */
interface Exception
{
    /**
     * This is the only thing, really...
     *
     * Return a raw (unformatted) version of the message.
     *
     * @return string
     */
    public function getRawMessage();
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A "fatal error" Exception for Psy.
 */
class FatalErrorException extends \ErrorException implements Exception
{
    private $rawMessage;

    /**
     * Create a fatal error.
     *
     * @param string     $message  (default: "")
     * @param int        $code     (default: 0)
     * @param int        $severity (default: 1)
     * @param string     $filename (default: null)
     * @param int        $lineno   (default: null)
     * @param \Exception $previous (default: null)
     */
    public function __construct($message = '', $code = 0, $severity = 1, $filename = null, $lineno = null, $previous = null)
    {
        // Since these are basically always PHP Parser Node line numbers, treat -1 as null.
        if ($lineno === -1) {
            $lineno = null;
        }

        $this->rawMessage = $message;
        $message = sprintf('PHP Fatal error:  %s in %s on line %d', $message, $filename ?: "eval()'d code", $lineno);
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
    }

    /**
     * Return a raw (unformatted) version of the error message.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->rawMessage;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A "parse error" Exception for Psy.
 */
class ParseErrorException extends \PhpParser\Error implements Exception
{
    /**
     * Constructor!
     *
     * @param string $message (default: "")
     * @param int    $line    (default: -1)
     */
    public function __construct($message = '', $line = -1)
    {
        $message = sprintf('PHP Parse error: %s', $message);
        parent::__construct($message, $line);
    }

    /**
     * Create a ParseErrorException from a PhpParser Error.
     *
     * @param \PhpParser\Error $e
     *
     * @return ParseErrorException
     */
    public static function fromParseError(\PhpParser\Error $e)
    {
        return new self($e->getRawMessage(), $e->getStartLine());
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A RuntimeException for Psy.
 */
class RuntimeException extends \RuntimeException implements Exception
{
    private $rawMessage;

    /**
     * Make this bad boy.
     *
     * @param string     $message  (default: "")
     * @param int        $code     (default: 0)
     * @param \Exception $previous (default: null)
     */
    public function __construct($message = '', $code = 0, \Exception $previous = null)
    {
        $this->rawMessage = $message;
        parent::__construct($message, $code, $previous);
    }

    /**
     * Return a raw (unformatted) version of the error message.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->rawMessage;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A throw-up exception, used for throwing an exception out of the Psy Shell.
 */
class ThrowUpException extends \Exception implements Exception
{
    /**
     * {@inheritdoc}
     */
    public function __construct(\Exception $exception)
    {
        $message = sprintf("Throwing %s with message '%s'", get_class($exception), $exception->getMessage());
        parent::__construct($message, $exception->getCode(), $exception);
    }

    /**
     * Return a raw (unformatted) version of the error message.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->getPrevious()->getMessage();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Exception;

/**
 * A "type error" Exception for Psy.
 */
class TypeErrorException extends \Exception implements Exception
{
    private $rawMessage;

    /**
     * Constructor!
     *
     * @param string $message (default: "")
     * @param int    $code    (default: 0)
     */
    public function __construct($message = '', $code = 0)
    {
        $this->rawMessage = $message;
        $message = preg_replace('/, called in .*?: eval\\(\\)\'d code/', '', $message);
        parent::__construct(sprintf('TypeError: %s', $message), $code);
    }

    /**
     * Get the raw (unformatted) message for this error.
     *
     * @return string
     */
    public function getRawMessage()
    {
        return $this->rawMessage;
    }

    /**
     * Create a TypeErrorException from a TypeError.
     *
     * @param \TypeError $e
     *
     * @return TypeErrorException
     */
    public static function fromTypeError(\TypeError $e)
    {
        return new self($e->getMessage(), $e->getLine());
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\ExecutionLoop;

use Psy\Context;
use Psy\Shell;

/**
 * A forking version of the Psy Shell execution loop.
 *
 * This version is preferred, as it won't die prematurely if user input includes
 * a fatal error, such as redeclaring a class or function.
 */
class ForkingLoop extends Loop
{
    private $savegame;

    /**
     * Run the execution loop.
     *
     * Forks into a master and a loop process. The loop process will handle the
     * evaluation of all instructions, then return its state via a socket upon
     * completion.
     *
     * @param Shell $shell
     */
    public function run(Shell $shell)
    {
        list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);

        if (!$up) {
            throw new \RuntimeException('Unable to create socket pair.');
        }

        $pid = pcntl_fork();
        if ($pid < 0) {
            throw new \RuntimeException('Unable to start execution loop.');
        } elseif ($pid > 0) {
            // This is the main thread. We'll just wait for a while.

            // We won't be needing this one.
            fclose($up);

            // Wait for a return value from the loop process.
            $read   = array($down);
            $write  = null;
            $except = null;
            if (stream_select($read, $write, $except, null) === false) {
                throw new \RuntimeException('Error waiting for execution loop.');
            }

            $content = stream_get_contents($down);
            fclose($down);

            if ($content) {
                $shell->setScopeVariables(@unserialize($content));
            }

            return;
        }

        // This is the child process. It's going to do all the work.
        if (function_exists('setproctitle')) {
            setproctitle('psysh (loop)');
        }

        // We won't be needing this one.
        fclose($down);

        // Let's do some processing.
        parent::run($shell);

        // Send the scope variables back up to the main thread
        fwrite($up, $this->serializeReturn($shell->getScopeVariables(false)));
        fclose($up);

        posix_kill(posix_getpid(), SIGKILL);
    }

    /**
     * Create a savegame at the start of each loop iteration.
     */
    public function beforeLoop()
    {
        $this->createSavegame();
    }

    /**
     * Clean up old savegames at the end of each loop iteration.
     */
    public function afterLoop()
    {
        // if there's an old savegame hanging around, let's kill it.
        if (isset($this->savegame)) {
            posix_kill($this->savegame, SIGKILL);
            pcntl_signal_dispatch();
        }
    }

    /**
     * Create a savegame fork.
     *
     * The savegame contains the current execution state, and can be resumed in
     * the event that the worker dies unexpectedly (for example, by encountering
     * a PHP fatal error).
     */
    private function createSavegame()
    {
        // the current process will become the savegame
        $this->savegame = posix_getpid();

        $pid = pcntl_fork();
        if ($pid < 0) {
            throw new \RuntimeException('Unable to create savegame fork.');
        } elseif ($pid > 0) {
            // we're the savegame now... let's wait and see what happens
            pcntl_waitpid($pid, $status);

            // worker exited cleanly, let's bail
            if (!pcntl_wexitstatus($status)) {
                posix_kill(posix_getpid(), SIGKILL);
            }

            // worker didn't exit cleanly, we'll need to have another go
            $this->createSavegame();
        }
    }

    /**
     * Serialize all serializable return values.
     *
     * A naïve serialization will run into issues if there is a Closure or
     * SimpleXMLElement (among other things) in scope when exiting the execution
     * loop. We'll just ignore these unserializable classes, and serialize what
     * we can.
     *
     * @param array $return
     *
     * @return string
     */
    private function serializeReturn(array $return)
    {
        $serializable = array();

        foreach ($return as $key => $value) {
            // No need to return magic variables
            if (Context::isSpecialVariableName($key)) {
                continue;
            }

            // Resources and Closures don't error, but they don't serialize well either.
            if (is_resource($value) || $value instanceof \Closure) {
                continue;
            }

            try {
                @serialize($value);
                $serializable[$key] = $value;
            } catch (\Exception $e) {
                // we'll just ignore this one...
            } catch (\Throwable $e) {
                // and this one too...
            }
        }

        return @serialize($serializable);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\ExecutionLoop;

use Psy\Configuration;
use Psy\Exception\BreakException;
use Psy\Exception\ErrorException;
use Psy\Exception\ThrowUpException;
use Psy\Exception\TypeErrorException;
use Psy\Shell;

/**
 * The Psy Shell execution loop.
 */
class Loop
{
    const NOOP_INPUT = 'return null;';

    /**
     * Loop constructor.
     *
     * The non-forking loop doesn't have much use for Configuration, so we'll
     * just ignore it.
     *
     * @param Configuration $config
     */
    public function __construct(Configuration $config)
    {
        // don't need this
    }

    /**
     * Run the execution loop.
     *
     * @throws ThrowUpException if thrown by the `throw-up` command
     *
     * @param Shell $shell
     */
    public function run(Shell $shell)
    {
        $loop = function ($__psysh__) {
            // Load user-defined includes
            set_error_handler(array($__psysh__, 'handleError'));
            try {
                foreach ($__psysh__->getIncludes() as $__psysh_include__) {
                    include $__psysh_include__;
                }
            } catch (\Exception $_e) {
                $__psysh__->writeException($_e);
            }
            restore_error_handler();
            unset($__psysh_include__);

            extract($__psysh__->getScopeVariables(false));

            do {
                $__psysh__->beforeLoop();
                $__psysh__->setScopeVariables(get_defined_vars());

                try {
                    // read a line, see if we should eval
                    $__psysh__->getInput();

                    // evaluate the current code buffer
                    ob_start(
                        array($__psysh__, 'writeStdout'),
                        version_compare(PHP_VERSION, '5.4', '>=') ? 1 : 2
                    );

                    // Let PsySH inject some magic variables back into the
                    // shell scope... things like $__class, and $__file set by
                    // reflection commands
                    extract($__psysh__->getSpecialScopeVariables(false));

                    // And unset any magic variables which are no longer needed
                    foreach ($__psysh__->getUnusedCommandScopeVariableNames() as $__psysh_var_name__) {
                        unset($$__psysh_var_name__, $__psysh_var_name__);
                    }

                    set_error_handler(array($__psysh__, 'handleError'));
                    $_ = eval($__psysh__->flushCode() ?: Loop::NOOP_INPUT);
                    restore_error_handler();

                    ob_end_flush();

                    $__psysh__->writeReturnValue($_);
                } catch (BreakException $_e) {
                    restore_error_handler();
                    if (ob_get_level() > 0) {
                        ob_end_clean();
                    }
                    $__psysh__->writeException($_e);

                    return;
                } catch (ThrowUpException $_e) {
                    restore_error_handler();
                    if (ob_get_level() > 0) {
                        ob_end_clean();
                    }
                    $__psysh__->writeException($_e);

                    throw $_e;
                } catch (\TypeError $_e) {
                    restore_error_handler();
                    if (ob_get_level() > 0) {
                        ob_end_clean();
                    }
                    $__psysh__->writeException(TypeErrorException::fromTypeError($_e));
                } catch (\Error $_e) {
                    restore_error_handler();
                    if (ob_get_level() > 0) {
                        ob_end_clean();
                    }
                    $__psysh__->writeException(ErrorException::fromError($_e));
                } catch (\Exception $_e) {
                    restore_error_handler();
                    if (ob_get_level() > 0) {
                        ob_end_clean();
                    }
                    $__psysh__->writeException($_e);
                }

                $__psysh__->afterLoop();
            } while (true);
        };

        // bind the closure to $this from the shell scope variables...
        if (self::bindLoop()) {
            $that = $shell->getBoundObject();
            if (is_object($that)) {
                $loop = $loop->bindTo($that, get_class($that));
            } else {
                $loop = $loop->bindTo(null, null);
            }
        }

        $loop($shell);
    }

    /**
     * A beforeLoop callback.
     *
     * This is executed at the start of each loop iteration. In the default
     * (non-forking) loop implementation, this is a no-op.
     */
    public function beforeLoop()
    {
        // no-op
    }

    /**
     * A afterLoop callback.
     *
     * This is executed at the end of each loop iteration. In the default
     * (non-forking) loop implementation, this is a no-op.
     */
    public function afterLoop()
    {
        // no-op
    }

    /**
     * Decide whether to bind the execution loop.
     *
     * @return bool
     */
    protected static function bindLoop()
    {
        // skip binding on HHVM <= 3.5.0
        // see https://github.com/facebook/hhvm/issues/1203
        if (defined('HHVM_VERSION')) {
            return version_compare(HHVM_VERSION, '3.5.0', '>=');
        }

        return version_compare(PHP_VERSION, '5.4', '>=');
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

use JakubOnderka\PhpConsoleHighlighter\Highlighter;
use Psy\Configuration;
use Psy\ConsoleColorFactory;
use Psy\Exception\RuntimeException;

/**
 * A pretty-printer for code.
 */
class CodeFormatter implements Formatter
{
    /**
     * Format the code represented by $reflector.
     *
     * @param \Reflector  $reflector
     * @param null|string $colorMode (default: null)
     *
     * @return string formatted code
     */
    public static function format(\Reflector $reflector, $colorMode = null)
    {
        if (!self::isReflectable($reflector)) {
            throw new RuntimeException('Source code unavailable.');
        }

        $colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO;

        if ($reflector instanceof \ReflectionGenerator) {
            $reflector = $reflector->getFunction();
        }

        if ($fileName = $reflector->getFileName()) {
            if (!is_file($fileName)) {
                throw new RuntimeException('Source code unavailable.');
            }

            $file  = file_get_contents($fileName);
            $start = $reflector->getStartLine();
            $end   = $reflector->getEndLine() - $start;

            $factory = new ConsoleColorFactory($colorMode);
            $colors = $factory->getConsoleColor();
            $highlighter = new Highlighter($colors);

            return $highlighter->getCodeSnippet($file, $start, 0, $end);
        } else {
            throw new RuntimeException('Source code unavailable.');
        }
    }

    /**
     * Check whether a Reflector instance is reflectable by this formatter.
     *
     * @param \Reflector $reflector
     *
     * @return bool
     */
    private static function isReflectable(\Reflector $reflector)
    {
        return $reflector instanceof \ReflectionClass ||
            $reflector instanceof \ReflectionFunctionAbstract ||
            $reflector instanceof \ReflectionGenerator;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

use Psy\Util\Docblock;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * A pretty-printer for docblocks.
 */
class DocblockFormatter implements Formatter
{
    private static $vectorParamTemplates = array(
        'type' => 'info',
        'var'  => 'strong',
    );

    /**
     * Format a docblock.
     *
     * @param \Reflector $reflector
     *
     * @return string Formatted docblock
     */
    public static function format(\Reflector $reflector)
    {
        $docblock = new Docblock($reflector);
        $chunks   = array();

        if (!empty($docblock->desc)) {
            $chunks[] = '<comment>Description:</comment>';
            $chunks[] = self::indent(OutputFormatter::escape($docblock->desc), '  ');
            $chunks[] = '';
        }

        if (!empty($docblock->tags)) {
            foreach ($docblock::$vectors as $name => $vector) {
                if (isset($docblock->tags[$name])) {
                    $chunks[] = sprintf('<comment>%s:</comment>', self::inflect($name));
                    $chunks[] = self::formatVector($vector, $docblock->tags[$name]);
                    $chunks[] = '';
                }
            }

            $tags = self::formatTags(array_keys($docblock::$vectors), $docblock->tags);
            if (!empty($tags)) {
                $chunks[] = $tags;
                $chunks[] = '';
            }
        }

        return rtrim(implode("\n", $chunks));
    }

    /**
     * Format a docblock vector, for example, `@throws`, `@param`, or `@return`.
     *
     * @see DocBlock::$vectors
     *
     * @param array $vector
     * @param array $lines
     *
     * @return string
     */
    private static function formatVector(array $vector, array $lines)
    {
        $template = array(' ');
        foreach ($vector as $type) {
            $max = 0;
            foreach ($lines as $line) {
                $chunk = $line[$type];
                $cur = empty($chunk) ? 0 : strlen($chunk) + 1;
                if ($cur > $max) {
                    $max = $cur;
                }
            }

            $template[] = self::getVectorParamTemplate($type, $max);
        }
        $template = implode(' ', $template);

        return implode("\n", array_map(function ($line) use ($template) {
            $escaped = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $line);

            return rtrim(vsprintf($template, $escaped));
        }, $lines));
    }

    /**
     * Format docblock tags.
     *
     * @param array $skip Tags to exclude
     * @param array $tags Tags to format
     *
     * @return string formatted tags
     */
    private static function formatTags(array $skip, array $tags)
    {
        $chunks = array();

        foreach ($tags as $name => $values) {
            if (in_array($name, $skip)) {
                continue;
            }

            foreach ($values as $value) {
                $chunks[] = sprintf('<comment>%s%s</comment> %s', self::inflect($name), empty($value) ? '' : ':', OutputFormatter::escape($value));
            }

            $chunks[] = '';
        }

        return implode("\n", $chunks);
    }

    /**
     * Get a docblock vector template.
     *
     * @param string $type Vector type
     * @param int    $max  Pad width
     *
     * @return string
     */
    private static function getVectorParamTemplate($type, $max)
    {
        if (!isset(self::$vectorParamTemplates[$type])) {
            return sprintf('%%-%ds', $max);
        }

        return sprintf('<%s>%%-%ds</%s>', self::$vectorParamTemplates[$type], $max, self::$vectorParamTemplates[$type]);
    }

    /**
     * Indent a string.
     *
     * @param string $text   String to indent
     * @param string $indent (default: '  ')
     *
     * @return string
     */
    private static function indent($text, $indent = '  ')
    {
        return $indent . str_replace("\n", "\n" . $indent, $text);
    }

    /**
     * Convert underscored or whitespace separated words into sentence case.
     *
     * @param string $text
     *
     * @return string
     */
    private static function inflect($text)
    {
        $words = trim(preg_replace('/[\s_-]+/', ' ', preg_replace('/([a-z])([A-Z])/', '$1 $2', $text)));

        return implode(' ', array_map('ucfirst', explode(' ', $words)));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

/**
 * Formatter interface.
 */
interface Formatter
{
    /**
     * @param \Reflector $reflector
     *
     * @return string
     */
    public static function format(\Reflector $reflector);
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

use Psy\Reflection\ReflectionConstant;
use Psy\Reflection\ReflectionLanguageConstruct;
use Psy\Util\Json;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * An abstract representation of a function, class or property signature.
 */
class SignatureFormatter implements Formatter
{
    /**
     * Format a signature for the given reflector.
     *
     * Defers to subclasses to do the actual formatting.
     *
     * @param \Reflector $reflector
     *
     * @return string Formatted signature
     */
    public static function format(\Reflector $reflector)
    {
        switch (true) {
            case $reflector instanceof \ReflectionFunction:
            case $reflector instanceof ReflectionLanguageConstruct:
                return self::formatFunction($reflector);

            // this case also covers \ReflectionObject:
            case $reflector instanceof \ReflectionClass:
                return self::formatClass($reflector);

            case $reflector instanceof ReflectionConstant:
                return self::formatConstant($reflector);

            case $reflector instanceof \ReflectionMethod:
                return self::formatMethod($reflector);

            case $reflector instanceof \ReflectionProperty:
                return self::formatProperty($reflector);

            default:
                throw new \InvalidArgumentException('Unexpected Reflector class: ' . get_class($reflector));
        }
    }

    /**
     * Print the signature name.
     *
     * @param \Reflector $reflector
     *
     * @return string Formatted name
     */
    public static function formatName(\Reflector $reflector)
    {
        return $reflector->getName();
    }

    /**
     * Print the method, property or class modifiers.
     *
     * Technically this should be a trait. Can't wait for 5.4 :)
     *
     * @param \Reflector $reflector
     *
     * @return string Formatted modifiers
     */
    private static function formatModifiers(\Reflector $reflector)
    {
        return implode(' ', array_map(function ($modifier) {
            return sprintf('<keyword>%s</keyword>', $modifier);
        }, \Reflection::getModifierNames($reflector->getModifiers())));
    }

    /**
     * Format a class signature.
     *
     * @param \ReflectionClass $reflector
     *
     * @return string Formatted signature
     */
    private static function formatClass(\ReflectionClass $reflector)
    {
        $chunks = array();

        if ($modifiers = self::formatModifiers($reflector)) {
            $chunks[] = $modifiers;
        }

        if (version_compare(PHP_VERSION, '5.4', '>=') && $reflector->isTrait()) {
            $chunks[] = 'trait';
        } else {
            $chunks[] = $reflector->isInterface() ? 'interface' : 'class';
        }

        $chunks[] = sprintf('<class>%s</class>', self::formatName($reflector));

        if ($parent = $reflector->getParentClass()) {
            $chunks[] = 'extends';
            $chunks[] = sprintf('<class>%s</class>', $parent->getName());
        }

        $interfaces = $reflector->getInterfaceNames();
        if (!empty($interfaces)) {
            sort($interfaces);

            $chunks[] = 'implements';
            $chunks[] = implode(', ', array_map(function ($name) {
                return sprintf('<class>%s</class>', $name);
            }, $interfaces));
        }

        return implode(' ', $chunks);
    }

    /**
     * Format a constant signature.
     *
     * @param ReflectionConstant $reflector
     *
     * @return string Formatted signature
     */
    private static function formatConstant(ReflectionConstant $reflector)
    {
        $value = $reflector->getValue();
        $style = self::getTypeStyle($value);

        return sprintf(
            '<keyword>const</keyword> <const>%s</const> = <%s>%s</%s>',
            self::formatName($reflector),
            $style,
            OutputFormatter::escape(Json::encode($value)),
            $style
        );
    }

    /**
     * Helper for getting output style for a given value's type.
     *
     * @param mixed $value
     *
     * @return string
     */
    private static function getTypeStyle($value)
    {
        if (is_int($value) || is_float($value)) {
            return 'number';
        } elseif (is_string($value)) {
            return 'string';
        } elseif (is_bool($value) || is_null($value)) {
            return 'bool';
        } else {
            return 'strong';
        }
    }

    /**
     * Format a property signature.
     *
     * @param \ReflectionProperty $reflector
     *
     * @return string Formatted signature
     */
    private static function formatProperty(\ReflectionProperty $reflector)
    {
        return sprintf(
            '%s <strong>$%s</strong>',
            self::formatModifiers($reflector),
            $reflector->getName()
        );
    }

    /**
     * Format a function signature.
     *
     * @param \ReflectionFunction $reflector
     *
     * @return string Formatted signature
     */
    private static function formatFunction(\ReflectionFunctionAbstract $reflector)
    {
        return sprintf(
            '<keyword>function</keyword> %s<function>%s</function>(%s)',
            $reflector->returnsReference() ? '&' : '',
            self::formatName($reflector),
            implode(', ', self::formatFunctionParams($reflector))
        );
    }

    /**
     * Format a method signature.
     *
     * @param \ReflectionMethod $reflector
     *
     * @return string Formatted signature
     */
    private static function formatMethod(\ReflectionMethod $reflector)
    {
        return sprintf(
            '%s %s',
            self::formatModifiers($reflector),
            self::formatFunction($reflector)
        );
    }

    /**
     * Print the function params.
     *
     * @param \ReflectionFunctionAbstract $reflector
     *
     * @return string
     */
    private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector)
    {
        $params = array();
        foreach ($reflector->getParameters() as $param) {
            $hint = '';
            try {
                if ($param->isArray()) {
                    $hint = '<keyword>array</keyword> ';
                } elseif ($class = $param->getClass()) {
                    $hint = sprintf('<class>%s</class> ', $class->getName());
                }
            } catch (\Exception $e) {
                // sometimes we just don't know...
                // bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou.
                // come to think of it, the only time I've seen this is with the intl extension.

                // Hax: we'll try to extract it :P
                $chunks = explode('$' . $param->getName(), (string) $param);
                $chunks = explode(' ', trim($chunks[0]));
                $guess  = end($chunks);

                $hint = sprintf('<urgent>%s</urgent> ', $guess);
            }

            if ($param->isOptional()) {
                if (!$param->isDefaultValueAvailable()) {
                    $value     = 'unknown';
                    $typeStyle = 'urgent';
                } else {
                    $value     = $param->getDefaultValue();
                    $typeStyle = self::getTypeStyle($value);
                    $value     = is_array($value) ? 'array()' : is_null($value) ? 'null' : var_export($value, true);
                }
                $default   = sprintf(' = <%s>%s</%s>', $typeStyle, OutputFormatter::escape($value), $typeStyle);
            } else {
                $default = '';
            }

            $params[] = sprintf(
                '%s%s<strong>$%s</strong>%s',
                $param->isPassedByReference() ? '&' : '',
                $hint,
                $param->getName(),
                $default
            );
        }

        return $params;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Input;

use Symfony\Component\Console\Input\InputArgument;

/**
 * An input argument for code.
 *
 * A CodeArgument must be the final argument of the command. Once all options
 * and other arguments are used, any remaining input until the end of the string
 * is considered part of a single CodeArgument, regardless of spaces, quoting,
 * escaping, etc.
 *
 * This means commands can support crazy input like
 *
 *     parse function() { return "wheee\n"; }
 *
 * ... without having to put the code in a quoted string and escape everything.
 */
class CodeArgument extends InputArgument
{
    /**
     * Constructor.
     *
     * @param string $name        The argument name
     * @param int    $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string $description A description text
     * @param mixed  $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws \InvalidArgumentException When argument mode is not valid
     */
    public function __construct($name, $mode = null, $description = '', $default = null)
    {
        if ($mode & InputArgument::IS_ARRAY) {
            throw new \InvalidArgumentException('Argument mode IS_ARRAY is not valid.');
        }

        parent::__construct($name, $mode, $description, $default);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Input;

use Psy\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;

/**
 * Parse, validate and match --grep, --insensitive and --invert command options.
 */
class FilterOptions
{
    private $filter = false;
    private $pattern;
    private $insensitive;
    private $invert;

    /**
     * Get input option definitions for filtering.
     *
     * @return InputOption[]
     */
    public static function getOptions()
    {
        return array(
            new InputOption('grep',        'G', InputOption::VALUE_REQUIRED, 'Limit to items matching the given pattern (string or regex).'),
            new InputOption('insensitive', 'i', InputOption::VALUE_NONE,     'Case-insensitive search (requires --grep).'),
            new InputOption('invert',      'v', InputOption::VALUE_NONE,     'Inverted search (requires --grep).'),
        );
    }

    /**
     * Bind input and prepare filter.
     *
     * @param InputInterface $input
     */
    public function bind(InputInterface $input)
    {
        $this->validateInput($input);

        if (!$pattern = $input->getOption('grep')) {
            $this->filter = false;

            return;
        }

        if (!$this->stringIsRegex($pattern)) {
            $pattern = '/' . preg_quote($pattern, '/') . '/';
        }

        if ($insensitive = $input->getOption('insensitive')) {
            $pattern .= 'i';
        }

        $this->validateRegex($pattern);

        $this->filter      = true;
        $this->pattern     = $pattern;
        $this->insensitive = $insensitive;
        $this->invert      = $input->getOption('invert');
    }

    /**
     * Check whether the bound input has filter options.
     *
     * @return bool
     */
    public function hasFilter()
    {
        return $this->filter;
    }

    /**
     * Check whether a string matches the current filter options.
     *
     * @param string $string
     * @param array  $matches
     *
     * @return bool
     */
    public function match($string, array &$matches = null)
    {
        return $this->filter === false || (preg_match($this->pattern, $string, $matches) xor $this->invert);
    }

    /**
     * Validate that grep, invert and insensitive input options are consistent.
     *
     * @param InputInterface $input
     *
     * @return bool
     */
    private function validateInput(InputInterface $input)
    {
        if (!$input->getOption('grep')) {
            foreach (array('invert', 'insensitive') as $option) {
                if ($input->getOption($option)) {
                    throw new RuntimeException('--' . $option . ' does not make sense without --grep');
                }
            }
        }
    }

    /**
     * Check whether a string appears to be a regular expression.
     *
     * @param string $string
     *
     * @return bool
     */
    private function stringIsRegex($string)
    {
        return substr($string, 0, 1) === '/' && substr($string, -1) === '/' && strlen($string) >= 3;
    }

    /**
     * Validate that $pattern is a valid regular expression.
     *
     * @param string $pattern
     *
     * @return bool
     */
    private function validateRegex($pattern)
    {
        set_error_handler(array('Psy\Exception\ErrorException', 'throwException'));
        try {
            preg_match($pattern, '');
        } catch (ErrorException $e) {
            throw new RuntimeException(str_replace('preg_match(): ', 'Invalid regular expression: ', $e->getRawMessage()));
        }
        restore_error_handler();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Input;

use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\StringInput;

/**
 * A StringInput subclass specialized for code arguments.
 */
class ShellInput extends StringInput
{
    private $hasCodeArgument = false;

    /**
     * Unlike the parent implementation's tokens, this contains an array of
     * token/rest pairs, so that code arguments can be handled while parsing.
     */
    private $tokenPairs;
    private $parsed;

    /**
     * Constructor.
     *
     * @param string $input An array of parameters from the CLI (in the argv format)
     */
    public function __construct($input)
    {
        parent::__construct($input);

        $this->tokenPairs = $this->tokenize($input);
    }

    /**
     * {@inheritdoc}
     *
     * @throws \InvalidArgumentException if $definition has CodeArgument before the final argument position
     */
    public function bind(InputDefinition $definition)
    {
        $hasCodeArgument = false;

        if ($definition->getArgumentCount() > 0) {
            $args = $definition->getArguments();
            $lastArg = array_pop($args);
            foreach ($args as $arg) {
                if ($arg instanceof CodeArgument) {
                    $msg = sprintf('Unexpected CodeArgument before the final position: %s', $arg->getName());
                    throw new \InvalidArgumentException($msg);
                }
            }

            if ($lastArg instanceof CodeArgument) {
                $hasCodeArgument = true;
            }
        }

        $this->hasCodeArgument = $hasCodeArgument;

        return parent::bind($definition);
    }

    /**
     * Tokenizes a string.
     *
     * The version of this on StringInput is good, but doesn't handle code
     * arguments if they're at all complicated. This does :)
     *
     * @param string $input The input to tokenize
     *
     * @return array An array of token/rest pairs
     *
     * @throws \InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize($input)
    {
        $tokens = array();
        $length = strlen($input);
        $cursor = 0;
        while ($cursor < $length) {
            if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
            } elseif (preg_match('/([^="\'\s]+?)(=?)(' . StringInput::REGEX_QUOTED_STRING . '+)/A', $input, $match, null, $cursor)) {
                $tokens[] = array(
                    $match[1] . $match[2] . stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))),
                    substr($input, $cursor),
                );
            } elseif (preg_match('/' . StringInput::REGEX_QUOTED_STRING . '/A', $input, $match, null, $cursor)) {
                $tokens[] = array(
                    stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)),
                    substr($input, $cursor),
                );
            } elseif (preg_match('/' . StringInput::REGEX_STRING . '/A', $input, $match, null, $cursor)) {
                $tokens[] = array(
                    stripcslashes($match[1]),
                    substr($input, $cursor),
                );
            } else {
                // should never happen
                throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
            }

            $cursor += strlen($match[0]);
        }

        return $tokens;
    }

    /**
     * Same as parent, but with some bonus handling for code arguments.
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokenPairs;
        while (null !== $tokenPair = array_shift($this->parsed)) {
            // token is what you'd expect. rest is the remainder of the input
            // string, including token, and will be used if this is a code arg.
            list($token, $rest) = $tokenPair;

            if ($parseOptions && '' === $token) {
                $this->parseShellArgument($token, $rest);
            } elseif ($parseOptions && '--' === $token) {
                $parseOptions = false;
            } elseif ($parseOptions && 0 === strpos($token, '--')) {
                $this->parseLongOption($token);
            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
                $this->parseShortOption($token);
            } else {
                $this->parseShellArgument($token, $rest);
            }
        }
    }

    /**
     * Parses an argument, with bonus handling for code arguments.
     *
     * @param string $token The current token
     * @param string $rest  The remaining unparsed input, including the current token
     *
     * @throws \RuntimeException When too many arguments are given
     */
    private function parseShellArgument($token, $rest)
    {
        $c = count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);

            if ($arg instanceof CodeArgument) {
                // When we find a code argument, we're done parsing. Add the
                // remaining input to the current argument and call it a day.
                $this->parsed = array();
                $this->arguments[$arg->getName()] = $rest;
            } else {
                $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token;
            }

            return;
        }

        // if last argument isArray(), append token to last argument
        if ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

            return;
        }

        // unexpected argument
        $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));
    }

    // Everything below this is copypasta from ArgvInput private methods

    /**
     * Parses a short option.
     *
     * @param string $token The current token
     */
    private function parseShortOption($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.
     *
     * @param string $name The current token
     *
     * @throws \RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet($name)
    {
        $len = strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
            }

            $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.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if (0 === strlen($value = substr($name, $pos + 1))) {
                // if no value after "=" then substr() returns "" since php7 only, false before
                // see http://php.net/manual/fr/migration70.incompatible.php#119151
                if (PHP_VERSION_ID < 70000 && false === $value) {
                    $value = '';
                }
                array_unshift($this->parsed, array($value, null));
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws \RuntimeException When option given doesn't exist
     */
    private function addShortOption($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.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws \RuntimeException When option given doesn't exist
     */
    private function addLongOption($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, array('', 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);
            $nextToken = $next[0];
            if ((isset($nextToken[0]) && '-' !== $nextToken[0]) || in_array($nextToken, array('', null), true)) {
                $value = $nextToken;
            } 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;
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Input;

/**
 * A simple class used internally by PsySH to represent silent input.
 *
 * Silent input is generally used for non-user-generated code, such as the
 * rewritten user code run by sudo command. Silent input isn't echoed before
 * evaluating, and it's not added to the readline history.
 */
class SilentInput
{
    private $inputString;

    /**
     * Constructor.
     *
     * @param string $inputString
     */
    public function __construct($inputString)
    {
        $this->inputString = $inputString;
    }

    /**
     * To. String.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->inputString;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Output;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * An output pager is much the same as a regular OutputInterface, but allows
 * the stream to be flushed to a pager periodically.
 */
interface OutputPager extends OutputInterface
{
    /**
     * Close the current pager process.
     */
    public function close();
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Output;

use Symfony\Component\Console\Output\StreamOutput;

/**
 * A passthrough pager is a no-op. It simply wraps a StreamOutput's stream and
 * does nothing when the pager is closed.
 */
class PassthruPager extends StreamOutput implements OutputPager
{
    /**
     * Constructor.
     *
     * @param StreamOutput $output
     */
    public function __construct(StreamOutput $output)
    {
        parent::__construct($output->getStream());
    }

    /**
     * Close the current pager process.
     */
    public function close()
    {
        // nothing to do here
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Output;

use Symfony\Component\Console\Output\StreamOutput;

/**
 * ProcOutputPager class.
 *
 * A ProcOutputPager instance wraps a regular StreamOutput's stream. Rather
 * than writing directly to the stream, it shells out to a pager process and
 * gives that process the stream as stdout. This means regular *nix commands
 * like `less` and `more` can be used to page large amounts of output.
 */
class ProcOutputPager extends StreamOutput implements OutputPager
{
    private $proc;
    private $pipe;
    private $stream;
    private $cmd;

    /**
     * Constructor.
     *
     * @param StreamOutput $output
     * @param string       $cmd    Pager process command (default: 'less -R -S -F -X')
     */
    public function __construct(StreamOutput $output, $cmd = 'less -R -S -F -X')
    {
        $this->stream = $output->getStream();
        $this->cmd    = $cmd;
    }

    /**
     * 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
     *
     * @throws \RuntimeException When unable to write output (should never happen)
     */
    public function doWrite($message, $newline)
    {
        $pipe = $this->getPipe();
        if (false === @fwrite($pipe, $message . ($newline ? PHP_EOL : ''))) {
            // @codeCoverageIgnoreStart
            // should never happen
            throw new \RuntimeException('Unable to write output.');
            // @codeCoverageIgnoreEnd
        }

        fflush($pipe);
    }

    /**
     * Close the current pager process.
     */
    public function close()
    {
        if (isset($this->pipe)) {
            fclose($this->pipe);
        }

        if (isset($this->proc)) {
            $exit = proc_close($this->proc);
            if ($exit !== 0) {
                throw new \RuntimeException('Error closing output stream');
            }
        }

        unset($this->pipe, $this->proc);
    }

    /**
     * Get a pipe for paging output.
     *
     * If no active pager process exists, fork one and return its input pipe.
     */
    private function getPipe()
    {
        if (!isset($this->pipe) || !isset($this->proc)) {
            $desc = array(array('pipe', 'r'), $this->stream, fopen('php://stderr', 'w'));
            $this->proc = proc_open($this->cmd, $desc, $pipes);

            if (!is_resource($this->proc)) {
                throw new \RuntimeException('Error opening output stream');
            }

            $this->pipe = $pipes[0];
        }

        return $this->pipe;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutput;

/**
 * A ConsoleOutput subclass specifically for Psy Shell output.
 */
class ShellOutput extends ConsoleOutput
{
    const NUMBER_LINES = 128;

    private $paging = 0;
    private $pager;

    /**
     * Construct a ShellOutput instance.
     *
     * @param mixed                    $verbosity (default: self::VERBOSITY_NORMAL)
     * @param bool                     $decorated (default: null)
     * @param OutputFormatterInterface $formatter (default: null)
     * @param null|string|OutputPager  $pager     (default: null)
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null, $pager = null)
    {
        parent::__construct($verbosity, $decorated, $formatter);

        $this->initFormatters();

        if ($pager === null) {
            $this->pager = new PassthruPager($this);
        } elseif (is_string($pager)) {
            $this->pager = new ProcOutputPager($this, $pager);
        } elseif ($pager instanceof OutputPager) {
            $this->pager = $pager;
        } else {
            throw new \InvalidArgumentException('Unexpected pager parameter: ' . $pager);
        }
    }

    /**
     * Page multiple lines of output.
     *
     * The output pager is started
     *
     * If $messages is callable, it will be called, passing this output instance
     * for rendering. Otherwise, all passed $messages are paged to output.
     *
     * Upon completion, the output pager is flushed.
     *
     * @param string|array|\Closure $messages A string, array of strings or a callback
     * @param int                   $type     (default: 0)
     */
    public function page($messages, $type = 0)
    {
        if (is_string($messages)) {
            $messages = (array) $messages;
        }

        if (!is_array($messages) && !is_callable($messages)) {
            throw new \InvalidArgumentException('Paged output requires a string, array or callback.');
        }

        $this->startPaging();

        if (is_callable($messages)) {
            $messages($this);
        } else {
            $this->write($messages, true, $type);
        }

        $this->stopPaging();
    }

    /**
     * Start sending output to the output pager.
     */
    public function startPaging()
    {
        $this->paging++;
    }

    /**
     * Stop paging output and flush the output pager.
     */
    public function stopPaging()
    {
        $this->paging--;
        $this->closePager();
    }

    /**
     * Writes a message to the output.
     *
     * Optionally, pass `$type | self::NUMBER_LINES` as the $type parameter to
     * number the lines of output.
     *
     * @throws \InvalidArgumentException When unknown output type is given
     *
     * @param string|array $messages The message as an array of lines or a single string
     * @param bool         $newline  Whether to add a newline or not
     * @param int          $type     The type of output
     */
    public function write($messages, $newline = false, $type = 0)
    {
        if ($this->getVerbosity() === self::VERBOSITY_QUIET) {
            return;
        }

        $messages = (array) $messages;

        if ($type & self::NUMBER_LINES) {
            $pad = strlen((string) count($messages));
            $template = $this->isDecorated() ? "<aside>%{$pad}s</aside>: %s" : "%{$pad}s: %s";

            if ($type & self::OUTPUT_RAW) {
                $messages = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $messages);
            }

            foreach ($messages as $i => $line) {
                $messages[$i] = sprintf($template, $i, $line);
            }

            // clean this up for super.
            $type = $type & ~self::NUMBER_LINES & ~self::OUTPUT_RAW;
        }

        parent::write($messages, $newline, $type);
    }

    /**
     * Writes a message to the output.
     *
     * Handles paged output, or writes directly to the output stream.
     *
     * @param string $message A message to write to the output
     * @param bool   $newline Whether to add a newline or not
     */
    public function doWrite($message, $newline)
    {
        if ($this->paging > 0) {
            $this->pager->doWrite($message, $newline);
        } else {
            parent::doWrite($message, $newline);
        }
    }

    /**
     * Flush and close the output pager.
     */
    private function closePager()
    {
        if ($this->paging <= 0) {
            $this->pager->close();
        }
    }

    /**
     * Initialize output formatter styles.
     */
    private function initFormatters()
    {
        $formatter = $this->getFormatter();

        $formatter->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
        $formatter->setStyle('error',   new OutputFormatterStyle('black', 'red', array('bold')));
        $formatter->setStyle('aside',   new OutputFormatterStyle('blue'));
        $formatter->setStyle('strong',  new OutputFormatterStyle(null, null, array('bold')));
        $formatter->setStyle('return',  new OutputFormatterStyle('cyan'));
        $formatter->setStyle('urgent',  new OutputFormatterStyle('red'));
        $formatter->setStyle('hidden',  new OutputFormatterStyle('black'));

        // Visibility
        $formatter->setStyle('public',    new OutputFormatterStyle(null, null, array('bold')));
        $formatter->setStyle('protected', new OutputFormatterStyle('yellow'));
        $formatter->setStyle('private',   new OutputFormatterStyle('red'));
        $formatter->setStyle('global',    new OutputFormatterStyle('cyan', null, array('bold')));
        $formatter->setStyle('const',     new OutputFormatterStyle('cyan'));
        $formatter->setStyle('class',     new OutputFormatterStyle('blue', null, array('underscore')));
        $formatter->setStyle('function',  new OutputFormatterStyle(null));
        $formatter->setStyle('default',   new OutputFormatterStyle(null));

        // Types
        $formatter->setStyle('number',   new OutputFormatterStyle('magenta'));
        $formatter->setStyle('string',   new OutputFormatterStyle('green'));
        $formatter->setStyle('bool',     new OutputFormatterStyle('cyan'));
        $formatter->setStyle('keyword',  new OutputFormatterStyle('yellow'));
        $formatter->setStyle('comment',  new OutputFormatterStyle('blue'));
        $formatter->setStyle('object',   new OutputFormatterStyle('blue'));
        $formatter->setStyle('resource', new OutputFormatterStyle('yellow'));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use PhpParser\Lexer;
use PhpParser\Parser;
use PhpParser\ParserFactory as OriginalParserFactory;

/**
 * Parser factory to abstract over PHP parser library versions.
 */
class ParserFactory
{
    const ONLY_PHP5   = 'ONLY_PHP5';
    const ONLY_PHP7   = 'ONLY_PHP7';
    const PREFER_PHP5 = 'PREFER_PHP5';
    const PREFER_PHP7 = 'PREFER_PHP7';

    /**
     * Possible kinds of parsers for the factory, from PHP parser library.
     *
     * @return array
     */
    public static function getPossibleKinds()
    {
        return array('ONLY_PHP5', 'ONLY_PHP7', 'PREFER_PHP5', 'PREFER_PHP7');
    }

    /**
     * Is this parser factory supports kinds?
     *
     * PHP parser < 2.0 doesn't support kinds, >= 2.0 — does.
     *
     * @return bool
     */
    public function hasKindsSupport()
    {
        return class_exists('PhpParser\ParserFactory');
    }

    /**
     * Default kind (if supported, based on current interpreter's version).
     *
     * @return string|null
     */
    public function getDefaultKind()
    {
        if ($this->hasKindsSupport()) {
            return version_compare(PHP_VERSION, '7.0', '>=') ? static::ONLY_PHP7 : static::ONLY_PHP5;
        }
    }

    /**
     * New parser instance with given kind.
     *
     * @param string|null $kind One of class constants (only for PHP parser 2.0 and above)
     *
     * @return Parser
     */
    public function createParser($kind = null)
    {
        if ($this->hasKindsSupport()) {
            $originalFactory = new OriginalParserFactory();

            $kind = $kind ?: $this->getDefaultKind();

            if (!in_array($kind, static::getPossibleKinds())) {
                throw new \InvalidArgumentException('Unknown parser kind');
            }

            $parser = $originalFactory->create(constant('PhpParser\ParserFactory::' . $kind));
        } else {
            if ($kind !== null) {
                throw new \InvalidArgumentException('Install PHP Parser v2.x to specify parser kind');
            }

            $parser = new Parser(new Lexer());
        }

        return $parser;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

/**
 * A Readline interface implementation for GNU Readline.
 *
 * This is by far the coolest way to do it, but it doesn't work with new PHP.
 *
 * Oh well.
 */
class GNUReadline implements Readline
{
    /** @var string|false */
    protected $historyFile;
    /** @var int */
    protected $historySize;
    /** @var bool */
    protected $eraseDups;

    /**
     * GNU Readline is supported iff `readline_list_history` is defined. PHP
     * decided it would be awesome to swap out GNU Readline for Libedit, but
     * they ended up shipping an incomplete implementation. So we've got this.
     *
     * @return bool
     */
    public static function isSupported()
    {
        return function_exists('readline_list_history');
    }

    /**
     * GNU Readline constructor.
     *
     * @param string|false $historyFile
     * @param int          $historySize
     * @param bool         $eraseDups
     */
    public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
    {
        $this->historyFile = ($historyFile !== null) ? $historyFile : false;
        $this->historySize = $historySize;
        $this->eraseDups = $eraseDups;
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory($line)
    {
        if ($res = readline_add_history($line)) {
            $this->writeHistory();
        }

        return $res;
    }

    /**
     * {@inheritdoc}
     */
    public function clearHistory()
    {
        if ($res = readline_clear_history()) {
            $this->writeHistory();
        }

        return $res;
    }

    /**
     * {@inheritdoc}
     */
    public function listHistory()
    {
        return readline_list_history();
    }

    /**
     * {@inheritdoc}
     */
    public function readHistory()
    {
        // Workaround PHP bug #69054
        //
        // If open_basedir is set, readline_read_history() segfaults. This was fixed in 5.6.7:
        //
        //     https://github.com/php/php-src/blob/423a057023ef3c00d2ffc16a6b43ba01d0f71796/NEWS#L19-L21
        //
        if (version_compare(PHP_VERSION, '5.6.7', '>=') || !ini_get('open_basedir')) {
            readline_read_history();
        }
        readline_clear_history();

        return readline_read_history($this->historyFile);
    }

    /**
     * {@inheritdoc}
     */
    public function readline($prompt = null)
    {
        return readline($prompt);
    }

    /**
     * {@inheritdoc}
     */
    public function redisplay()
    {
        readline_redisplay();
    }

    /**
     * {@inheritdoc}
     */
    public function writeHistory()
    {
        // We have to write history first, since it is used
        // by Libedit to list history
        if ($this->historyFile !== false) {
            $res = readline_write_history($this->historyFile);
        } else {
            $res = true;
        }

        if (!$res || !$this->eraseDups && !$this->historySize > 0) {
            return $res;
        }

        $hist = $this->listHistory();
        if (!$hist) {
            return true;
        }

        if ($this->eraseDups) {
            // flip-flip technique: removes duplicates, latest entries win.
            $hist = array_flip(array_flip($hist));
            // sort on keys to get the order back
            ksort($hist);
        }

        if ($this->historySize > 0) {
            $histsize = count($hist);
            if ($histsize > $this->historySize) {
                $hist = array_slice($hist, $histsize - $this->historySize);
            }
        }

        readline_clear_history();
        foreach ($hist as $line) {
            readline_add_history($line);
        }

        if ($this->historyFile !== false) {
            return readline_write_history($this->historyFile);
        }

        return true;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

use Hoa\Console\Readline\Readline as HoaReadline;
use Psy\Exception\BreakException;

/**
 * Hoa\Console Readline implementation.
 */
class HoaConsole implements Readline
{
    /** @var HoaReadline */
    private $hoaReadline;

    /**
     * @return bool
     */
    public static function isSupported()
    {
        return class_exists('\Hoa\Console\Console', true);
    }

    public function __construct()
    {
        $this->hoaReadline = new HoaReadline();
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory($line)
    {
        $this->hoaReadline->addHistory($line);

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function clearHistory()
    {
        $this->hoaReadline->clearHistory();

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function listHistory()
    {
        $i = 0;
        $list = array();
        while (($item = $this->hoaReadline->getHistory($i++)) !== null) {
            $list[] = $item;
        }

        return $list;
    }

    /**
     * {@inheritdoc}
     */
    public function readHistory()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws BreakException if user hits Ctrl+D
     *
     * @return string
     */
    public function readline($prompt = null)
    {
        return $this->hoaReadline->readLine($prompt);
    }

    /**
     * {@inheritdoc}
     */
    public function redisplay()
    {
        // noop
    }

    /**
     * {@inheritdoc}
     */
    public function writeHistory()
    {
        return true;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

use Psy\Util\Str;

/**
 * A Libedit-based Readline implementation.
 *
 * This is largely the same as the Readline implementation, but it emulates
 * support for `readline_list_history` since PHP decided it was a good idea to
 * ship a fake Readline implementation that is missing history support.
 */
class Libedit extends GNUReadline
{
    /**
     * Let's emulate GNU Readline by manually reading and parsing the history file!
     *
     * @return bool
     */
    public static function isSupported()
    {
        return function_exists('readline') && !function_exists('readline_list_history');
    }

    /**
     * {@inheritdoc}
     */
    public function listHistory()
    {
        $history = file_get_contents($this->historyFile);
        if (!$history) {
            return array();
        }

        // libedit doesn't seem to support non-unix line separators.
        $history = explode("\n", $history);

        // shift the history signature, ensure it's valid
        if (array_shift($history) !== '_HiStOrY_V2_') {
            return array();
        }

        // decode the line
        $history = array_map(array($this, 'parseHistoryLine'), $history);
        // filter empty lines & comments
        return array_values(array_filter($history));
    }

    /**
     * From GNUReadline (readline/histfile.c & readline/histexpand.c):
     * lines starting with "\0" are comments or timestamps;
     * if "\0" is found in an entry,
     * everything from it until the next line is a comment.
     *
     * @param string $line The history line to parse
     *
     * @return string | null
     */
    protected function parseHistoryLine($line)
    {
        // empty line, comment or timestamp
        if (!$line || $line[0] === "\0") {
            return;
        }
        // if "\0" is found in an entry, then
        // everything from it until the end of line is a comment.
        if (($pos = strpos($line, "\0")) !== false) {
            $line = substr($line, 0, $pos);
        }

        return ($line !== '') ? Str::unvis($line) : null;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

/**
 * An interface abstracting the various readline_* functions.
 */
interface Readline
{
    /**
     * Check whether this Readline class is supported by the current system.
     *
     * @return bool
     */
    public static function isSupported();

    /**
     * Add a line to the command history.
     *
     * @param string $line
     *
     * @return bool Success
     */
    public function addHistory($line);

    /**
     * Clear the command history.
     *
     * @return bool Success
     */
    public function clearHistory();

    /**
     * List the command history.
     *
     * @return array
     */
    public function listHistory();

    /**
     * Read the command history.
     *
     * @return bool Success
     */
    public function readHistory();

    /**
     * Read a single line of input from the user.
     *
     * @param null|string $prompt
     *
     * @return false|string
     */
    public function readline($prompt = null);

    /**
     * Redraw readline to redraw the display.
     */
    public function redisplay();

    /**
     * Write the command history to a file.
     *
     * @return bool Success
     */
    public function writeHistory();
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

use Psy\Exception\BreakException;

/**
 * An array-based Readline emulation implementation.
 */
class Transient implements Readline
{
    private $history;
    private $historySize;
    private $eraseDups;

    /**
     * Transient Readline is always supported.
     *
     * {@inheritdoc}
     */
    public static function isSupported()
    {
        return true;
    }

    /**
     * Transient Readline constructor.
     */
    public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
    {
        // don't do anything with the history file...
        $this->history     = array();
        $this->historySize = $historySize;
        $this->eraseDups   = $eraseDups;
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory($line)
    {
        if ($this->eraseDups) {
            if (($key = array_search($line, $this->history)) !== false) {
                unset($this->history[$key]);
            }
        }

        $this->history[] = $line;

        if ($this->historySize > 0) {
            $histsize = count($this->history);
            if ($histsize > $this->historySize) {
                $this->history = array_slice($this->history, $histsize - $this->historySize);
            }
        }

        $this->history = array_values($this->history);

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function clearHistory()
    {
        $this->history = array();

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function listHistory()
    {
        return $this->history;
    }

    /**
     * {@inheritdoc}
     */
    public function readHistory()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws BreakException if user hits Ctrl+D
     *
     * @return string
     */
    public function readline($prompt = null)
    {
        echo $prompt;

        return rtrim(fgets($this->getStdin(), 1024));
    }

    /**
     * {@inheritdoc}
     */
    public function redisplay()
    {
        // noop
    }

    /**
     * {@inheritdoc}
     */
    public function writeHistory()
    {
        return true;
    }

    /**
     * Get a STDIN file handle.
     *
     * @throws BreakException if user hits Ctrl+D
     *
     * @return resource
     */
    private function getStdin()
    {
        if (!isset($this->stdin)) {
            $this->stdin = fopen('php://stdin', 'r');
        }

        if (feof($this->stdin)) {
            throw new BreakException('Ctrl+D');
        }

        return $this->stdin;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Reflection;

/**
 * Somehow the standard reflection library doesn't include constants.
 *
 * ReflectionConstant corrects that omission.
 */
class ReflectionConstant implements \Reflector
{
    private $class;
    private $name;
    private $value;

    /**
     * Construct a ReflectionConstant object.
     *
     * @param mixed  $class
     * @param string $name
     */
    public function __construct($class, $name)
    {
        if (!$class instanceof \ReflectionClass) {
            $class = new \ReflectionClass($class);
        }

        $this->class = $class;
        $this->name  = $name;

        $constants = $class->getConstants();
        if (!array_key_exists($name, $constants)) {
            throw new \InvalidArgumentException('Unknown constant: ' . $name);
        }

        $this->value = $constants[$name];
    }

    /**
     * Gets the declaring class.
     *
     * @return string
     */
    public function getDeclaringClass()
    {
        $parent = $this->class;

        // Since we don't have real reflection constants, we can't see where
        // it's actually defined. Let's check for a constant that is also
        // available on the parent class which has exactly the same value.
        //
        // While this isn't _technically_ correct, it's prolly close enough.
        do {
            $class = $parent;
            $parent = $class->getParentClass();
        } while ($parent && $parent->hasConstant($this->name) && $parent->getConstant($this->name) === $this->value);

        return $class;
    }

    /**
     * Gets the constant name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Gets the value of the constant.
     *
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Gets the constant's file name.
     *
     * Currently returns null, because if it returns a file name the signature
     * formatter will barf.
     */
    public function getFileName()
    {
        return;
        // return $this->class->getFileName();
    }

    /**
     * Get the code start line.
     *
     * @throws \RuntimeException
     */
    public function getStartLine()
    {
        throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
    }

    /**
     * Get the code end line.
     *
     * @throws \RuntimeException
     */
    public function getEndLine()
    {
        return $this->getStartLine();
    }

    /**
     * Get the constant's docblock.
     *
     * @return false
     */
    public function getDocComment()
    {
        return false;
    }

    /**
     * Export the constant? I don't think this is possible.
     *
     * @throws \RuntimeException
     */
    public static function export()
    {
        throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
    }

    /**
     * To string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getName();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Reflection;

/**
 * A fake ReflectionFunction but for language constructs.
 */
class ReflectionLanguageConstruct extends \ReflectionFunctionAbstract
{
    public $keyword;

    /**
     * Language construct parameter definitions.
     */
    private static $languageConstructs = array(
        'isset' => array(
            'var' => array(),
            '...' => array(
                'isOptional'   => true,
                'defaultValue' => null,
            ),
        ),

        'unset' => array(
            'var' => array(),
            '...' => array(
                'isOptional'   => true,
                'defaultValue' => null,
            ),
        ),

        'empty' => array(
            'var' => array(),
        ),

        'echo' => array(
            'arg1' => array(),
            '...'  => array(
                'isOptional'   => true,
                'defaultValue' => null,
            ),
        ),

        'print' => array(
            'arg' => array(),
        ),

        'die' => array(
            'status' => array(
                'isOptional'   => true,
                'defaultValue' => 0,
            ),
        ),

        'exit' => array(
            'status' => array(
                'isOptional'   => true,
                'defaultValue' => 0,
            ),
        ),
    );

    /**
     * Construct a ReflectionLanguageConstruct object.
     *
     * @param string $name
     */
    public function __construct($keyword)
    {
        if (self::isLanguageConstruct($keyword)) {
            throw new \InvalidArgumentException('Unknown language construct: ' . $keyword);
        }

        $this->keyword = $keyword;
    }

    /**
     * This can't (and shouldn't) do anything :).
     *
     * @throws \RuntimeException
     */
    public static function export($name)
    {
        throw new \RuntimeException('Not yet implemented because it\'s unclear what I should do here :)');
    }

    /**
     * Get language construct name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->keyword;
    }

    /**
     * None of these return references.
     *
     * @return bool
     */
    public function returnsReference()
    {
        return false;
    }

    /**
     * Get language construct params.
     *
     * @return
     */
    public function getParameters()
    {
        $params = array();
        foreach (self::$languageConstructs[$this->keyword] as $parameter => $opts) {
            array_push($params, new ReflectionLanguageConstructParameter($this->keyword, $parameter, $opts));
        }

        return $params;
    }

    /**
     * To string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getName();
    }

    /**
     * Check whether keyword is a (known) language construct.
     *
     * @param $keyword
     *
     * @return bool
     */
    public static function isLanguageConstruct($keyword)
    {
        return array_key_exists($keyword, self::$languageConstructs);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Reflection;

/**
 * A fake ReflectionParameter but for language construct parameters.
 *
 * It stubs out all the important bits and returns whatever was passed in $opts.
 */
class ReflectionLanguageConstructParameter extends \ReflectionParameter
{
    private $function;
    private $parameter;
    private $opts;

    public function __construct($function, $parameter, array $opts)
    {
        $this->function = $function;
        $this->parameter = $parameter;
        $this->opts = $opts;
    }

    /**
     * No class here.
     */
    public function getClass()
    {
        return;
    }

    /**
     * Is the param an array?
     *
     * @return bool
     */
    public function isArray()
    {
        return array_key_exists('isArray', $this->opts) && $this->opts['isArray'];
    }

    /**
     * Get param default value.
     *
     * @return mixed
     */
    public function getDefaultValue()
    {
        if ($this->isDefaultValueAvailable()) {
            return $this->opts['defaultValue'];
        }
    }

    /**
     * Get param name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->parameter;
    }

    /**
     * Is the param optional?
     *
     * @return bool
     */
    public function isOptional()
    {
        return array_key_exists('isOptional', $this->opts) && $this->opts['isOptional'];
    }

    /**
     * Does the param have a default value?
     *
     * @return bool
     */
    public function isDefaultValueAvailable()
    {
        return array_key_exists('defaultValue', $this->opts);
    }

    /**
     * Is the param passed by reference?
     *
     * (I don't think this is true for anything we need to fake a param for)
     *
     * @return bool
     */
    public function isPassedByReference()
    {
        return array_key_exists('isPassedByReference', $this->opts) && $this->opts['isPassedByReference'];
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use Psy\CodeCleaner\NoReturnValue;
use Psy\Exception\BreakException;
use Psy\Exception\ErrorException;
use Psy\Exception\Exception as PsyException;
use Psy\Exception\ThrowUpException;
use Psy\Input\ShellInput;
use Psy\Input\SilentInput;
use Psy\Output\ShellOutput;
use Psy\TabCompletion\Matcher;
use Psy\VarDumper\PresenterAware;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\ArgvInput;
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\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * The Psy Shell application.
 *
 * Usage:
 *
 *     $shell = new Shell;
 *     $shell->run();
 *
 * @author Justin Hileman <justin@justinhileman.info>
 */
class Shell extends Application
{
    const VERSION = 'v0.8.14';

    const PROMPT      = '>>> ';
    const BUFF_PROMPT = '... ';
    const REPLAY      = '--> ';
    const RETVAL      = '=> ';

    private $config;
    private $cleaner;
    private $output;
    private $readline;
    private $inputBuffer;
    private $code;
    private $codeBuffer;
    private $codeBufferOpen;
    private $context;
    private $includes;
    private $loop;
    private $outputWantsNewline = false;
    private $completion;
    private $tabCompletionMatchers = array();
    private $stdoutBuffer;
    private $prompt;

    /**
     * Create a new Psy Shell.
     *
     * @param Configuration $config (default: null)
     */
    public function __construct(Configuration $config = null)
    {
        $this->config   = $config ?: new Configuration();
        $this->cleaner  = $this->config->getCodeCleaner();
        $this->loop     = $this->config->getLoop();
        $this->context  = new Context();
        $this->includes = array();
        $this->readline = $this->config->getReadline();
        $this->inputBuffer = array();
        $this->stdoutBuffer = '';

        parent::__construct('Psy Shell', self::VERSION);

        $this->config->setShell($this);

        // Register the current shell session's config with \Psy\info
        \Psy\info($this->config);
    }

    /**
     * Check whether the first thing in a backtrace is an include call.
     *
     * This is used by the psysh bin to decide whether to start a shell on boot,
     * or to simply autoload the library.
     */
    public static function isIncluded(array $trace)
    {
        return isset($trace[0]['function']) &&
          in_array($trace[0]['function'], array('require', 'include', 'require_once', 'include_once'));
    }

    /**
     * Invoke a Psy Shell from the current context.
     *
     * @see Psy\debug
     * @deprecated will be removed in 1.0. Use \Psy\debug instead
     *
     * @param array  $vars        Scope variables from the calling context (default: array())
     * @param object $boundObject Bound object ($this) value for the shell
     *
     * @return array Scope variables from the debugger session
     */
    public static function debug(array $vars = array(), $boundObject = null)
    {
        return \Psy\debug($vars, $boundObject);
    }

    /**
     * Adds a command object.
     *
     * {@inheritdoc}
     *
     * @param BaseCommand $command A Symfony Console Command object
     *
     * @return BaseCommand The registered command
     */
    public function add(BaseCommand $command)
    {
        if ($ret = parent::add($command)) {
            if ($ret instanceof ContextAware) {
                $ret->setContext($this->context);
            }

            if ($ret instanceof PresenterAware) {
                $ret->setPresenter($this->config->getPresenter());
            }
        }

        return $ret;
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
        ));
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return array An array of default Command instances
     */
    protected function getDefaultCommands()
    {
        $sudo = new Command\SudoCommand();
        $sudo->setReadline($this->readline);

        $hist = new Command\HistoryCommand();
        $hist->setReadline($this->readline);

        return array(
            new Command\HelpCommand(),
            new Command\ListCommand(),
            new Command\DumpCommand(),
            new Command\DocCommand(),
            new Command\ShowCommand($this->config->colorMode()),
            new Command\WtfCommand($this->config->colorMode()),
            new Command\WhereamiCommand($this->config->colorMode()),
            new Command\ThrowUpCommand(),
            new Command\TraceCommand(),
            new Command\BufferCommand(),
            new Command\ClearCommand(),
            new Command\EditCommand($this->config->getRuntimeDir()),
            // new Command\PsyVersionCommand(),
            $sudo,
            $hist,
            new Command\ExitCommand(),
        );
    }

    /**
     * @return array
     */
    protected function getTabCompletionMatchers()
    {
        if (empty($this->tabCompletionMatchers)) {
            $this->tabCompletionMatchers = array(
                new Matcher\CommandsMatcher($this->all()),
                new Matcher\KeywordsMatcher(),
                new Matcher\VariablesMatcher(),
                new Matcher\ConstantsMatcher(),
                new Matcher\FunctionsMatcher(),
                new Matcher\ClassNamesMatcher(),
                new Matcher\ClassMethodsMatcher(),
                new Matcher\ClassAttributesMatcher(),
                new Matcher\ObjectMethodsMatcher(),
                new Matcher\ObjectAttributesMatcher(),
                new Matcher\ClassMethodDefaultParametersMatcher(),
                new Matcher\ObjectMethodDefaultParametersMatcher(),
                new Matcher\FunctionDefaultParametersMatcher(),
            );
        }

        return $this->tabCompletionMatchers;
    }

    /**
     * @param array $matchers
     */
    public function addTabCompletionMatchers(array $matchers)
    {
        $this->tabCompletionMatchers = array_merge($matchers, $this->getTabCompletionMatchers());
    }

    /**
     * Set the Shell output.
     *
     * @param OutputInterface $output
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * Runs the current application.
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        $this->initializeTabCompletion();

        if ($input === null && !isset($_SERVER['argv'])) {
            $input = new ArgvInput(array());
        }

        if ($output === null) {
            $output = $this->config->getOutput();
        }

        try {
            return parent::run($input, $output);
        } catch (\Exception $e) {
            $this->writeException($e);
        }
    }

    /**
     * Runs the current application.
     *
     * @throws Exception if thrown via the `throw-up` command
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        $this->setOutput($output);

        $this->resetCodeBuffer();

        $this->setAutoExit(false);
        $this->setCatchExceptions(false);

        $this->readline->readHistory();

        // if ($this->config->useReadline()) {
        //     readline_completion_function(array($this, 'autocomplete'));
        // }

        $this->output->writeln($this->getHeader());
        $this->writeVersionInfo();
        $this->writeStartupMessage();

        try {
            $this->loop->run($this);
        } catch (ThrowUpException $e) {
            throw $e->getPrevious();
        }
    }

    /**
     * Read user input.
     *
     * This will continue fetching user input until the code buffer contains
     * valid code.
     *
     * @throws BreakException if user hits Ctrl+D
     */
    public function getInput()
    {
        $this->codeBufferOpen = false;

        do {
            // reset output verbosity (in case it was altered by a subcommand)
            $this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);

            $input = $this->readline();

            /*
             * Handle Ctrl+D. It behaves differently in different cases:
             *
             *   1) In an expression, like a function or "if" block, clear the input buffer
             *   2) At top-level session, behave like the exit command
             */
            if ($input === false) {
                $this->output->writeln('');

                if ($this->hasCode()) {
                    $this->resetCodeBuffer();
                } else {
                    throw new BreakException('Ctrl+D');
                }
            }

            // handle empty input
            if (trim($input) === '') {
                continue;
            }

            if ($this->hasCommand($input)) {
                $this->readline->addHistory($input);
                $this->runCommand($input);

                continue;
            }

            $this->addCode($input);
        } while (!$this->hasValidCode());
    }

    /**
     * Pass the beforeLoop callback through to the Loop instance.
     *
     * @see Loop::beforeLoop
     */
    public function beforeLoop()
    {
        $this->loop->beforeLoop();
    }

    /**
     * Pass the afterLoop callback through to the Loop instance.
     *
     * @see Loop::afterLoop
     */
    public function afterLoop()
    {
        $this->loop->afterLoop();
    }

    /**
     * Set the variables currently in scope.
     *
     * @param array $vars
     */
    public function setScopeVariables(array $vars)
    {
        $this->context->setAll($vars);
    }

    /**
     * Return the set of variables currently in scope.
     *
     * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
     *                                 passing the scope variables to `extract`
     *                                 in PHP 7.1+, you _must_ exclude 'this'
     *
     * @return array Associative array of scope variables
     */
    public function getScopeVariables($includeBoundObject = true)
    {
        $vars = $this->context->getAll();

        if (!$includeBoundObject) {
            unset($vars['this']);
        }

        return $vars;
    }

    /**
     * Return the set of magic variables currently in scope.
     *
     * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
     *                                 passing the scope variables to `extract`
     *                                 in PHP 7.1+, you _must_ exclude 'this'
     *
     * @return array Associative array of magic scope variables
     */
    public function getSpecialScopeVariables($includeBoundObject = true)
    {
        $vars = $this->context->getSpecialVariables();

        if (!$includeBoundObject) {
            unset($vars['this']);
        }

        return $vars;
    }

    /**
     * Get the set of unused command-scope variable names.
     *
     * @return array Array of unused variable names
     */
    public function getUnusedCommandScopeVariableNames()
    {
        return $this->context->getUnusedCommandScopeVariableNames();
    }

    /**
     * Get the set of variable names currently in scope.
     *
     * @return array Array of variable names
     */
    public function getScopeVariableNames()
    {
        return array_keys($this->context->getAll());
    }

    /**
     * Get a scope variable value by name.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function getScopeVariable($name)
    {
        return $this->context->get($name);
    }

    /**
     * Set the bound object ($this variable) for the interactive shell.
     *
     * @param object|null $boundObject
     */
    public function setBoundObject($boundObject)
    {
        $this->context->setBoundObject($boundObject);
    }

    /**
     * Get the bound object ($this variable) for the interactive shell.
     *
     * @return object|null
     */
    public function getBoundObject()
    {
        return $this->context->getBoundObject();
    }

    /**
     * Add includes, to be parsed and executed before running the interactive shell.
     *
     * @param array $includes
     */
    public function setIncludes(array $includes = array())
    {
        $this->includes = $includes;
    }

    /**
     * Get PHP files to be parsed and executed before running the interactive shell.
     *
     * @return array
     */
    public function getIncludes()
    {
        return array_merge($this->config->getDefaultIncludes(), $this->includes);
    }

    /**
     * Check whether this shell's code buffer contains code.
     *
     * @return bool True if the code buffer contains code
     */
    public function hasCode()
    {
        return !empty($this->codeBuffer);
    }

    /**
     * Check whether the code in this shell's code buffer is valid.
     *
     * If the code is valid, the code buffer should be flushed and evaluated.
     *
     * @return bool True if the code buffer content is valid
     */
    protected function hasValidCode()
    {
        return !$this->codeBufferOpen && $this->code !== false;
    }

    /**
     * Add code to the code buffer.
     *
     * @param string $code
     */
    public function addCode($code)
    {
        try {
            // Code lines ending in \ keep the buffer open
            if (substr(rtrim($code), -1) === '\\') {
                $this->codeBufferOpen = true;
                $code = substr(rtrim($code), 0, -1);
            } else {
                $this->codeBufferOpen = false;
            }

            $this->codeBuffer[] = $code;
            $this->code         = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
        } catch (\Exception $e) {
            // Add failed code blocks to the readline history.
            $this->addCodeBufferToHistory();

            throw $e;
        }
    }

    /**
     * Get the current code buffer.
     *
     * This is useful for commands which manipulate the buffer.
     *
     * @return array
     */
    public function getCodeBuffer()
    {
        return $this->codeBuffer;
    }

    /**
     * Run a Psy Shell command given the user input.
     *
     * @throws InvalidArgumentException if the input is not a valid command
     *
     * @param string $input User input string
     *
     * @return mixed Who knows?
     */
    protected function runCommand($input)
    {
        $command = $this->getCommand($input);

        if (empty($command)) {
            throw new \InvalidArgumentException('Command not found: ' . $input);
        }

        $input = new ShellInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));

        if ($input->hasParameterOption(array('--help', '-h'))) {
            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand->run($input, $this->output);
        }

        return $command->run($input, $this->output);
    }

    /**
     * Reset the current code buffer.
     *
     * This should be run after evaluating user input, catching exceptions, or
     * on demand by commands such as BufferCommand.
     */
    public function resetCodeBuffer()
    {
        $this->codeBuffer = array();
        $this->code       = false;
    }

    /**
     * Inject input into the input buffer.
     *
     * This is useful for commands which want to replay history.
     *
     * @param string|array $input
     * @param bool         $silent
     */
    public function addInput($input, $silent = false)
    {
        foreach ((array) $input as $line) {
            $this->inputBuffer[] = $silent ? new SilentInput($line) : $line;
        }
    }

    /**
     * Flush the current (valid) code buffer.
     *
     * If the code buffer is valid, resets the code buffer and returns the
     * current code.
     *
     * @return string PHP code buffer contents
     */
    public function flushCode()
    {
        if ($this->hasValidCode()) {
            $this->addCodeBufferToHistory();
            $code = $this->code;
            $this->resetCodeBuffer();

            return $code;
        }
    }

    /**
     * Filter silent input from code buffer, write the rest to readline history.
     */
    private function addCodeBufferToHistory()
    {
        $codeBuffer = array_filter($this->codeBuffer, function ($line) {
            return !$line instanceof SilentInput;
        });

        $code = implode("\n", $codeBuffer);

        if (trim($code) !== '') {
            $this->readline->addHistory($code);
        }
    }

    /**
     * Get the current evaluation scope namespace.
     *
     * @see CodeCleaner::getNamespace
     *
     * @return string Current code namespace
     */
    public function getNamespace()
    {
        if ($namespace = $this->cleaner->getNamespace()) {
            return implode('\\', $namespace);
        }
    }

    /**
     * Write a string to stdout.
     *
     * This is used by the shell loop for rendering output from evaluated code.
     *
     * @param string $out
     * @param int    $phase Output buffering phase
     */
    public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
    {
        $isCleaning = false;
        if (version_compare(PHP_VERSION, '5.4', '>=')) {
            $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
        }

        // Incremental flush
        if ($out !== '' && !$isCleaning) {
            $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
            $this->outputWantsNewline = (substr($out, -1) !== "\n");
            $this->stdoutBuffer .= $out;
        }

        // Output buffering is done!
        if ($phase & PHP_OUTPUT_HANDLER_END) {
            // Write an extra newline if stdout didn't end with one
            if ($this->outputWantsNewline) {
                $this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
                $this->outputWantsNewline = false;
            }

            // Save the stdout buffer as $__out
            if ($this->stdoutBuffer !== '') {
                $this->context->setLastStdout($this->stdoutBuffer);
                $this->stdoutBuffer = '';
            }
        }
    }

    /**
     * Write a return value to stdout.
     *
     * The return value is formatted or pretty-printed, and rendered in a
     * visibly distinct manner (in this case, as cyan).
     *
     * @see self::presentValue
     *
     * @param mixed $ret
     */
    public function writeReturnValue($ret)
    {
        if ($ret instanceof NoReturnValue) {
            return;
        }

        $this->context->setReturnValue($ret);
        $ret    = $this->presentValue($ret);
        $indent = str_repeat(' ', strlen(static::RETVAL));

        $this->output->writeln(static::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
    }

    /**
     * Renders a caught Exception.
     *
     * Exceptions are formatted according to severity. ErrorExceptions which were
     * warnings or Strict errors aren't rendered as harshly as real errors.
     *
     * Stores $e as the last Exception in the Shell Context.
     *
     * @param \Exception      $e      An exception instance
     * @param OutputInterface $output An OutputInterface instance
     */
    public function writeException(\Exception $e)
    {
        $this->context->setLastException($e);
        $this->output->writeln($this->formatException($e));
        $this->resetCodeBuffer();
    }

    /**
     * Helper for formatting an exception for writeException().
     *
     * @todo extract this to somewhere it makes more sense
     *
     * @param \Exception $e
     *
     * @return string
     */
    public function formatException(\Exception $e)
    {
        $message = $e->getMessage();
        if (!$e instanceof PsyException) {
            if ($message === '') {
                $message = get_class($e);
            } else {
                $message = sprintf('%s with message \'%s\'', get_class($e), $message);
            }
        }

        $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';

        return sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity);
    }

    /**
     * Helper for getting an output style for the given ErrorException's level.
     *
     * @param \ErrorException $e
     *
     * @return string
     */
    protected function getSeverity(\ErrorException $e)
    {
        $severity = $e->getSeverity();
        if ($severity & error_reporting()) {
            switch ($severity) {
                case E_WARNING:
                case E_NOTICE:
                case E_CORE_WARNING:
                case E_COMPILE_WARNING:
                case E_USER_WARNING:
                case E_USER_NOTICE:
                case E_STRICT:
                    return 'warning';

                default:
                    return 'error';
            }
        } else {
            // Since this is below the user's reporting threshold, it's always going to be a warning.
            return 'warning';
        }
    }

    /**
     * Helper for throwing an ErrorException.
     *
     * This allows us to:
     *
     *     set_error_handler(array($psysh, 'handleError'));
     *
     * Unlike ErrorException::throwException, this error handler respects the
     * current error_reporting level; i.e. it logs warnings and notices, but
     * doesn't throw an exception unless it's above the current error_reporting
     * threshold. This should probably only be used in the inner execution loop
     * of the shell, as most of the time a thrown exception is much more useful.
     *
     * If the error type matches the `errorLoggingLevel` config, it will be
     * logged as well, regardless of the `error_reporting` level.
     *
     * @see \Psy\Exception\ErrorException::throwException
     * @see \Psy\Shell::writeException
     *
     * @throws \Psy\Exception\ErrorException depending on the current error_reporting level
     *
     * @param int    $errno   Error type
     * @param string $errstr  Message
     * @param string $errfile Filename
     * @param int    $errline Line number
     */
    public function handleError($errno, $errstr, $errfile, $errline)
    {
        if ($errno & error_reporting()) {
            ErrorException::throwException($errno, $errstr, $errfile, $errline);
        } elseif ($errno & $this->config->errorLoggingLevel()) {
            // log it and continue...
            $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
        }
    }

    /**
     * Format a value for display.
     *
     * @see Presenter::present
     *
     * @param mixed $val
     *
     * @return string Formatted value
     */
    protected function presentValue($val)
    {
        return $this->config->getPresenter()->present($val);
    }

    /**
     * Get a command (if one exists) for the current input string.
     *
     * @param string $input
     *
     * @return null|BaseCommand
     */
    protected function getCommand($input)
    {
        $input = new StringInput($input);
        if ($name = $input->getFirstArgument()) {
            return $this->get($name);
        }
    }

    /**
     * Check whether a command is set for the current input string.
     *
     * @param string $input
     *
     * @return bool True if the shell has a command for the given input
     */
    protected function hasCommand($input)
    {
        $input = new StringInput($input);
        if ($name = $input->getFirstArgument()) {
            return $this->has($name);
        }

        return false;
    }

    /**
     * Get the current input prompt.
     *
     * @return string
     */
    protected function getPrompt()
    {
        if ($this->hasCode()) {
            return static::BUFF_PROMPT;
        }

        return $this->config->getPrompt() ?: static::PROMPT;
    }

    /**
     * Read a line of user input.
     *
     * This will return a line from the input buffer (if any exist). Otherwise,
     * it will ask the user for input.
     *
     * If readline is enabled, this delegates to readline. Otherwise, it's an
     * ugly `fgets` call.
     *
     * @return string One line of user input
     */
    protected function readline()
    {
        if (!empty($this->inputBuffer)) {
            $line = array_shift($this->inputBuffer);
            if (!$line instanceof SilentInput) {
                $this->output->writeln(sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line)));
            }

            return $line;
        }

        if ($bracketedPaste = $this->config->useBracketedPaste()) {
            printf("\e[?2004h"); // Enable bracketed paste
        }

        $line = $this->readline->readline($this->getPrompt());

        if ($bracketedPaste) {
            printf("\e[?2004l"); // ... and disable it again
        }

        return $line;
    }

    /**
     * Get the shell output header.
     *
     * @return string
     */
    protected function getHeader()
    {
        return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
    }

    /**
     * Get the current version of Psy Shell.
     *
     * @return string
     */
    public function getVersion()
    {
        $separator = $this->config->useUnicode() ? '—' : '-';

        return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
    }

    /**
     * Get a PHP manual database instance.
     *
     * @return \PDO|null
     */
    public function getManualDb()
    {
        return $this->config->getManualDb();
    }

    /**
     * Autocomplete variable names.
     *
     * This is used by `readline` for tab completion.
     *
     * @param string $text
     *
     * @return mixed Array possible completions for the given input, if any
     */
    protected function autocomplete($text)
    {
        $info = readline_info();
        // $line = substr($info['line_buffer'], 0, $info['end']);

        // Check whether there's a command for this
        // $words = explode(' ', $line);
        // $firstWord = reset($words);

        // check whether this is a variable...
        $firstChar = substr($info['line_buffer'], max(0, $info['end'] - strlen($text) - 1), 1);
        if ($firstChar === '$') {
            return $this->getScopeVariableNames();
        }
    }

    /**
     * Initialize tab completion matchers.
     *
     * If tab completion is enabled this adds tab completion matchers to the
     * auto completer and sets context if needed.
     */
    protected function initializeTabCompletion()
    {
        // auto completer needs shell to be linked to configuration because of the context aware matchers
        if ($this->config->getTabCompletion()) {
            $this->completion = $this->config->getAutoCompleter();
            $this->addTabCompletionMatchers($this->config->getTabCompletionMatchers());
            foreach ($this->getTabCompletionMatchers() as $matcher) {
                if ($matcher instanceof ContextAware) {
                    $matcher->setContext($this->context);
                }
                $this->completion->addMatcher($matcher);
            }
            $this->completion->activate();
        }
    }

    /**
     * @todo Implement self-update
     * @todo Implement prompt to start update
     *
     * @return void|string
     */
    protected function writeVersionInfo()
    {
        if (PHP_SAPI !== 'cli') {
            return;
        }

        try {
            $client = $this->config->getChecker();
            if (!$client->isLatest()) {
                $this->output->writeln(sprintf('New version is available (current: %s, latest: %s)',self::VERSION, $client->getLatest()));
            }
        } catch (\InvalidArgumentException $e) {
            $this->output->writeln($e->getMessage());
        }
    }

    /**
     * Write a startup message if set.
     */
    protected function writeStartupMessage()
    {
        $message = $this->config->getStartupMessage();
        if ($message !== null && $message !== '') {
            $this->output->writeln($message);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

/**
 * Helpers for bypassing visibility restrictions, mostly used in code generated
 * by the `sudo` command.
 */
class Sudo
{
    /**
     * Fetch a property of an object, bypassing visibility restrictions.
     *
     * @param object $object
     * @param string $property property name
     *
     * @return mixed Value of $object->property
     */
    public static function fetchProperty($object, $property)
    {
        $refl = new \ReflectionObject($object);
        $prop = $refl->getProperty($property);
        $prop->setAccessible(true);

        return $prop->getValue($object);
    }

    /**
     * Assign the value of a property of an object, bypassing visibility restrictions.
     *
     * @param object $object
     * @param string $property property name
     * @param mixed  $value
     *
     * @return mixed Value of $object->property
     */
    public static function assignProperty($object, $property, $value)
    {
        $refl = new \ReflectionObject($object);
        $prop = $refl->getProperty($property);
        $prop->setAccessible(true);
        $prop->setValue($object, $value);

        return $value;
    }

    /**
     * Call a method on an object, bypassing visibility restrictions.
     *
     * @param object $object
     * @param string $method  method name
     * @param mixed  $args...
     *
     * @return mixed
     */
    public static function callMethod($object, $method, $args = null)
    {
        $args   = func_get_args();
        $object = array_shift($args);
        $method = array_shift($args);

        $refl = new \ReflectionObject($object);
        $reflMethod = $refl->getMethod($method);
        $reflMethod->setAccessible(true);

        return $reflMethod->invokeArgs($object, $args);
    }

    /**
     * Fetch a property of a class, bypassing visibility restrictions.
     *
     * @param string|object $class    class name or instance
     * @param string        $property property name
     *
     * @return mixed Value of $class::$property
     */
    public static function fetchStaticProperty($class, $property)
    {
        $refl = new \ReflectionClass($class);
        $prop = $refl->getProperty($property);
        $prop->setAccessible(true);

        return $prop->getValue();
    }

    /**
     * Assign the value of a static property of a class, bypassing visibility restrictions.
     *
     * @param string|object $class    class name or instance
     * @param string        $property property name
     * @param mixed         $value
     *
     * @return mixed Value of $class::$property
     */
    public static function assignStaticProperty($class, $property, $value)
    {
        $refl = new \ReflectionClass($class);
        $prop = $refl->getProperty($property);
        $prop->setAccessible(true);
        $prop->setValue($value);

        return $value;
    }

    /**
     * Call a static method on a class, bypassing visibility restrictions.
     *
     * @param string|object $class   class name or instance
     * @param string        $method  method name
     * @param mixed         $args...
     *
     * @return mixed
     */
    public static function callStatic($class, $method, $args = null)
    {
        $args   = func_get_args();
        $class  = array_shift($args);
        $method = array_shift($args);

        $refl = new \ReflectionClass($class);
        $reflMethod = $refl->getMethod($method);
        $reflMethod->setAccessible(true);

        return $reflMethod->invokeArgs(null, $args);
    }

    /**
     * Fetch a class constant, bypassing visibility restrictions.
     *
     * @param string|object $class class name or instance
     * @param string        $const constant name
     *
     * @return mixed
     */
    public static function fetchClassConst($class, $const)
    {
        $refl = new \ReflectionClass($class);

        return $refl->getConstant($const);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Sudo;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
use PhpParser\Node\Scalar\String_;
use PhpParser\NodeVisitorAbstract;

/**
 * A PHP Parser node visitor which rewrites property and method access to use
 * the Psy\Sudo visibility bypass methods.
 *
 * @todo handle assigning by reference
 */
class SudoVisitor extends NodeVisitorAbstract
{
    const SUDO_CLASS = 'Psy\Sudo';

    const PROPERTY_FETCH         = 'fetchProperty';
    const PROPERTY_ASSIGN        = 'assignProperty';
    const METHOD_CALL            = 'callMethod';
    const STATIC_PROPERTY_FETCH  = 'fetchStaticProperty';
    const STATIC_PROPERTY_ASSIGN = 'assignStaticProperty';
    const STATIC_CALL            = 'callStatic';
    const CLASS_CONST_FETCH      = 'fetchClassConst';

    /**
     * {@inheritdoc}
     */
    public function enterNode(Node $node)
    {
        if ($node instanceof PropertyFetch) {
            $args = array(
                $node->var,
                is_string($node->name) ? new String_($node->name) : $node->name,
            );

            return $this->prepareCall(self::PROPERTY_FETCH, $args);
        } elseif ($node instanceof Assign && $node->var instanceof PropertyFetch) {
            $target = $node->var;
            $args = array(
                $target->var,
                is_string($target->name) ? new String_($target->name) : $target->name,
                $node->expr,
            );

            return $this->prepareCall(self::PROPERTY_ASSIGN, $args);
        } elseif ($node instanceof MethodCall) {
            $args = $node->args;
            array_unshift($args, new Arg(is_string($node->name) ? new String_($node->name) : $node->name));
            array_unshift($args, new Arg($node->var));

            // not using prepareCall because the $node->args we started with are already Arg instances
            return new StaticCall(new FullyQualifiedName(self::SUDO_CLASS), self::METHOD_CALL, $args);
        } elseif ($node instanceof StaticPropertyFetch) {
            $class = $node->class instanceof Name ? (string) $node->class : $node->class;
            $args = array(
                is_string($class) ? new String_($class) : $class,
                is_string($node->name) ? new String_($node->name) : $node->name,
            );

            return $this->prepareCall(self::STATIC_PROPERTY_FETCH, $args);
        } elseif ($node instanceof Assign && $node->var instanceof StaticPropertyFetch) {
            $target = $node->var;
            $class = $target->class instanceof Name ? (string) $target->class : $target->class;
            $args = array(
                is_string($class) ? new String_($class) : $class,
                is_string($target->name) ? new String_($target->name) : $target->name,
                $node->expr,
            );

            return $this->prepareCall(self::STATIC_PROPERTY_ASSIGN, $args);
        } elseif ($node instanceof StaticCall) {
            $args = $node->args;
            $class = $node->class instanceof Name ? (string) $node->class : $node->class;
            array_unshift($args, new Arg(is_string($node->name) ? new String_($node->name) : $node->name));
            array_unshift($args, new Arg(is_string($class) ? new String_($class) : $class));

            // not using prepareCall because the $node->args we started with are already Arg instances
            return new StaticCall(new FullyQualifiedName(self::SUDO_CLASS), self::STATIC_CALL, $args);
        } elseif ($node instanceof ClassConstFetch) {
            $class = $node->class instanceof Name ? (string) $node->class : $node->class;
            $args = array(
                is_string($class) ? new String_($class) : $class,
                is_string($node->name) ? new String_($node->name) : $node->name,
            );

            return $this->prepareCall(self::CLASS_CONST_FETCH, $args);
        }
    }

    private function prepareCall($method, $args)
    {
        return new StaticCall(new FullyQualifiedName(self::SUDO_CLASS), $method, array_map(function ($arg) {
            return new Arg($arg);
        }, $args));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion;

use Psy\TabCompletion\Matcher\AbstractMatcher;

/**
 * A readline tab completion service.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class AutoCompleter
{
    /** @var Matcher\AbstractMatcher[] */
    protected $matchers;

    /**
     * Register a tab completion Matcher.
     *
     * @param AbstractMatcher $matcher
     */
    public function addMatcher(AbstractMatcher $matcher)
    {
        $this->matchers[] = $matcher;
    }

    /**
     * Activate readline tab completion.
     */
    public function activate()
    {
        readline_completion_function(array(&$this, 'callback'));
    }

    /**
     * Handle readline completion.
     *
     * @param string $input Readline current word
     * @param int    $index Current word index
     * @param array  $info  readline_info() data
     *
     * @return array
     */
    public function processCallback($input, $index, $info = array())
    {
        // Some (Windows?) systems provide incomplete `readline_info`, so let's
        // try to work around it.
        $line = $info['line_buffer'];
        if (isset($info['end'])) {
            $line = substr($line, 0, $info['end']);
        }
        if ($line === '' && $input !== '') {
            $line = $input;
        }

        $tokens = token_get_all('<?php ' . $line);

        // remove whitespaces
        $tokens = array_filter($tokens, function ($token) {
            return !AbstractMatcher::tokenIs($token, AbstractMatcher::T_WHITESPACE);
        });

        $matches = array();
        foreach ($this->matchers as $matcher) {
            if ($matcher->hasMatched($tokens)) {
                $matches = array_merge($matcher->getMatches($tokens), $matches);
            }
        }

        $matches = array_unique($matches);

        return !empty($matches) ? $matches : array('');
    }

    /**
     * The readline_completion_function callback handler.
     *
     * @see processCallback
     *
     * @param $input
     * @param $index
     *
     * @return array
     */
    public function callback($input, $index)
    {
        return $this->processCallback($input, $index, readline_info());
    }

    /**
     * Remove readline callback handler on destruct.
     */
    public function __destruct()
    {
        // PHP didn't implement the whole readline API when they first switched
        // to libedit. And they still haven't.
        //
        // So this is a thing to make PsySH work on 5.3.x:
        if (function_exists('readline_callback_handler_remove')) {
            readline_callback_handler_remove();
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

use Psy\Context;
use Psy\ContextAware;

/**
 * An abstract tab completion Matcher which implements ContextAware.
 *
 * The AutoCompleter service will inject a Context instance into all
 * ContextAware Matchers.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
abstract class AbstractContextAwareMatcher extends AbstractMatcher implements ContextAware
{
    /**
     * Context instance (for ContextAware interface).
     *
     * @var Context
     */
    protected $context;

    /**
     * ContextAware interface.
     *
     * @param Context $context
     */
    public function setContext(Context $context)
    {
        $this->context = $context;
    }

    /**
     * Get a Context variable by name.
     *
     * @param $var Variable name
     *
     * @return mixed
     */
    protected function getVariable($var)
    {
        return $this->context->get($var);
    }

    /**
     * Get all variables in the current Context.
     *
     * @return array
     */
    protected function getVariables()
    {
        return $this->context->getAll();
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

abstract class AbstractDefaultParametersMatcher extends AbstractContextAwareMatcher
{
    /**
     * @param \ReflectionParameter[] $reflectionParameters
     *
     * @return array
     */
    public function getDefaultParameterCompletion(array $reflectionParameters)
    {
        $parametersProcessed = array();

        foreach ($reflectionParameters as $parameter) {
            if (!$parameter->isDefaultValueAvailable()) {
                return array();
            }

            $defaultValue = $this->valueToShortString($parameter->getDefaultValue());

            $parametersProcessed[] = "\${$parameter->getName()} = $defaultValue";
        }

        if (empty($parametersProcessed)) {
            return array();
        }

        return array(implode(', ', $parametersProcessed) . ')');
    }

    /**
     * Takes in the default value of a parameter and turns it into a
     *  string representation that fits inline.
     * This is not 100% true to the original (newlines are inlined, for example).
     *
     * @param mixed $value
     *
     * @return string
     */
    private function valueToShortString($value)
    {
        if (!is_array($value)) {
            return json_encode($value);
        }

        $chunks = array();
        $chunksSequential = array();

        $allSequential = true;

        foreach ($value as $key => $item) {
            $allSequential = $allSequential && is_numeric($key) && $key === count($chunksSequential);

            $keyString = $this->valueToShortString($key);
            $itemString = $this->valueToShortString($item);

            $chunks[] = "{$keyString} => {$itemString}";
            $chunksSequential[] = $itemString;
        }

        $chunksToImplode = $allSequential ? $chunksSequential : $chunks;

        return '[' . implode(', ', $chunksToImplode) . ']';
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * Abstract tab completion Matcher.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
abstract class AbstractMatcher
{
    /** Syntax types */
    const CONSTANT_SYNTAX = '^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
    const VAR_SYNTAX = '^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$';
    const MISC_OPERATORS = '+-*/^|&';
    /** Token values */
    const T_OPEN_TAG = 'T_OPEN_TAG';
    const T_VARIABLE = 'T_VARIABLE';
    const T_OBJECT_OPERATOR = 'T_OBJECT_OPERATOR';
    const T_DOUBLE_COLON = 'T_DOUBLE_COLON';
    const T_NEW = 'T_NEW';
    const T_CLONE = 'T_CLONE';
    const T_NS_SEPARATOR = 'T_NS_SEPARATOR';
    const T_STRING = 'T_STRING';
    const T_WHITESPACE = 'T_WHITESPACE';
    const T_AND_EQUAL = 'T_AND_EQUAL';
    const T_BOOLEAN_AND = 'T_BOOLEAN_AND';
    const T_BOOLEAN_OR = 'T_BOOLEAN_OR';

    const T_ENCAPSED_AND_WHITESPACE = 'T_ENCAPSED_AND_WHITESPACE';
    const T_REQUIRE = 'T_REQUIRE';
    const T_REQUIRE_ONCE = 'T_REQUIRE_ONCE';
    const T_INCLUDE = 'T_INCLUDE';
    const T_INCLUDE_ONCE = 'T_INCLUDE_ONCE';

    /**
     * Check whether this matcher can provide completions for $tokens.
     *
     * @param array $tokens Tokenized readline input
     *
     * @return bool
     */
    public function hasMatched(array $tokens)
    {
        return false;
    }

    /**
     * Get current readline input word.
     *
     * @param array $tokens Tokenized readline input (see token_get_all)
     *
     * @return string
     */
    protected function getInput(array $tokens)
    {
        $var = '';
        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            $var = $firstToken[1];
        }

        return $var;
    }

    /**
     * Get current namespace and class (if any) from readline input.
     *
     * @param array $tokens Tokenized readline input (see token_get_all)
     *
     * @return string
     */
    protected function getNamespaceAndClass($tokens)
    {
        $class = '';
        while (self::hasToken(
            array(self::T_NS_SEPARATOR, self::T_STRING),
            $token = array_pop($tokens)
        )) {
            $class = $token[1] . $class;
        }

        return $class;
    }

    /**
     * Provide tab completion matches for readline input.
     *
     * @param array $tokens information substracted with get_token_all
     * @param array $info   readline_info object
     *
     * @return array The matches resulting from the query
     */
    abstract public function getMatches(array $tokens, array $info = array());

    /**
     * Check whether $word starts with $prefix.
     *
     * @param string $prefix
     * @param string $word
     *
     * @return bool
     */
    public static function startsWith($prefix, $word)
    {
        return preg_match(sprintf('#^%s#', $prefix), $word);
    }

    /**
     * Check whether $token matches a given syntax pattern.
     *
     * @param mixed  $token  A PHP token (see token_get_all)
     * @param string $syntax A syntax pattern (default: variable pattern)
     *
     * @return bool
     */
    public static function hasSyntax($token, $syntax = self::VAR_SYNTAX)
    {
        if (!is_array($token)) {
            return false;
        }

        $regexp = sprintf('#%s#', $syntax);

        return (bool) preg_match($regexp, $token[1]);
    }

    /**
     * Check whether $token type is $which.
     *
     * @param string $which A PHP token type
     * @param mixed  $token A PHP token (see token_get_all)
     *
     * @return bool
     */
    public static function tokenIs($token, $which)
    {
        if (!is_array($token)) {
            return false;
        }

        return token_name($token[0]) === $which;
    }

    /**
     * Check whether $token is an operator.
     *
     * @param mixed $token A PHP token (see token_get_all)
     *
     * @return bool
     */
    public static function isOperator($token)
    {
        if (!is_string($token)) {
            return false;
        }

        return strpos(self::MISC_OPERATORS, $token) !== false;
    }

    /**
     * Check whether $token type is present in $coll.
     *
     * @param array $coll  A list of token types
     * @param mixed $token A PHP token (see token_get_all)
     *
     * @return bool
     */
    public static function hasToken(array $coll, $token)
    {
        if (!is_array($token)) {
            return false;
        }

        return in_array(token_name($token[0]), $coll);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A class attribute tab completion Matcher.
 *
 * Given a namespace and class, this matcher provides completion for constants
 * and static properties.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ClassAttributesMatcher extends AbstractMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the nekudotayim operator
            array_pop($tokens);
        }

        $class = $this->getNamespaceAndClass($tokens);

        try {
            $reflection = new \ReflectionClass($class);
        } catch (\ReflectionException $re) {
            return array();
        }

        $vars = array_merge(
            array_map(
                function ($var) {
                    return '$' . $var;
                },
                array_keys($reflection->getStaticProperties())
            ),
            array_keys($reflection->getConstants())
        );

        return array_map(
            function ($name) use ($class) {
                return $class . '::' . $name;
            },
            array_filter(
                $vars,
                function ($var) use ($input) {
                    return AbstractMatcher::startsWith($input, $var);
                }
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
            case self::tokenIs($token, self::T_DOUBLE_COLON):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

class ClassMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
    public function getMatches(array $tokens, array $info = array())
    {
        $openBracket = array_pop($tokens);
        $functionName = array_pop($tokens);
        $methodOperator = array_pop($tokens);

        $class = $this->getNamespaceAndClass($tokens);

        try {
            $reflection = new \ReflectionClass($class);
        } catch (\ReflectionException $e) {
            // In this case the class apparently does not exist, so we can do nothing
            return array();
        }

        $methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC);

        foreach ($methods as $method) {
            if ($method->getName() === $functionName[1]) {
                return $this->getDefaultParameterCompletion($method->getParameters());
            }
        }

        return array();
    }

    public function hasMatched(array $tokens)
    {
        $openBracket = array_pop($tokens);

        if ($openBracket !== '(') {
            return false;
        }

        $functionName = array_pop($tokens);

        if (!self::tokenIs($functionName, self::T_STRING)) {
            return false;
        }

        $operator = array_pop($tokens);

        if (!self::tokenIs($operator, self::T_DOUBLE_COLON)) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A class method tab completion Matcher.
 *
 * Given a namespace and class, this matcher provides completion for static
 * methods.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ClassMethodsMatcher extends AbstractMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the nekudotayim operator
            array_pop($tokens);
        }

        $class = $this->getNamespaceAndClass($tokens);

        try {
            $reflection = new \ReflectionClass($class);
        } catch (\ReflectionException $re) {
            return array();
        }

        $methods = $reflection->getMethods(\ReflectionMethod::IS_STATIC);
        $methods = array_map(function (\ReflectionMethod $method) {
            return $method->getName();
        }, $methods);

        return array_map(
            function ($name) use ($class) {
                return $class . '::' . $name;
            },
            array_filter($methods, function ($method) use ($input) {
                return AbstractMatcher::startsWith($input, $method);
            })
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($prevToken, self::T_DOUBLE_COLON) && self::tokenIs($token, self::T_STRING):
            case self::tokenIs($token, self::T_DOUBLE_COLON):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A class name tab completion Matcher.
 *
 * This matcher provides completion for all declared classes.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ClassNamesMatcher extends AbstractMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $class = $this->getNamespaceAndClass($tokens);
        if (strlen($class) > 0 && $class[0] === '\\') {
            $class = substr($class, 1, strlen($class));
        }
        $quotedClass = preg_quote($class);

        return array_map(
            function ($className) use ($class) {
                // get the number of namespace separators
                $nsPos = substr_count($class, '\\');
                $pieces = explode('\\', $className);
                //$methods = Mirror::get($class);
                return implode('\\', array_slice($pieces, $nsPos, count($pieces)));
            },
            array_filter(
                get_declared_classes(),
                function ($className) use ($quotedClass) {
                    return AbstractMatcher::startsWith($quotedClass, $className);
                }
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        $blacklistedTokens = array(
            self::T_INCLUDE, self::T_INCLUDE_ONCE, self::T_REQUIRE, self::T_REQUIRE_ONCE,
        );

        switch (true) {
            case self::hasToken(array($blacklistedTokens), $token):
            case self::hasToken(array($blacklistedTokens), $prevToken):
            case is_string($token) && $token === '$':
                return false;
            case self::hasToken(array(self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR), $prevToken):
            case self::hasToken(array(self::T_NEW, self::T_OPEN_TAG, self::T_NS_SEPARATOR), $token):
            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
            case self::isOperator($token):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

use Psy\Command\Command;

/**
 * A Psy Command tab completion Matcher.
 *
 * This matcher provides completion for all registered Psy Command names and
 * aliases.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class CommandsMatcher extends AbstractMatcher
{
    /** @var string[] */
    protected $commands = array();

    /**
     * CommandsMatcher constructor.
     *
     * @param Command[] $commands
     */
    public function __construct(array $commands)
    {
        $this->setCommands($commands);
    }

    /**
     * Set Commands for completion.
     *
     * @param Command[] $commands
     */
    public function setCommands(array $commands)
    {
        $names = array();
        foreach ($commands as $command) {
            $names = array_merge(array($command->getName()), $names);
            $names = array_merge($command->getAliases(), $names);
        }
        $this->commands = $names;
    }

    /**
     * Check whether a command $name is defined.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function isCommand($name)
    {
        return in_array($name, $this->commands);
    }

    /**
     * Check whether input matches a defined command.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function matchCommand($name)
    {
        foreach ($this->commands as $cmd) {
            if ($this->startsWith($name, $cmd)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        return array_filter($this->commands, function ($command) use ($input) {
            return AbstractMatcher::startsWith($input, $command);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        /* $openTag */ array_shift($tokens);
        $command = array_shift($tokens);

        switch (true) {
            case self::tokenIs($command, self::T_STRING) &&
                !$this->isCommand($command[1]) &&
                $this->matchCommand($command[1]) &&
                empty($tokens):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A constant name tab completion Matcher.
 *
 * This matcher provides completion for all defined constants.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ConstantsMatcher extends AbstractMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $const = $this->getInput($tokens);

        return array_filter(array_keys(get_defined_constants()), function ($constant) use ($const) {
            return AbstractMatcher::startsWith($const, $constant);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($prevToken, self::T_NEW):
            case self::tokenIs($prevToken, self::T_NS_SEPARATOR):
                return false;
            case self::hasToken(array(self::T_OPEN_TAG, self::T_STRING), $token):
            case self::isOperator($token):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

class FunctionDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
    public function getMatches(array $tokens, array $info = array())
    {
        array_pop($tokens); // open bracket

        $functionName = array_pop($tokens);

        try {
            $reflection = new \ReflectionFunction($functionName[1]);
        } catch (\ReflectionException $e) {
            return array();
        }

        $parameters = $reflection->getParameters();

        return $this->getDefaultParameterCompletion($parameters);
    }

    public function hasMatched(array $tokens)
    {
        $openBracket = array_pop($tokens);

        if ($openBracket !== '(') {
            return false;
        }

        $functionName = array_pop($tokens);

        if (!self::tokenIs($functionName, self::T_STRING)) {
            return false;
        }

        if (!function_exists($functionName[1])) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A function name tab completion Matcher.
 *
 * This matcher provides completion for all internal and user-defined functions.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class FunctionsMatcher extends AbstractMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $func = $this->getInput($tokens);

        $functions = get_defined_functions();
        $allFunctions = array_merge($functions['user'], $functions['internal']);

        return array_filter($allFunctions, function ($function) use ($func) {
            return AbstractMatcher::startsWith($func, $function);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($prevToken, self::T_NEW):
                return false;
            case self::hasToken(array(self::T_OPEN_TAG, self::T_STRING), $token):
            case self::isOperator($token):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A PHP keyword tab completion Matcher.
 *
 * This matcher provides completion for all function-like PHP keywords.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class KeywordsMatcher extends AbstractMatcher
{
    protected $keywords = array(
        'array', 'clone', 'declare', 'die', 'echo', 'empty', 'eval', 'exit', 'include',
        'include_once', 'isset', 'list', 'print',  'require', 'require_once', 'unset',
    );

    protected $mandatoryStartKeywords = array(
        'die', 'echo', 'print', 'unset',
    );

    /**
     * Get all (completable) PHP keywords.
     *
     * @return array
     */
    public function getKeywords()
    {
        return $this->keywords;
    }

    /**
     * Check whether $keyword is a (completable) PHP keyword.
     *
     * @param string $keyword
     *
     * @return bool
     */
    public function isKeyword($keyword)
    {
        return in_array($keyword, $this->keywords);
    }

    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        return array_filter($this->keywords, function ($keyword) use ($input) {
            return AbstractMatcher::startsWith($input, $keyword);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
//            case is_string($token) && $token === '$':
            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $prevToken) &&
                self::tokenIs($token, self::T_STRING):
            case self::isOperator($token):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A MongoDB Client tab completion Matcher.
 *
 * This matcher provides completion for MongoClient database names.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class MongoClientMatcher extends AbstractContextAwareMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the object operator
            array_pop($tokens);
        }
        $objectToken = array_pop($tokens);
        $objectName = str_replace('$', '', $objectToken[1]);
        $object = $this->getVariable($objectName);

        if (!$object instanceof \MongoClient) {
            return array();
        }

        $list = $object->listDBs();

        return array_filter(
            array_map(function ($info) {
                return $info['name'];
            }, $list['databases']),
            function ($var) use ($input) {
                return AbstractMatcher::startsWith($input, $var);
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A MongoDB tab completion Matcher.
 *
 * This matcher provides completion for Mongo collection names.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class MongoDatabaseMatcher extends AbstractContextAwareMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the object operator
            array_pop($tokens);
        }
        $objectToken = array_pop($tokens);
        $objectName = str_replace('$', '', $objectToken[1]);
        $object = $this->getVariable($objectName);

        if (!$object instanceof \MongoDB) {
            return array();
        }

        return array_filter(
            $object->getCollectionNames(),
            function ($var) use ($input) {
                return AbstractMatcher::startsWith($input, $var);
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

use InvalidArgumentException;

/**
 * An object attribute tab completion Matcher.
 *
 * This matcher provides completion for properties of objects in the current
 * Context.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ObjectAttributesMatcher extends AbstractContextAwareMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the object operator
            array_pop($tokens);
        }
        $objectToken = array_pop($tokens);
        if (!is_array($objectToken)) {
            return array();
        }
        $objectName = str_replace('$', '', $objectToken[1]);

        try {
            $object = $this->getVariable($objectName);
        } catch (InvalidArgumentException $e) {
            return array();
        }

        if (!is_object($object)) {
            return array();
        }

        return array_filter(
            array_keys(get_class_vars(get_class($object))),
            function ($var) use ($input) {
                return AbstractMatcher::startsWith($input, $var);
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

class ObjectMethodDefaultParametersMatcher extends AbstractDefaultParametersMatcher
{
    public function getMatches(array $tokens, array $info = array())
    {
        $openBracket = array_pop($tokens);
        $functionName = array_pop($tokens);
        $methodOperator = array_pop($tokens);

        $objectToken = array_pop($tokens);
        if (!is_array($objectToken)) {
            return array();
        }

        $objectName = str_replace('$', '', $objectToken[1]);

        try {
            $object = $this->getVariable($objectName);
            $reflection = new \ReflectionObject($object);
        } catch (InvalidArgumentException $e) {
            return array();
        } catch (\ReflectionException $e) {
            return array();
        }

        $methods = $reflection->getMethods();

        foreach ($methods as $method) {
            if ($method->getName() === $functionName[1]) {
                return $this->getDefaultParameterCompletion($method->getParameters());
            }
        }

        return array();
    }

    public function hasMatched(array $tokens)
    {
        $openBracket = array_pop($tokens);

        if ($openBracket !== '(') {
            return false;
        }

        $functionName = array_pop($tokens);

        if (!self::tokenIs($functionName, self::T_STRING)) {
            return false;
        }

        $operator = array_pop($tokens);

        if (!self::tokenIs($operator, self::T_OBJECT_OPERATOR)) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

use InvalidArgumentException;

/**
 * An object method tab completion Matcher.
 *
 * This matcher provides completion for methods of objects in the current
 * Context.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class ObjectMethodsMatcher extends AbstractContextAwareMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $input = $this->getInput($tokens);

        $firstToken = array_pop($tokens);
        if (self::tokenIs($firstToken, self::T_STRING)) {
            // second token is the object operator
            array_pop($tokens);
        }
        $objectToken = array_pop($tokens);
        if (!is_array($objectToken)) {
            return array();
        }
        $objectName = str_replace('$', '', $objectToken[1]);

        try {
            $object = $this->getVariable($objectName);
        } catch (InvalidArgumentException $e) {
            return array();
        }

        if (!is_object($object)) {
            return array();
        }

        return array_filter(
            get_class_methods($object),
            function ($var) use ($input) {
                return AbstractMatcher::startsWith($input, $var) &&
                    // also check that we do not suggest invoking a super method(__construct, __wakeup, …)
                    !AbstractMatcher::startsWith('__', $var);
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);
        $prevToken = array_pop($tokens);

        switch (true) {
            case self::tokenIs($token, self::T_OBJECT_OPERATOR):
            case self::tokenIs($prevToken, self::T_OBJECT_OPERATOR):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\TabCompletion\Matcher;

/**
 * A variable name tab completion Matcher.
 *
 * This matcher provides completion for variable names in the current Context.
 *
 * @author Marc Garcia <markcial@gmail.com>
 */
class VariablesMatcher extends AbstractContextAwareMatcher
{
    /**
     * {@inheritdoc}
     */
    public function getMatches(array $tokens, array $info = array())
    {
        $var = str_replace('$', '', $this->getInput($tokens));

        return array_filter(array_keys($this->getVariables()), function ($variable) use ($var) {
            return AbstractMatcher::startsWith($var, $variable);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function hasMatched(array $tokens)
    {
        $token = array_pop($tokens);

        switch (true) {
            case self::hasToken(array(self::T_OPEN_TAG, self::T_VARIABLE), $token):
            case is_string($token) && $token === '$':
            case self::isOperator($token):
                return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Util;

/**
 * A docblock representation.
 *
 * Based on PHP-DocBlock-Parser by Paul Scott:
 *
 * {@link http://www.github.com/icio/PHP-DocBlock-Parser}
 *
 * @author Paul Scott <paul@duedil.com>
 * @author Justin Hileman <justin@justinhileman.info>
 */
class Docblock
{
    /**
     * Tags in the docblock that have a whitespace-delimited number of parameters
     * (such as `@param type var desc` and `@return type desc`) and the names of
     * those parameters.
     *
     * @var array
     */
    public static $vectors = array(
        'throws' => array('type', 'desc'),
        'param'  => array('type', 'var', 'desc'),
        'return' => array('type', 'desc'),
    );

    protected $reflector;

    /**
     * The description of the symbol.
     *
     * @var string
     */
    public $desc;

    /**
     * The tags defined in the docblock.
     *
     * The array has keys which are the tag names (excluding the @) and values
     * that are arrays, each of which is an entry for the tag.
     *
     * In the case where the tag name is defined in {@see DocBlock::$vectors} the
     * value within the tag-value array is an array in itself with keys as
     * described by {@see DocBlock::$vectors}.
     *
     * @var array
     */
    public $tags;

    /**
     * The entire DocBlock comment that was parsed.
     *
     * @var string
     */
    public $comment;

    /**
     * Docblock constructor.
     *
     * @param \Reflector $reflector
     */
    public function __construct(\Reflector $reflector)
    {
        $this->reflector = $reflector;
        $this->setComment($reflector->getDocComment());
    }

    /**
     * Set and parse the docblock comment.
     *
     * @param string $comment The docblock
     */
    protected function setComment($comment)
    {
        $this->desc    = '';
        $this->tags    = array();
        $this->comment = $comment;

        $this->parseComment($comment);
    }

    /**
     * Find the length of the docblock prefix.
     *
     * @param array $lines
     *
     * @return int Prefix length
     */
    protected static function prefixLength(array $lines)
    {
        // find only lines with interesting things
        $lines = array_filter($lines, function ($line) {
            return substr($line, strspn($line, "* \t\n\r\0\x0B"));
        });

        // if we sort the lines, we only have to compare two items
        sort($lines);

        $first = reset($lines);
        $last  = end($lines);

        // find the longest common substring
        $count = min(strlen($first), strlen($last));
        for ($i = 0; $i < $count; $i++) {
            if ($first[$i] !== $last[$i]) {
                return $i;
            }
        }

        return $count;
    }

    /**
     * Parse the comment into the component parts and set the state of the object.
     *
     * @param string $comment The docblock
     */
    protected function parseComment($comment)
    {
        // Strip the opening and closing tags of the docblock
        $comment = substr($comment, 3, -2);

        // Split into arrays of lines
        $comment = array_filter(preg_split('/\r?\n\r?/', $comment));

        // Trim asterisks and whitespace from the beginning and whitespace from the end of lines
        $prefixLength = self::prefixLength($comment);
        $comment = array_map(function ($line) use ($prefixLength) {
            return rtrim(substr($line, $prefixLength));
        }, $comment);

        // Group the lines together by @tags
        $blocks = array();
        $b = -1;
        foreach ($comment as $line) {
            if (self::isTagged($line)) {
                $b++;
                $blocks[] = array();
            } elseif ($b === -1) {
                $b = 0;
                $blocks[] = array();
            }
            $blocks[$b][] = $line;
        }

        // Parse the blocks
        foreach ($blocks as $block => $body) {
            $body = trim(implode("\n", $body));

            if ($block === 0 && !self::isTagged($body)) {
                // This is the description block
                $this->desc = $body;
            } else {
                // This block is tagged
                $tag  = substr(self::strTag($body), 1);
                $body = ltrim(substr($body, strlen($tag) + 2));

                if (isset(self::$vectors[$tag])) {
                    // The tagged block is a vector
                    $count = count(self::$vectors[$tag]);
                    if ($body) {
                        $parts = preg_split('/\s+/', $body, $count);
                    } else {
                        $parts = array();
                    }

                    // Default the trailing values
                    $parts = array_pad($parts, $count, null);

                    // Store as a mapped array
                    $this->tags[$tag][] = array_combine(self::$vectors[$tag], $parts);
                } else {
                    // The tagged block is only text
                    $this->tags[$tag][] = $body;
                }
            }
        }
    }

    /**
     * Whether or not a docblock contains a given @tag.
     *
     * @param string $tag The name of the @tag to check for
     *
     * @return bool
     */
    public function hasTag($tag)
    {
        return is_array($this->tags) && array_key_exists($tag, $this->tags);
    }

    /**
     * The value of a tag.
     *
     * @param string $tag
     *
     * @return array
     */
    public function tag($tag)
    {
        return $this->hasTag($tag) ? $this->tags[$tag] : null;
    }

    /**
     * Whether or not a string begins with a @tag.
     *
     * @param string $str
     *
     * @return bool
     */
    public static function isTagged($str)
    {
        return isset($str[1]) && $str[0] === '@' && ctype_alpha($str[1]);
    }

    /**
     * The tag at the beginning of a string.
     *
     * @param string $str
     *
     * @return string|null
     */
    public static function strTag($str)
    {
        if (preg_match('/^@[a-z0-9_]+/', $str, $matches)) {
            return $matches[0];
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Util;

/**
 * A static class to wrap JSON encoding/decoding with PsySH's default options.
 */
class Json
{
    /**
     * Encode a value as JSON.
     *
     * @param mixed $val
     * @param int   $opt
     *
     * @return string
     */
    public static function encode($val, $opt = 0)
    {
        if (version_compare(PHP_VERSION, '5.4', '>=')) {
            $opt |= JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
        }

        return json_encode($val, $opt);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Util;

use Psy\Exception\RuntimeException;
use Psy\Reflection\ReflectionConstant;

/**
 * A utility class for getting Reflectors.
 */
class Mirror
{
    const CONSTANT        = 1;
    const METHOD          = 2;
    const STATIC_PROPERTY = 4;
    const PROPERTY        = 8;

    /**
     * Get a Reflector for a function, class or instance, constant, method or property.
     *
     * Optionally, pass a $filter param to restrict the types of members checked. For example, to only Reflectors for
     * static properties and constants, pass:
     *
     *    $filter = Mirror::CONSTANT | Mirror::STATIC_PROPERTY
     *
     * @throws \Psy\Exception\RuntimeException when a $member specified but not present on $value
     * @throws \InvalidArgumentException       if $value is something other than an object or class/function name
     *
     * @param mixed  $value  Class or function name, or variable instance
     * @param string $member Optional: property, constant or method name (default: null)
     * @param int    $filter (default: CONSTANT | METHOD | PROPERTY | STATIC_PROPERTY)
     *
     * @return Reflector
     */
    public static function get($value, $member = null, $filter = 15)
    {
        if ($member === null && is_string($value) && function_exists($value)) {
            return new \ReflectionFunction($value);
        }

        $class = self::getClass($value);

        if ($member === null) {
            return $class;
        } elseif ($filter & self::CONSTANT && $class->hasConstant($member)) {
            return new ReflectionConstant($class, $member);
        } elseif ($filter & self::METHOD && $class->hasMethod($member)) {
            return $class->getMethod($member);
        } elseif ($filter & self::PROPERTY && $class->hasProperty($member)) {
            return $class->getProperty($member);
        } elseif ($filter & self::STATIC_PROPERTY && $class->hasProperty($member) && $class->getProperty($member)->isStatic()) {
            return $class->getProperty($member);
        } else {
            throw new RuntimeException(sprintf(
                'Unknown member %s on class %s',
                $member,
                is_object($value) ? get_class($value) : $value
            ));
        }
    }

    /**
     * Get a ReflectionClass (or ReflectionObject) if possible.
     *
     * @throws \InvalidArgumentException if $value is not a class name or instance
     *
     * @param mixed $value
     *
     * @return \ReflectionClass
     */
    private static function getClass($value)
    {
        if (is_object($value)) {
            return new \ReflectionObject($value);
        }

        if (!is_string($value)) {
            throw new \InvalidArgumentException('Mirror expects an object or class');
        } elseif (!class_exists($value) && !interface_exists($value) && !(function_exists('trait_exists') && trait_exists($value))) {
            throw new \InvalidArgumentException('Unknown class or function: ' . $value);
        }

        return new \ReflectionClass($value);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Util;

/**
 * String utility methods.
 *
 * @author ju1ius
 */
class Str
{
    const UNVIS_RX = <<<'EOS'
/
    \\(?:
        ((?:040)|s)
        | (240)
        | (?: M-(.) )
        | (?: M\^(.) )
        | (?: \^(.) )
    )
/xS
EOS;

    /**
     * Decodes a string encoded by libsd's strvis.
     *
     * From `man 3 vis`:
     *
     * Use an ‘M’ to represent meta characters (characters with the 8th bit set),
     * and use a caret ‘^’ to represent control characters (see iscntrl(3)).
     * The following formats are used:
     *
     *      \040    Represents ASCII space.
     *
     *      \240    Represents Meta-space (&nbsp in HTML).
     *
     *      \M-C    Represents character ‘C’ with the 8th bit set.
     *              Spans characters ‘\241’ through ‘\376’.
     *
     *      \M^C    Represents control character ‘C’ with the 8th bit set.
     *              Spans characters ‘\200’ through ‘\237’, and ‘\377’ (as ‘\M^?’).
     *
     *      \^C     Represents the control character ‘C’.
     *              Spans characters ‘\000’ through ‘\037’, and ‘\177’ (as ‘\^?’).
     *
     * The other formats are supported by PHP's stripcslashes,
     * except for the \s sequence (ASCII space).
     *
     * @param string $input The string to decode
     *
     * @return string
     */
    public static function unvis($input)
    {
        $output = preg_replace_callback(self::UNVIS_RX, 'self::unvisReplace', $input);
        // other escapes & octal are handled by stripcslashes
        return stripcslashes($output);
    }

    /**
     * Callback for Str::unvis.
     *
     * @param array $match The matches passed by preg_replace_callback
     *
     * @return string
     */
    protected static function unvisReplace($match)
    {
        // \040, \s
        if (!empty($match[1])) {
            return "\x20";
        }
        // \240
        if (!empty($match[2])) {
            return "\xa0";
        }
        // \M-(.)
        if (isset($match[3]) && $match[3] !== '') {
            $chr = $match[3];
            // unvis S_META1
            $cp = 0200;
            $cp |= ord($chr);

            return chr($cp);
        }
        // \M^(.)
        if (isset($match[4]) && $match[4] !== '') {
            $chr = $match[4];
            // unvis S_META | S_CTRL
            $cp = 0200;
            $cp |= ($chr === '?') ? 0177 : ord($chr) & 037;

            return chr($cp);
        }
        // \^(.)
        if (isset($match[5]) && $match[5] !== '') {
            $chr = $match[5];
            // unvis S_CTRL
            $cp = 0;
            $cp |= ($chr === '?') ? 0177 : ord($chr) & 037;

            return chr($cp);
        }
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VarDumper;

use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;

/**
 * A PsySH-specialized VarCloner.
 */
class Cloner extends VarCloner
{
    private $filter = 0;

    /**
     * {@inheritdoc}
     */
    public function cloneVar($var, $filter = 0)
    {
        $this->filter = $filter;

        return parent::cloneVar($var, $filter);
    }

    /**
     * {@inheritdoc}
     */
    protected function castResource(Stub $stub, $isNested)
    {
        return Caster::EXCLUDE_VERBOSE & $this->filter ? array() : parent::castResource($stub, $isNested);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VarDumper;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\VarDumper\Cloner\Cursor;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
 * A PsySH-specialized CliDumper.
 */
class Dumper extends CliDumper
{
    private $formatter;
    private $forceArrayIndexes;

    protected static $onlyControlCharsRx = '/^[\x00-\x1F\x7F]+$/';
    protected static $controlCharsRx = '/([\x00-\x1F\x7F]+)/';
    protected static $controlCharsMap = array(
        "\0"   => '\0',
        "\t"   => '\t',
        "\n"   => '\n',
        "\v"   => '\v',
        "\f"   => '\f',
        "\r"   => '\r',
        "\033" => '\e',
    );

    public function __construct(OutputFormatter $formatter, $forceArrayIndexes = false)
    {
        $this->formatter = $formatter;
        $this->forceArrayIndexes = $forceArrayIndexes;
        parent::__construct();
        $this->setColors(false);
    }

    /**
     * {@inheritdoc}
     */
    public function enterHash(Cursor $cursor, $type, $class, $hasChild)
    {
        if (Cursor::HASH_INDEXED === $type || Cursor::HASH_ASSOC === $type) {
            $class = 0;
        }
        parent::enterHash($cursor, $type, $class, $hasChild);
    }

    /**
     * {@inheritdoc}
     */
    protected function dumpKey(Cursor $cursor)
    {
        if ($this->forceArrayIndexes || Cursor::HASH_INDEXED !== $cursor->hashType) {
            parent::dumpKey($cursor);
        }
    }

    protected function style($style, $value, $attr = array())
    {
        if ('ref' === $style) {
            $value = strtr($value, '@', '#');
        }

        $styled = '';
        $map = self::$controlCharsMap;
        $cchr = $this->styles['cchr'];

        $chunks = preg_split(self::$controlCharsRx, $value, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        foreach ($chunks as $chunk) {
            if (preg_match(self::$onlyControlCharsRx, $chunk)) {
                $chars = '';
                $i = 0;
                do {
                    $chars .= isset($map[$chunk[$i]]) ? $map[$chunk[$i]] : sprintf('\x%02X', ord($chunk[$i]));
                } while (isset($chunk[++$i]));

                $chars = $this->formatter->escape($chars);
                $styled .= "<{$cchr}>{$chars}</{$cchr}>";
            } else {
                $styled .= $this->formatter->escape($chunk);
            }
        }

        $style = $this->styles[$style];

        return "<{$style}>{$styled}</{$style}>";
    }

    /**
     * {@inheritdoc}
     */
    protected function dumpLine($depth, $endOfValue = false)
    {
        if ($endOfValue && 0 < $depth) {
            $this->line .= ',';
        }
        $this->line = $this->formatter->format($this->line);
        parent::dumpLine($depth, $endOfValue);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VarDumper;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;

/**
 * A Presenter service.
 */
class Presenter
{
    const VERBOSE = 1;

    private $cloner;
    private $dumper;
    private $exceptionsImportants = array(
        "\0*\0message",
        "\0*\0code",
        "\0*\0file",
        "\0*\0line",
        "\0Exception\0previous",
    );
    private $styles = array(
        'num'       => 'number',
        'const'     => 'const',
        'str'       => 'string',
        'cchr'      => 'default',
        'note'      => 'class',
        'ref'       => 'default',
        'public'    => 'public',
        'protected' => 'protected',
        'private'   => 'private',
        'meta'      => 'comment',
        'key'       => 'comment',
        'index'     => 'number',
    );

    public function __construct(OutputFormatter $formatter, $forceArrayIndexes = false)
    {
        // Work around https://github.com/symfony/symfony/issues/23572
        $oldLocale = setlocale(LC_NUMERIC, 0);
        setlocale(LC_NUMERIC, 'C');

        $this->dumper = new Dumper($formatter, $forceArrayIndexes);
        $this->dumper->setStyles($this->styles);

        // Now put the locale back
        setlocale(LC_NUMERIC, $oldLocale);

        $this->cloner = new Cloner();
        $this->cloner->addCasters(array('*' => function ($obj, array $a, Stub $stub, $isNested, $filter = 0) {
            if ($filter || $isNested) {
                if ($obj instanceof \Exception) {
                    $a = Caster::filter($a, Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY, $this->exceptionsImportants);
                } else {
                    $a = Caster::filter($a, Caster::EXCLUDE_PROTECTED | Caster::EXCLUDE_PRIVATE);
                }
            }

            return $a;
        }));
    }

    /**
     * Register casters.
     *
     * @see http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
     *
     * @param callable[] $casters A map of casters
     */
    public function addCasters(array $casters)
    {
        $this->cloner->addCasters($casters);
    }

    /**
     * Present a reference to the value.
     *
     * @param mixed $value
     *
     * @return string
     */
    public function presentRef($value)
    {
        return $this->present($value, 0);
    }

    /**
     * Present a full representation of the value.
     *
     * If $depth is 0, the value will be presented as a ref instead.
     *
     * @param mixed $value
     * @param int   $depth   (default: null)
     * @param int   $options One of Presenter constants
     *
     * @return string
     */
    public function present($value, $depth = null, $options = 0)
    {
        $data = $this->cloner->cloneVar($value, !($options & self::VERBOSE) ? Caster::EXCLUDE_VERBOSE : 0);

        if (null !== $depth) {
            $data = $data->withMaxDepth($depth);
        }

        // Work around https://github.com/symfony/symfony/issues/23572
        $oldLocale = setlocale(LC_NUMERIC, 0);
        setlocale(LC_NUMERIC, 'C');

        $output = '';
        $this->dumper->dump($data, function ($line, $depth) use (&$output) {
            if ($depth >= 0) {
                if ('' !== $output) {
                    $output .= PHP_EOL;
                }
                $output .= str_repeat('  ', $depth) . $line;
            }
        });

        // Now put the locale back
        setlocale(LC_NUMERIC, $oldLocale);

        return OutputFormatter::escape($output);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VarDumper;

/**
 * Presenter injects itself as a dependency to all objects which
 * implement PresenterAware.
 */
interface PresenterAware
{
    /**
     * Set a reference to the Presenter.
     *
     * @param Presenter $presenter
     */
    public function setPresenter(Presenter $presenter);
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VersionUpdater;

interface Checker
{
    const ALWAYS = 'always';
    const DAILY = 'daily';
    const WEEKLY = 'weekly';
    const MONTHLY = 'monthly';
    const NEVER = 'never';

    /**
     * @return bool
     */
    public function isLatest();

    /**
     * @return string
     */
    public function getLatest();
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VersionUpdater;

use Psy\Shell;

class GitHubChecker implements Checker
{
    const URL = 'https://api.github.com/repos/bobthecow/psysh/releases/latest';

    private $latest;

    /**
     * @return bool
     */
    public function isLatest()
    {
        return version_compare(Shell::VERSION, $this->getLatest(), '>=');
    }

    /**
     * @return string
     */
    public function getLatest()
    {
        if (!isset($this->latest)) {
            $this->setLatest($this->getVersionFromTag());
        }

        return $this->latest;
    }

    /**
     * @param string $version
     */
    public function setLatest($version)
    {
        $this->latest = $version;
    }

    /**
     * @return string|null
     */
    private function getVersionFromTag()
    {
        $contents = $this->fetchLatestRelease();
        if (!$contents || !isset($contents->tag_name)) {
            throw new \InvalidArgumentException('Unable to check for updates');
        }
        $this->setLatest($contents->tag_name);

        return $this->getLatest();
    }

    /**
     * Set to public to make testing easier.
     *
     * @return mixed
     */
    public function fetchLatestRelease()
    {
        $context = stream_context_create(array(
            'http' => array(
                'user_agent' => 'PsySH/' . Shell::VERSION,
                'timeout'    => 3,
            ),
        ));

        set_error_handler(function () {
            // Just ignore all errors with this. The checker will throw an exception
            // if it doesn't work :)
        });

        $result = @file_get_contents(self::URL, false, $context);

        restore_error_handler();

        return json_decode($result);
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VersionUpdater;

use Psy\Shell;

class IntervalChecker extends GitHubChecker
{
    private $cacheFile;
    private $interval;

    public function __construct($cacheFile, $interval)
    {
        $this->cacheFile = $cacheFile;
        $this->interval = $interval;
    }

    public function fetchLatestRelease()
    {
        // Read the cached file
        $cached = json_decode(@file_get_contents($this->cacheFile, false));
        if ($cached && isset($cached->last_check) && isset($cached->release)) {
            $now = new \DateTime();
            $lastCheck = new \DateTime($cached->last_check);
            if ($lastCheck >= $now->sub($this->getDateInterval())) {
                return $cached->release;
            }
        }

        // Fall back to fetching from GitHub
        $release = parent::fetchLatestRelease();
        if ($release && isset($release->tag_name)) {
            $this->updateCache($release);
        }

        return $release;
    }

    private function getDateInterval()
    {
        switch ($this->interval) {
            case Checker::DAILY:
                return new \DateInterval('P1D');
            case Checker::WEEKLY:
                return new \DateInterval('P1W');
            case Checker::MONTHLY:
                return new \DateInterval('P1M');
        }
    }

    private function updateCache($release)
    {
        $data = array(
            'last_check' => date(DATE_ATOM),
            'release'    => $release,
        );

        file_put_contents($this->cacheFile, json_encode($data));
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\VersionUpdater;

use Psy\Shell;

/**
 * A version checker stub which always thinks the current verion is up to date.
 */
class NoopChecker implements Checker
{
    /**
     * @return bool
     */
    public function isLatest()
    {
        return true;
    }

    /**
     * @return string
     */
    public function getLatest()
    {
        return Shell::VERSION;
    }
}
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy;

use Psy\VersionUpdater\GitHubChecker;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use XdgBaseDir\Xdg;

if (!function_exists('Psy\sh')) {
    /**
     * Command to return the eval-able code to startup PsySH.
     *
     *     eval(\Psy\sh());
     *
     * @return string
     */
    function sh()
    {
        return 'extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : null));';
    }
}

if (!function_exists('Psy\debug')) {
    /**
     * Invoke a Psy Shell from the current context.
     *
     * For example:
     *
     *     foreach ($items as $item) {
     *         \Psy\debug(get_defined_vars());
     *     }
     *
     * If you would like your shell interaction to affect the state of the
     * current context, you can extract() the values returned from this call:
     *
     *     foreach ($items as $item) {
     *         extract(\Psy\debug(get_defined_vars()));
     *         var_dump($item); // will be whatever you set $item to in Psy Shell
     *     }
     *
     * Optionally, supply an object as the `$boundObject` parameter. This
     * determines the value `$this` will have in the shell, and sets up class
     * scope so that private and protected members are accessible:
     *
     *     class Foo {
     *         function bar() {
     *             \Psy\debug(get_defined_vars(), $this);
     *         }
     *     }
     *
     * This only really works in PHP 5.4+ and HHVM 3.5+, so upgrade already.
     *
     * @param array  $vars        Scope variables from the calling context (default: array())
     * @param object $boundObject Bound object ($this) value for the shell
     *
     * @return array Scope variables from the debugger session
     */
    function debug(array $vars = array(), $boundObject = null)
    {
        echo PHP_EOL;

        $sh = new Shell();
        $sh->setScopeVariables($vars);

        // Show a couple of lines of call context for the debug session.
        //
        // @todo come up with a better way of doing this which doesn't involve injecting input :-P
        if ($sh->has('whereami')) {
            $sh->addInput('whereami -n2', true);
        }

        if ($boundObject !== null) {
            $sh->setBoundObject($boundObject);
        }

        $sh->run();

        return $sh->getScopeVariables(false);
    }
}

if (!function_exists('Psy\info')) {
    /**
     * Get a bunch of debugging info about the current PsySH environment and
     * configuration.
     *
     * If a Configuration param is passed, that configuration is stored and
     * used for the current shell session, and no debugging info is returned.
     *
     * @param Configuration|null $config
     *
     * @return array|null
     */
    function info(Configuration $config = null)
    {
        static $lastConfig;
        if ($config !== null) {
            $lastConfig = $config;

            return;
        }

        $xdg = new Xdg();
        $home = rtrim(str_replace('\\', '/', $xdg->getHomeDir()), '/');
        $homePattern = '#^' . preg_quote($home, '#') . '/#';

        $prettyPath = function ($path) use ($homePattern) {
            if (is_string($path)) {
                return preg_replace($homePattern, '~/', $path);
            } else {
                return $path;
            }
        };

        $config = $lastConfig ?: new Configuration();

        $core = array(
            'PsySH version'       => Shell::VERSION,
            'PHP version'         => PHP_VERSION,
            'default includes'    => $config->getDefaultIncludes(),
            'require semicolons'  => $config->requireSemicolons(),
            'error logging level' => $config->errorLoggingLevel(),
            'config file'         => array(
                'default config file' => $prettyPath($config->getConfigFile()),
                'local config file'   => $prettyPath($config->getLocalConfigFile()),
                'PSYSH_CONFIG env'    => $prettyPath(getenv('PSYSH_CONFIG')),
            ),
            // 'config dir'  => $config->getConfigDir(),
            // 'data dir'    => $config->getDataDir(),
            // 'runtime dir' => $config->getRuntimeDir(),
        );

        // Use an explicit, fresh update check here, rather than relying on whatever is in $config.
        $checker = new GitHubChecker();
        $updateAvailable = null;
        $latest = null;
        try {
            $updateAvailable = !$checker->isLatest();
            $latest = $checker->getLatest();
        } catch (\Exception $e) {
        }

        $updates = array(
            'update available'       => $updateAvailable,
            'latest release version' => $latest,
            'update check interval'  => $config->getUpdateCheck(),
            'update cache file'      => $prettyPath($config->getUpdateCheckCacheFile()),
        );

        if ($config->hasReadline()) {
            $info = readline_info();

            $readline = array(
                'readline available' => true,
                'readline enabled'   => $config->useReadline(),
                'readline service'   => get_class($config->getReadline()),
            );

            if (isset($info['library_version'])) {
                $readline['readline library'] = $info['library_version'];
            }

            if (isset($info['readline_name']) && $info['readline_name'] !== '') {
                $readline['readline name'] = $info['readline_name'];
            }
        } else {
            $readline = array(
                'readline available' => false,
            );
        }

        $pcntl = array(
            'pcntl available' => function_exists('pcntl_signal'),
            'posix available' => function_exists('posix_getpid'),
        );

        $disabledFuncs = array_map('trim', explode(',', ini_get('disable_functions')));
        if (in_array('pcntl_signal', $disabledFuncs) || in_array('pcntl_fork', $disabledFuncs)) {
            $pcntl['pcntl disabled'] = true;
        }

        $history = array(
            'history file'     => $prettyPath($config->getHistoryFile()),
            'history size'     => $config->getHistorySize(),
            'erase duplicates' => $config->getEraseDuplicates(),
        );

        $docs = array(
            'manual db file'   => $prettyPath($config->getManualDbFile()),
            'sqlite available' => true,
        );

        try {
            if ($db = $config->getManualDb()) {
                if ($q = $db->query('SELECT * FROM meta;')) {
                    $q->setFetchMode(\PDO::FETCH_KEY_PAIR);
                    $meta = $q->fetchAll();

                    foreach ($meta as $key => $val) {
                        switch ($key) {
                            case 'built_at':
                                $d = new \DateTime('@' . $val);
                                $val = $d->format(\DateTime::RFC2822);
                                break;
                        }
                        $key = 'db ' . str_replace('_', ' ', $key);
                        $docs[$key] = $val;
                    }
                } else {
                    $docs['db schema'] = '0.1.0';
                }
            }
        } catch (Exception\RuntimeException $e) {
            if ($e->getMessage() === 'SQLite PDO driver not found') {
                $docs['sqlite available'] = false;
            } else {
                throw $e;
            }
        }

        $autocomplete = array(
            'tab completion enabled' => $config->getTabCompletion(),
            'custom matchers'        => array_map('get_class', $config->getTabCompletionMatchers()),
            'bracketed paste'        => $config->useBracketedPaste(),
        );

        return array_merge($core, compact('updates', 'pcntl', 'readline', 'history', 'docs', 'autocomplete'));
    }
}

if (!function_exists('Psy\bin')) {
    /**
     * `psysh` command line executable.
     *
     * @return Closure
     */
    function bin()
    {
        return function () {
            $usageException = null;

            $input = new ArgvInput();
            try {
                $input->bind(new InputDefinition(array(
                    new InputOption('help',     'h',  InputOption::VALUE_NONE),
                    new InputOption('config',   'c',  InputOption::VALUE_REQUIRED),
                    new InputOption('version',  'v',  InputOption::VALUE_NONE),
                    new InputOption('cwd',      null, InputOption::VALUE_REQUIRED),
                    new InputOption('color',    null, InputOption::VALUE_NONE),
                    new InputOption('no-color', null, InputOption::VALUE_NONE),

                    new InputArgument('include', InputArgument::IS_ARRAY),
                )));
            } catch (\RuntimeException $e) {
                $usageException = $e;
            }

            $config = array();

            // Handle --config
            if ($configFile = $input->getOption('config')) {
                $config['configFile'] = $configFile;
            }

            // Handle --color and --no-color
            if ($input->getOption('color') && $input->getOption('no-color')) {
                $usageException = new \RuntimeException('Using both "--color" and "--no-color" options is invalid.');
            } elseif ($input->getOption('color')) {
                $config['colorMode'] = Configuration::COLOR_MODE_FORCED;
            } elseif ($input->getOption('no-color')) {
                $config['colorMode'] = Configuration::COLOR_MODE_DISABLED;
            }

            $shell = new Shell(new Configuration($config));

            // Handle --help
            if ($usageException !== null || $input->getOption('help')) {
                if ($usageException !== null) {
                    echo $usageException->getMessage() . PHP_EOL . PHP_EOL;
                }

                $version = $shell->getVersion();
                $name    = basename(reset($_SERVER['argv']));
                echo <<<EOL
$version

Usage:
  $name [--version] [--help] [files...]

Options:
  --help     -h Display this help message.
  --config   -c Use an alternate PsySH config file location.
  --cwd         Use an alternate working directory.
  --version  -v Display the PsySH version.
  --color       Force colors in output.
  --no-color    Disable colors in output.

EOL;
                exit($usageException === null ? 0 : 1);
            }

            // Handle --version
            if ($input->getOption('version')) {
                echo $shell->getVersion() . PHP_EOL;
                exit(0);
            }

            // Pass additional arguments to Shell as 'includes'
            $shell->setIncludes($input->getArgument('include'));

            try {
                // And go!
                $shell->run();
            } catch (Exception $e) {
                echo $e->getMessage() . PHP_EOL;

                // @todo this triggers the "exited unexpectedly" logic in the
                // ForkingLoop, so we can't exit(1) after starting the shell...
                // fix this :)

                // exit(1);
            }
        };
    }
}
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\Assert;

use BadMethodCallException;
use InvalidArgumentException;
use Traversable;
use Exception;
use Throwable;
use Closure;

/**
 * Efficient assertions to validate the input/output of your methods.
 *
 * @method static void nullOrString($value, $message = '')
 * @method static void nullOrStringNotEmpty($value, $message = '')
 * @method static void nullOrInteger($value, $message = '')
 * @method static void nullOrIntegerish($value, $message = '')
 * @method static void nullOrFloat($value, $message = '')
 * @method static void nullOrNumeric($value, $message = '')
 * @method static void nullOrBoolean($value, $message = '')
 * @method static void nullOrScalar($value, $message = '')
 * @method static void nullOrObject($value, $message = '')
 * @method static void nullOrResource($value, $type = null, $message = '')
 * @method static void nullOrIsCallable($value, $message = '')
 * @method static void nullOrIsArray($value, $message = '')
 * @method static void nullOrIsTraversable($value, $message = '')
 * @method static void nullOrIsInstanceOf($value, $class, $message = '')
 * @method static void nullOrNotInstanceOf($value, $class, $message = '')
 * @method static void nullOrIsEmpty($value, $message = '')
 * @method static void nullOrNotEmpty($value, $message = '')
 * @method static void nullOrTrue($value, $message = '')
 * @method static void nullOrFalse($value, $message = '')
 * @method static void nullOrEq($value, $value2, $message = '')
 * @method static void nullOrNotEq($value,$value2,  $message = '')
 * @method static void nullOrSame($value, $value2, $message = '')
 * @method static void nullOrNotSame($value, $value2, $message = '')
 * @method static void nullOrGreaterThan($value, $value2, $message = '')
 * @method static void nullOrGreaterThanEq($value, $value2, $message = '')
 * @method static void nullOrLessThan($value, $value2, $message = '')
 * @method static void nullOrLessThanEq($value, $value2, $message = '')
 * @method static void nullOrRange($value, $min, $max, $message = '')
 * @method static void nullOrOneOf($value, $values, $message = '')
 * @method static void nullOrContains($value, $subString, $message = '')
 * @method static void nullOrStartsWith($value, $prefix, $message = '')
 * @method static void nullOrStartsWithLetter($value, $message = '')
 * @method static void nullOrEndsWith($value, $suffix, $message = '')
 * @method static void nullOrRegex($value, $pattern, $message = '')
 * @method static void nullOrAlpha($value, $message = '')
 * @method static void nullOrDigits($value, $message = '')
 * @method static void nullOrAlnum($value, $message = '')
 * @method static void nullOrLower($value, $message = '')
 * @method static void nullOrUpper($value, $message = '')
 * @method static void nullOrLength($value, $length, $message = '')
 * @method static void nullOrMinLength($value, $min, $message = '')
 * @method static void nullOrMaxLength($value, $max, $message = '')
 * @method static void nullOrLengthBetween($value, $min, $max, $message = '')
 * @method static void nullOrFileExists($value, $message = '')
 * @method static void nullOrFile($value, $message = '')
 * @method static void nullOrDirectory($value, $message = '')
 * @method static void nullOrReadable($value, $message = '')
 * @method static void nullOrWritable($value, $message = '')
 * @method static void nullOrClassExists($value, $message = '')
 * @method static void nullOrSubclassOf($value, $class, $message = '')
 * @method static void nullOrImplementsInterface($value, $interface, $message = '')
 * @method static void nullOrPropertyExists($value, $property, $message = '')
 * @method static void nullOrPropertyNotExists($value, $property, $message = '')
 * @method static void nullOrMethodExists($value, $method, $message = '')
 * @method static void nullOrMethodNotExists($value, $method, $message = '')
 * @method static void nullOrKeyExists($value, $key, $message = '')
 * @method static void nullOrKeyNotExists($value, $key, $message = '')
 * @method static void nullOrCount($value, $key, $message = '')
 * @method static void nullOrUuid($values, $message = '')
 * @method static void allString($values, $message = '')
 * @method static void allStringNotEmpty($values, $message = '')
 * @method static void allInteger($values, $message = '')
 * @method static void allIntegerish($values, $message = '')
 * @method static void allFloat($values, $message = '')
 * @method static void allNumeric($values, $message = '')
 * @method static void allBoolean($values, $message = '')
 * @method static void allScalar($values, $message = '')
 * @method static void allObject($values, $message = '')
 * @method static void allResource($values, $type = null, $message = '')
 * @method static void allIsCallable($values, $message = '')
 * @method static void allIsArray($values, $message = '')
 * @method static void allIsTraversable($values, $message = '')
 * @method static void allIsInstanceOf($values, $class, $message = '')
 * @method static void allNotInstanceOf($values, $class, $message = '')
 * @method static void allNull($values, $message = '')
 * @method static void allNotNull($values, $message = '')
 * @method static void allIsEmpty($values, $message = '')
 * @method static void allNotEmpty($values, $message = '')
 * @method static void allTrue($values, $message = '')
 * @method static void allFalse($values, $message = '')
 * @method static void allEq($values, $value2, $message = '')
 * @method static void allNotEq($values,$value2,  $message = '')
 * @method static void allSame($values, $value2, $message = '')
 * @method static void allNotSame($values, $value2, $message = '')
 * @method static void allGreaterThan($values, $value2, $message = '')
 * @method static void allGreaterThanEq($values, $value2, $message = '')
 * @method static void allLessThan($values, $value2, $message = '')
 * @method static void allLessThanEq($values, $value2, $message = '')
 * @method static void allRange($values, $min, $max, $message = '')
 * @method static void allOneOf($values, $values, $message = '')
 * @method static void allContains($values, $subString, $message = '')
 * @method static void allStartsWith($values, $prefix, $message = '')
 * @method static void allStartsWithLetter($values, $message = '')
 * @method static void allEndsWith($values, $suffix, $message = '')
 * @method static void allRegex($values, $pattern, $message = '')
 * @method static void allAlpha($values, $message = '')
 * @method static void allDigits($values, $message = '')
 * @method static void allAlnum($values, $message = '')
 * @method static void allLower($values, $message = '')
 * @method static void allUpper($values, $message = '')
 * @method static void allLength($values, $length, $message = '')
 * @method static void allMinLength($values, $min, $message = '')
 * @method static void allMaxLength($values, $max, $message = '')
 * @method static void allLengthBetween($values, $min, $max, $message = '')
 * @method static void allFileExists($values, $message = '')
 * @method static void allFile($values, $message = '')
 * @method static void allDirectory($values, $message = '')
 * @method static void allReadable($values, $message = '')
 * @method static void allWritable($values, $message = '')
 * @method static void allClassExists($values, $message = '')
 * @method static void allSubclassOf($values, $class, $message = '')
 * @method static void allImplementsInterface($values, $interface, $message = '')
 * @method static void allPropertyExists($values, $property, $message = '')
 * @method static void allPropertyNotExists($values, $property, $message = '')
 * @method static void allMethodExists($values, $method, $message = '')
 * @method static void allMethodNotExists($values, $method, $message = '')
 * @method static void allKeyExists($values, $key, $message = '')
 * @method static void allKeyNotExists($values, $key, $message = '')
 * @method static void allCount($values, $key, $message = '')
 * @method static void allUuid($values, $message = '')
 *
 * @since  1.0
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class Assert
{
    public static function string($value, $message = '')
    {
        if (!is_string($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a string. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function stringNotEmpty($value, $message = '')
    {
        static::string($value, $message);
        static::notEmpty($value, $message);
    }

    public static function integer($value, $message = '')
    {
        if (!is_int($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integer. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function integerish($value, $message = '')
    {
        if (!is_numeric($value) || $value != (int) $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integerish value. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function float($value, $message = '')
    {
        if (!is_float($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a float. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function numeric($value, $message = '')
    {
        if (!is_numeric($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a numeric. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function boolean($value, $message = '')
    {
        if (!is_bool($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a boolean. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function scalar($value, $message = '')
    {
        if (!is_scalar($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a scalar. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function object($value, $message = '')
    {
        if (!is_object($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an object. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function resource($value, $type = null, $message = '')
    {
        if (!is_resource($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource. Got: %s',
                static::typeToString($value)
            ));
        }

        if ($type && $type !== get_resource_type($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource of type %2$s. Got: %s',
                static::typeToString($value),
                $type
            ));
        }
    }

    public static function isCallable($value, $message = '')
    {
        if (!is_callable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a callable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isArray($value, $message = '')
    {
        if (!is_array($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isTraversable($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof Traversable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a traversable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isInstanceOf($value, $class, $message = '')
    {
        if (!($value instanceof $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance of %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function notInstanceOf($value, $class, $message = '')
    {
        if ($value instanceof $class) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance other than %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function isEmpty($value, $message = '')
    {
        if (!empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notEmpty($value, $message = '')
    {
        if (empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function null($value, $message = '')
    {
        if (null !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected null. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notNull($value, $message = '')
    {
        if (null === $value) {
            static::reportInvalidArgument(
                $message ?: 'Expected a value other than null.'
            );
        }
    }

    public static function true($value, $message = '')
    {
        if (true !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be true. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function false($value, $message = '')
    {
        if (false !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be false. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function eq($value, $value2, $message = '')
    {
        if ($value2 != $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notEq($value, $value2, $message = '')
    {
        if ($value2 == $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a different value than %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function same($value, $value2, $message = '')
    {
        if ($value2 !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value identical to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notSame($value, $value2, $message = '')
    {
        if ($value2 === $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value not identical to %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function greaterThan($value, $limit, $message = '')
    {
        if ($value <= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function greaterThanEq($value, $limit, $message = '')
    {
        if ($value < $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThan($value, $limit, $message = '')
    {
        if ($value >= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThanEq($value, $limit, $message = '')
    {
        if ($value > $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function range($value, $min, $max, $message = '')
    {
        if ($value < $min || $value > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value between %2$s and %3$s. Got: %s',
                static::valueToString($value),
                static::valueToString($min),
                static::valueToString($max)
            ));
        }
    }

    public static function oneOf($value, array $values, $message = '')
    {
        if (!in_array($value, $values, true)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected one of: %2$s. Got: %s',
                static::valueToString($value),
                implode(', ', array_map(array('static', 'valueToString'), $values))
            ));
        }
    }

    public static function contains($value, $subString, $message = '')
    {
        if (false === strpos($value, $subString)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($subString)
            ));
        }
    }

    public static function startsWith($value, $prefix, $message = '')
    {
        if (0 !== strpos($value, $prefix)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($prefix)
            ));
        }
    }

    public static function startsWithLetter($value, $message = '')
    {
        $valid = isset($value[0]);

        if ($valid) {
            $locale = setlocale(LC_CTYPE, 0);
            setlocale(LC_CTYPE, 'C');
            $valid = ctype_alpha($value[0]);
            setlocale(LC_CTYPE, $locale);
        }

        if (!$valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with a letter. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function endsWith($value, $suffix, $message = '')
    {
        if ($suffix !== substr($value, -static::strlen($suffix))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to end with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($suffix)
            ));
        }
    }

    public static function regex($value, $pattern, $message = '')
    {
        if (!preg_match($pattern, $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The value %s does not match the expected pattern.',
                static::valueToString($value)
            ));
        }
    }

    public static function alpha($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alpha($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain only letters. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function digits($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_digit($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function alnum($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alnum($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain letters and digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function lower($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_lower($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain lowercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function upper($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_upper($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain uppercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function length($value, $length, $message = '')
    {
        if ($length !== static::strlen($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s characters. Got: %s',
                static::valueToString($value),
                $length
            ));
        }
    }

    public static function minLength($value, $min, $message = '')
    {
        if (static::strlen($value) < $min) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
                static::valueToString($value),
                $min
            ));
        }
    }

    public static function maxLength($value, $max, $message = '')
    {
        if (static::strlen($value) > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
                static::valueToString($value),
                $max
            ));
        }
    }

    public static function lengthBetween($value, $min, $max, $message = '')
    {
        $length = static::strlen($value);

        if ($length < $min || $length > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
                static::valueToString($value),
                $min,
                $max
            ));
        }
    }

    public static function fileExists($value, $message = '')
    {
        static::string($value);

        if (!file_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The file %s does not exist.',
                static::valueToString($value)
            ));
        }
    }

    public static function file($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_file($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not a file.',
                static::valueToString($value)
            ));
        }
    }

    public static function directory($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_dir($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is no directory.',
                static::valueToString($value)
            ));
        }
    }

    public static function readable($value, $message = '')
    {
        if (!is_readable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not readable.',
                static::valueToString($value)
            ));
        }
    }

    public static function writable($value, $message = '')
    {
        if (!is_writable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not writable.',
                static::valueToString($value)
            ));
        }
    }

    public static function classExists($value, $message = '')
    {
        if (!class_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an existing class name. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function subclassOf($value, $class, $message = '')
    {
        if (!is_subclass_of($value, $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a sub-class of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($class)
            ));
        }
    }

    public static function implementsInterface($value, $interface, $message = '')
    {
        if (!in_array($interface, class_implements($value))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an implementation of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($interface)
            ));
        }
    }

    public static function propertyExists($classOrObject, $property, $message = '')
    {
        if (!property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function propertyNotExists($classOrObject, $property, $message = '')
    {
        if (property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to not exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function methodExists($classOrObject, $method, $message = '')
    {
        if (!method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function methodNotExists($classOrObject, $method, $message = '')
    {
        if (method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to not exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function keyExists($array, $key, $message = '')
    {
        if (!array_key_exists($key, $array)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function keyNotExists($array, $key, $message = '')
    {
        if (array_key_exists($key, $array)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to not exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function count($array, $number, $message = '')
    {
        static::eq(
            count($array),
            $number,
            $message ?: sprintf('Expected an array to contain %d elements. Got: %d.', $number, count($array))
        );
    }

    public static function uuid($value, $message = '')
    {
        $value = str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);

        // The nil UUID is special form of UUID that is specified to have all
        // 128 bits set to zero.
        if ('00000000-0000-0000-0000-000000000000' === $value) {
            return;
        }

        if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Value %s is not a valid UUID.',
                static::valueToString($value)
            ));
        }
    }

    public static function throws(Closure $expression, $class = 'Exception', $message = '')
    {
        static::string($class);

        $actual = 'none';
        try {
            $expression();
        } catch (Exception $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        } catch (Throwable $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        }

        static::reportInvalidArgument($message ?: sprintf(
            'Expected to throw "%s", got "%s"',
            $class,
            $actual
        ));
    }

    public static function __callStatic($name, $arguments)
    {
        if ('nullOr' === substr($name, 0, 6)) {
            if (null !== $arguments[0]) {
                $method = lcfirst(substr($name, 6));
                call_user_func_array(array('static', $method), $arguments);
            }

            return;
        }

        if ('all' === substr($name, 0, 3)) {
            static::isTraversable($arguments[0]);

            $method = lcfirst(substr($name, 3));
            $args = $arguments;

            foreach ($arguments[0] as $entry) {
                $args[0] = $entry;

                call_user_func_array(array('static', $method), $args);
            }

            return;
        }

        throw new BadMethodCallException('No such method: '.$name);
    }

    protected static function valueToString($value)
    {
        if (null === $value) {
            return 'null';
        }

        if (true === $value) {
            return 'true';
        }

        if (false === $value) {
            return 'false';
        }

        if (is_array($value)) {
            return 'array';
        }

        if (is_object($value)) {
            return get_class($value);
        }

        if (is_resource($value)) {
            return 'resource';
        }

        if (is_string($value)) {
            return '"'.$value.'"';
        }

        return (string) $value;
    }

    protected static function typeToString($value)
    {
        return is_object($value) ? get_class($value) : gettype($value);
    }

    protected static function strlen($value)
    {
        if (!function_exists('mb_detect_encoding')) {
            return strlen($value);
        }

        if (false === $encoding = mb_detect_encoding($value)) {
            return strlen($value);
        }

        return mb_strwidth($value, $encoding);
    }

    protected static function reportInvalidArgument($message)
    {
        throw new InvalidArgumentException($message);
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the webmozart/path-util package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\PathUtil;

use InvalidArgumentException;
use RuntimeException;
use Webmozart\Assert\Assert;

/**
 * Contains utility methods for handling path strings.
 *
 * The methods in this class are able to deal with both UNIX and Windows paths
 * with both forward and backward slashes. All methods return normalized parts
 * containing only forward slashes and no excess "." and ".." segments.
 *
 * @since  1.0
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Thomas Schulz <mail@king2500.net>
 */
final class Path
{
    /**
     * The number of buffer entries that triggers a cleanup operation.
     */
    const CLEANUP_THRESHOLD = 1250;

    /**
     * The buffer size after the cleanup operation.
     */
    const CLEANUP_SIZE = 1000;

    /**
     * Buffers input/output of {@link canonicalize()}.
     *
     * @var array
     */
    private static $buffer = array();

    /**
     * The size of the buffer.
     *
     * @var int
     */
    private static $bufferSize = 0;

    /**
     * Canonicalizes the given path.
     *
     * During normalization, all slashes are replaced by forward slashes ("/").
     * Furthermore, all "." and ".." segments are removed as far as possible.
     * ".." segments at the beginning of relative paths are not removed.
     *
     * ```php
     * echo Path::canonicalize("\webmozart\puli\..\css\style.css");
     * // => /webmozart/css/style.css
     *
     * echo Path::canonicalize("../css/./style.css");
     * // => ../css/style.css
     * ```
     *
     * This method is able to deal with both UNIX and Windows paths.
     *
     * @param string $path A path string.
     *
     * @return string The canonical path.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     * @since 2.1 Added support for `~`.
     */
    public static function canonicalize($path)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($path, 'The path must be a string. Got: %s');

        // This method is called by many other methods in this class. Buffer
        // the canonicalized paths to make up for the severe performance
        // decrease.
        if (isset(self::$buffer[$path])) {
            return self::$buffer[$path];
        }

        // Replace "~" with user's home directory.
        if ('~' === $path[0]) {
            $path = static::getHomeDirectory().substr($path, 1);
        }

        $path = str_replace('\\', '/', $path);

        list($root, $pathWithoutRoot) = self::split($path);

        $parts = explode('/', $pathWithoutRoot);
        $canonicalParts = array();

        // Collapse "." and "..", if possible
        foreach ($parts as $part) {
            if ('.' === $part || '' === $part) {
                continue;
            }

            // Collapse ".." with the previous part, if one exists
            // Don't collapse ".." if the previous part is also ".."
            if ('..' === $part && count($canonicalParts) > 0
                    && '..' !== $canonicalParts[count($canonicalParts) - 1]) {
                array_pop($canonicalParts);

                continue;
            }

            // Only add ".." prefixes for relative paths
            if ('..' !== $part || '' === $root) {
                $canonicalParts[] = $part;
            }
        }

        // Add the root directory again
        self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts);
        ++self::$bufferSize;

        // Clean up regularly to prevent memory leaks
        if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
            self::$buffer = array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true);
            self::$bufferSize = self::CLEANUP_SIZE;
        }

        return $canonicalPath;
    }

    /**
     * Normalizes the given path.
     *
     * During normalization, all slashes are replaced by forward slashes ("/").
     * Contrary to {@link canonicalize()}, this method does not remove invalid
     * or dot path segments. Consequently, it is much more efficient and should
     * be used whenever the given path is known to be a valid, absolute system
     * path.
     *
     * This method is able to deal with both UNIX and Windows paths.
     *
     * @param string $path A path string.
     *
     * @return string The normalized path.
     *
     * @since 2.2 Added method.
     */
    public static function normalize($path)
    {
        Assert::string($path, 'The path must be a string. Got: %s');

        return str_replace('\\', '/', $path);
    }

    /**
     * Returns the directory part of the path.
     *
     * This method is similar to PHP's dirname(), but handles various cases
     * where dirname() returns a weird result:
     *
     *  - dirname() does not accept backslashes on UNIX
     *  - dirname("C:/webmozart") returns "C:", not "C:/"
     *  - dirname("C:/") returns ".", not "C:/"
     *  - dirname("C:") returns ".", not "C:/"
     *  - dirname("webmozart") returns ".", not ""
     *  - dirname() does not canonicalize the result
     *
     * This method fixes these shortcomings and behaves like dirname()
     * otherwise.
     *
     * The result is a canonical path.
     *
     * @param string $path A path string.
     *
     * @return string The canonical directory part. Returns the root directory
     *                if the root directory is passed. Returns an empty string
     *                if a relative path is passed that contains no slashes.
     *                Returns an empty string if an empty string is passed.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function getDirectory($path)
    {
        if ('' === $path) {
            return '';
        }

        $path = static::canonicalize($path);

        // Maintain scheme
        if (false !== ($pos = strpos($path, '://'))) {
            $scheme = substr($path, 0, $pos + 3);
            $path = substr($path, $pos + 3);
        } else {
            $scheme = '';
        }

        if (false !== ($pos = strrpos($path, '/'))) {
            // Directory equals root directory "/"
            if (0 === $pos) {
                return $scheme.'/';
            }

            // Directory equals Windows root "C:/"
            if (2 === $pos && ctype_alpha($path[0]) && ':' === $path[1]) {
                return $scheme.substr($path, 0, 3);
            }

            return $scheme.substr($path, 0, $pos);
        }

        return '';
    }

    /**
     * Returns canonical path of the user's home directory.
     *
     * Supported operating systems:
     *
     *  - UNIX
     *  - Windows8 and upper
     *
     * If your operation system or environment isn't supported, an exception is thrown.
     *
     * The result is a canonical path.
     *
     * @return string The canonical home directory
     *
     * @throws RuntimeException If your operation system or environment isn't supported
     *
     * @since 2.1 Added method.
     */
    public static function getHomeDirectory()
    {
        // For UNIX support
        if (getenv('HOME')) {
            return static::canonicalize(getenv('HOME'));
        }

        // For >= Windows8 support
        if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
            return static::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH'));
        }

        throw new RuntimeException("Your environment or operation system isn't supported");
    }

    /**
     * Returns the root directory of a path.
     *
     * The result is a canonical path.
     *
     * @param string $path A path string.
     *
     * @return string The canonical root directory. Returns an empty string if
     *                the given path is relative or empty.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function getRoot($path)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($path, 'The path must be a string. Got: %s');

        // Maintain scheme
        if (false !== ($pos = strpos($path, '://'))) {
            $scheme = substr($path, 0, $pos + 3);
            $path = substr($path, $pos + 3);
        } else {
            $scheme = '';
        }

        // UNIX root "/" or "\" (Windows style)
        if ('/' === $path[0] || '\\' === $path[0]) {
            return $scheme.'/';
        }

        $length = strlen($path);

        // Windows root
        if ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
            // Special case: "C:"
            if (2 === $length) {
                return $scheme.$path.'/';
            }

            // Normal case: "C:/ or "C:\"
            if ('/' === $path[2] || '\\' === $path[2]) {
                return $scheme.$path[0].$path[1].'/';
            }
        }

        return '';
    }

    /**
     * Returns the file name from a file path.
     *
     * @param string $path The path string.
     *
     * @return string The file name.
     *
     * @since 1.1 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function getFilename($path)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($path, 'The path must be a string. Got: %s');

        return basename($path);
    }

    /**
     * Returns the file name without the extension from a file path.
     *
     * @param string      $path      The path string.
     * @param string|null $extension If specified, only that extension is cut
     *                               off (may contain leading dot).
     *
     * @return string The file name without extension.
     *
     * @since 1.1 Added method.
     * @since 2.0 Method now fails if $path or $extension have invalid types.
     */
    public static function getFilenameWithoutExtension($path, $extension = null)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($path, 'The path must be a string. Got: %s');
        Assert::nullOrString($extension, 'The extension must be a string or null. Got: %s');

        if (null !== $extension) {
            // remove extension and trailing dot
            return rtrim(basename($path, $extension), '.');
        }

        return pathinfo($path, PATHINFO_FILENAME);
    }

    /**
     * Returns the extension from a file path.
     *
     * @param string $path           The path string.
     * @param bool   $forceLowerCase Forces the extension to be lower-case
     *                               (requires mbstring extension for correct
     *                               multi-byte character handling in extension).
     *
     * @return string The extension of the file path (without leading dot).
     *
     * @since 1.1 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function getExtension($path, $forceLowerCase = false)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($path, 'The path must be a string. Got: %s');

        $extension = pathinfo($path, PATHINFO_EXTENSION);

        if ($forceLowerCase) {
            $extension = self::toLower($extension);
        }

        return $extension;
    }

    /**
     * Returns whether the path has an extension.
     *
     * @param string            $path       The path string.
     * @param string|array|null $extensions If null or not provided, checks if
     *                                      an extension exists, otherwise
     *                                      checks for the specified extension
     *                                      or array of extensions (with or
     *                                      without leading dot).
     * @param bool              $ignoreCase Whether to ignore case-sensitivity
     *                                      (requires mbstring extension for
     *                                      correct multi-byte character
     *                                      handling in the extension).
     *
     * @return bool Returns `true` if the path has an (or the specified)
     *              extension and `false` otherwise.
     *
     * @since 1.1 Added method.
     * @since 2.0 Method now fails if $path or $extensions have invalid types.
     */
    public static function hasExtension($path, $extensions = null, $ignoreCase = false)
    {
        if ('' === $path) {
            return false;
        }

        $extensions = is_object($extensions) ? array($extensions) : (array) $extensions;

        Assert::allString($extensions, 'The extensions must be strings. Got: %s');

        $actualExtension = self::getExtension($path, $ignoreCase);

        // Only check if path has any extension
        if (empty($extensions)) {
            return '' !== $actualExtension;
        }

        foreach ($extensions as $key => $extension) {
            if ($ignoreCase) {
                $extension = self::toLower($extension);
            }

            // remove leading '.' in extensions array
            $extensions[$key] = ltrim($extension, '.');
        }

        return in_array($actualExtension, $extensions);
    }

    /**
     * Changes the extension of a path string.
     *
     * @param string $path      The path string with filename.ext to change.
     * @param string $extension New extension (with or without leading dot).
     *
     * @return string The path string with new file extension.
     *
     * @since 1.1 Added method.
     * @since 2.0 Method now fails if $path or $extension is not a string.
     */
    public static function changeExtension($path, $extension)
    {
        if ('' === $path) {
            return '';
        }

        Assert::string($extension, 'The extension must be a string. Got: %s');

        $actualExtension = self::getExtension($path);
        $extension = ltrim($extension, '.');

        // No extension for paths
        if ('/' === substr($path, -1)) {
            return $path;
        }

        // No actual extension in path
        if (empty($actualExtension)) {
            return $path.('.' === substr($path, -1) ? '' : '.').$extension;
        }

        return substr($path, 0, -strlen($actualExtension)).$extension;
    }

    /**
     * Returns whether a path is absolute.
     *
     * @param string $path A path string.
     *
     * @return bool Returns true if the path is absolute, false if it is
     *              relative or empty.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function isAbsolute($path)
    {
        if ('' === $path) {
            return false;
        }

        Assert::string($path, 'The path must be a string. Got: %s');

        // Strip scheme
        if (false !== ($pos = strpos($path, '://'))) {
            $path = substr($path, $pos + 3);
        }

        // UNIX root "/" or "\" (Windows style)
        if ('/' === $path[0] || '\\' === $path[0]) {
            return true;
        }

        // Windows root
        if (strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
            // Special case: "C:"
            if (2 === strlen($path)) {
                return true;
            }

            // Normal case: "C:/ or "C:\"
            if ('/' === $path[2] || '\\' === $path[2]) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns whether a path is relative.
     *
     * @param string $path A path string.
     *
     * @return bool Returns true if the path is relative or empty, false if
     *              it is absolute.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function isRelative($path)
    {
        return !static::isAbsolute($path);
    }

    /**
     * Turns a relative path into an absolute path.
     *
     * Usually, the relative path is appended to the given base path. Dot
     * segments ("." and "..") are removed/collapsed and all slashes turned
     * into forward slashes.
     *
     * ```php
     * echo Path::makeAbsolute("../style.css", "/webmozart/puli/css");
     * // => /webmozart/puli/style.css
     * ```
     *
     * If an absolute path is passed, that path is returned unless its root
     * directory is different than the one of the base path. In that case, an
     * exception is thrown.
     *
     * ```php
     * Path::makeAbsolute("/style.css", "/webmozart/puli/css");
     * // => /style.css
     *
     * Path::makeAbsolute("C:/style.css", "C:/webmozart/puli/css");
     * // => C:/style.css
     *
     * Path::makeAbsolute("C:/style.css", "/webmozart/puli/css");
     * // InvalidArgumentException
     * ```
     *
     * If the base path is not an absolute path, an exception is thrown.
     *
     * The result is a canonical path.
     *
     * @param string $path     A path to make absolute.
     * @param string $basePath An absolute base path.
     *
     * @return string An absolute path in canonical form.
     *
     * @throws InvalidArgumentException If the base path is not absolute or if
     *                                  the given path is an absolute path with
     *                                  a different root than the base path.
     *
     * @since 1.0   Added method.
     * @since 2.0   Method now fails if $path or $basePath is not a string.
     * @since 2.2.2 Method does not fail anymore of $path and $basePath are
     *              absolute, but on different partitions.
     */
    public static function makeAbsolute($path, $basePath)
    {
        Assert::stringNotEmpty($basePath, 'The base path must be a non-empty string. Got: %s');

        if (!static::isAbsolute($basePath)) {
            throw new InvalidArgumentException(sprintf(
                'The base path "%s" is not an absolute path.',
                $basePath
            ));
        }

        if (static::isAbsolute($path)) {
            return static::canonicalize($path);
        }

        if (false !== ($pos = strpos($basePath, '://'))) {
            $scheme = substr($basePath, 0, $pos + 3);
            $basePath = substr($basePath, $pos + 3);
        } else {
            $scheme = '';
        }

        return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path);
    }

    /**
     * Turns a path into a relative path.
     *
     * The relative path is created relative to the given base path:
     *
     * ```php
     * echo Path::makeRelative("/webmozart/style.css", "/webmozart/puli");
     * // => ../style.css
     * ```
     *
     * If a relative path is passed and the base path is absolute, the relative
     * path is returned unchanged:
     *
     * ```php
     * Path::makeRelative("style.css", "/webmozart/puli/css");
     * // => style.css
     * ```
     *
     * If both paths are relative, the relative path is created with the
     * assumption that both paths are relative to the same directory:
     *
     * ```php
     * Path::makeRelative("style.css", "webmozart/puli/css");
     * // => ../../../style.css
     * ```
     *
     * If both paths are absolute, their root directory must be the same,
     * otherwise an exception is thrown:
     *
     * ```php
     * Path::makeRelative("C:/webmozart/style.css", "/webmozart/puli");
     * // InvalidArgumentException
     * ```
     *
     * If the passed path is absolute, but the base path is not, an exception
     * is thrown as well:
     *
     * ```php
     * Path::makeRelative("/webmozart/style.css", "webmozart/puli");
     * // InvalidArgumentException
     * ```
     *
     * If the base path is not an absolute path, an exception is thrown.
     *
     * The result is a canonical path.
     *
     * @param string $path     A path to make relative.
     * @param string $basePath A base path.
     *
     * @return string A relative path in canonical form.
     *
     * @throws InvalidArgumentException If the base path is not absolute or if
     *                                  the given path has a different root
     *                                  than the base path.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path or $basePath is not a string.
     */
    public static function makeRelative($path, $basePath)
    {
        Assert::string($basePath, 'The base path must be a string. Got: %s');

        $path = static::canonicalize($path);
        $basePath = static::canonicalize($basePath);

        list($root, $relativePath) = self::split($path);
        list($baseRoot, $relativeBasePath) = self::split($basePath);

        // If the base path is given as absolute path and the path is already
        // relative, consider it to be relative to the given absolute path
        // already
        if ('' === $root && '' !== $baseRoot) {
            // If base path is already in its root
            if ('' === $relativeBasePath) {
                $relativePath = ltrim($relativePath, './\\');
            }

            return $relativePath;
        }

        // If the passed path is absolute, but the base path is not, we
        // cannot generate a relative path
        if ('' !== $root && '' === $baseRoot) {
            throw new InvalidArgumentException(sprintf(
                'The absolute path "%s" cannot be made relative to the '.
                'relative path "%s". You should provide an absolute base '.
                'path instead.',
                $path,
                $basePath
            ));
        }

        // Fail if the roots of the two paths are different
        if ($baseRoot && $root !== $baseRoot) {
            throw new InvalidArgumentException(sprintf(
                'The path "%s" cannot be made relative to "%s", because they '.
                'have different roots ("%s" and "%s").',
                $path,
                $basePath,
                $root,
                $baseRoot
            ));
        }

        if ('' === $relativeBasePath) {
            return $relativePath;
        }

        // Build a "../../" prefix with as many "../" parts as necessary
        $parts = explode('/', $relativePath);
        $baseParts = explode('/', $relativeBasePath);
        $dotDotPrefix = '';

        // Once we found a non-matching part in the prefix, we need to add
        // "../" parts for all remaining parts
        $match = true;

        foreach ($baseParts as $i => $basePart) {
            if ($match && isset($parts[$i]) && $basePart === $parts[$i]) {
                unset($parts[$i]);

                continue;
            }

            $match = false;
            $dotDotPrefix .= '../';
        }

        return rtrim($dotDotPrefix.implode('/', $parts), '/');
    }

    /**
     * Returns whether the given path is on the local filesystem.
     *
     * @param string $path A path string.
     *
     * @return bool Returns true if the path is local, false for a URL.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $path is not a string.
     */
    public static function isLocal($path)
    {
        Assert::string($path, 'The path must be a string. Got: %s');

        return '' !== $path && false === strpos($path, '://');
    }

    /**
     * Returns the longest common base path of a set of paths.
     *
     * Dot segments ("." and "..") are removed/collapsed and all slashes turned
     * into forward slashes.
     *
     * ```php
     * $basePath = Path::getLongestCommonBasePath(array(
     *     '/webmozart/css/style.css',
     *     '/webmozart/css/..'
     * ));
     * // => /webmozart
     * ```
     *
     * The root is returned if no common base path can be found:
     *
     * ```php
     * $basePath = Path::getLongestCommonBasePath(array(
     *     '/webmozart/css/style.css',
     *     '/puli/css/..'
     * ));
     * // => /
     * ```
     *
     * If the paths are located on different Windows partitions, `null` is
     * returned.
     *
     * ```php
     * $basePath = Path::getLongestCommonBasePath(array(
     *     'C:/webmozart/css/style.css',
     *     'D:/webmozart/css/..'
     * ));
     * // => null
     * ```
     *
     * @param array $paths A list of paths.
     *
     * @return string|null The longest common base path in canonical form or
     *                     `null` if the paths are on different Windows
     *                     partitions.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $paths are not strings.
     */
    public static function getLongestCommonBasePath(array $paths)
    {
        Assert::allString($paths, 'The paths must be strings. Got: %s');

        list($bpRoot, $basePath) = self::split(self::canonicalize(reset($paths)));

        for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
            list($root, $path) = self::split(self::canonicalize(current($paths)));

            // If we deal with different roots (e.g. C:/ vs. D:/), it's time
            // to quit
            if ($root !== $bpRoot) {
                return null;
            }

            // Make the base path shorter until it fits into path
            while (true) {
                if ('.' === $basePath) {
                    // No more base paths
                    $basePath = '';

                    // Next path
                    continue 2;
                }

                // Prevent false positives for common prefixes
                // see isBasePath()
                if (0 === strpos($path.'/', $basePath.'/')) {
                    // Next path
                    continue 2;
                }

                $basePath = dirname($basePath);
            }
        }

        return $bpRoot.$basePath;
    }

    /**
     * Joins two or more path strings.
     *
     * The result is a canonical path.
     *
     * @param string[]|string $paths Path parts as parameters or array.
     *
     * @return string The joint path.
     *
     * @since 2.0 Added method.
     */
    public static function join($paths)
    {
        if (!is_array($paths)) {
            $paths = func_get_args();
        }

        Assert::allString($paths, 'The paths must be strings. Got: %s');

        $finalPath = null;
        $wasScheme = false;

        foreach ($paths as $path) {
            $path = (string) $path;

            if ('' === $path) {
                continue;
            }

            if (null === $finalPath) {
                // For first part we keep slashes, like '/top', 'C:\' or 'phar://'
                $finalPath = $path;
                $wasScheme = (strpos($path, '://') !== false);
                continue;
            }

            // Only add slash if previous part didn't end with '/' or '\'
            if (!in_array(substr($finalPath, -1), array('/', '\\'))) {
                $finalPath .= '/';
            }

            // If first part included a scheme like 'phar://' we allow current part to start with '/', otherwise trim
            $finalPath .= $wasScheme ? $path : ltrim($path, '/');
            $wasScheme = false;
        }

        if (null === $finalPath) {
            return '';
        }

        return self::canonicalize($finalPath);
    }

    /**
     * Returns whether a path is a base path of another path.
     *
     * Dot segments ("." and "..") are removed/collapsed and all slashes turned
     * into forward slashes.
     *
     * ```php
     * Path::isBasePath('/webmozart', '/webmozart/css');
     * // => true
     *
     * Path::isBasePath('/webmozart', '/webmozart');
     * // => true
     *
     * Path::isBasePath('/webmozart', '/webmozart/..');
     * // => false
     *
     * Path::isBasePath('/webmozart', '/puli');
     * // => false
     * ```
     *
     * @param string $basePath The base path to test.
     * @param string $ofPath   The other path.
     *
     * @return bool Whether the base path is a base path of the other path.
     *
     * @since 1.0 Added method.
     * @since 2.0 Method now fails if $basePath or $ofPath is not a string.
     */
    public static function isBasePath($basePath, $ofPath)
    {
        Assert::string($basePath, 'The base path must be a string. Got: %s');

        $basePath = self::canonicalize($basePath);
        $ofPath = self::canonicalize($ofPath);

        // Append slashes to prevent false positives when two paths have
        // a common prefix, for example /base/foo and /base/foobar.
        // Don't append a slash for the root "/", because then that root
        // won't be discovered as common prefix ("//" is not a prefix of
        // "/foobar/").
        return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/');
    }

    /**
     * Splits a part into its root directory and the remainder.
     *
     * If the path has no root directory, an empty root directory will be
     * returned.
     *
     * If the root directory is a Windows style partition, the resulting root
     * will always contain a trailing slash.
     *
     * list ($root, $path) = Path::split("C:/webmozart")
     * // => array("C:/", "webmozart")
     *
     * list ($root, $path) = Path::split("C:")
     * // => array("C:/", "")
     *
     * @param string $path The canonical path to split.
     *
     * @return string[] An array with the root directory and the remaining
     *                  relative path.
     */
    private static function split($path)
    {
        if ('' === $path) {
            return array('', '');
        }

        // Remember scheme as part of the root, if any
        if (false !== ($pos = strpos($path, '://'))) {
            $root = substr($path, 0, $pos + 3);
            $path = substr($path, $pos + 3);
        } else {
            $root = '';
        }

        $length = strlen($path);

        // Remove and remember root directory
        if ('/' === $path[0]) {
            $root .= '/';
            $path = $length > 1 ? substr($path, 1) : '';
        } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
            if (2 === $length) {
                // Windows special case: "C:"
                $root .= $path.'/';
                $path = '';
            } elseif ('/' === $path[2]) {
                // Windows normal case: "C:/"..
                $root .= substr($path, 0, 3);
                $path = $length > 3 ? substr($path, 3) : '';
            }
        }

        return array($root, $path);
    }

    /**
     * Converts string to lower-case (multi-byte safe if mbstring is installed).
     *
     * @param string $str The string
     *
     * @return string Lower case string
     */
    private static function toLower($str)
    {
        if (function_exists('mb_strtolower')) {
            return mb_strtolower($str, mb_detect_encoding($str));
        }

        return strtolower($str);
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the webmozart/path-util package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\PathUtil;

use InvalidArgumentException;
use Webmozart\Assert\Assert;

/**
 * Contains utility methods for handling URL strings.
 *
 * The methods in this class are able to deal with URLs.
 *
 * @since  2.3
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Claudio Zizza <claudio@budgegeria.de>
 */
final class Url
{
    /**
     * Turns a URL into a relative path.
     *
     * The result is a canonical path. This class is using functionality of Path class.
     *
     * @see Path
     *
     * @param string $url     A URL to make relative.
     * @param string $baseUrl A base URL.
     *
     * @return string
     *
     * @throws InvalidArgumentException If the URL and base URL does
     *                                  not match.
     */
    public static function makeRelative($url, $baseUrl)
    {
        Assert::string($url, 'The URL must be a string. Got: %s');
        Assert::string($baseUrl, 'The base URL must be a string. Got: %s');
        Assert::contains($baseUrl, '://', '%s is not an absolute Url.');

        list($baseHost, $basePath) = self::split($baseUrl);

        if (false === strpos($url, '://')) {
            if (0 === strpos($url, '/')) {
                $host = $baseHost;
            } else {
                $host = '';
            }
            $path = $url;
        } else {
            list($host, $path) = self::split($url);
        }

        if ('' !== $host && $host !== $baseHost) {
            throw new InvalidArgumentException(sprintf(
                'The URL "%s" cannot be made relative to "%s" since their host names are different.',
                $host,
                $baseHost
            ));
        }

        return Path::makeRelative($path, $basePath);
    }

    /**
     * Splits a URL into its host and the path.
     *
     * ```php
     * list ($root, $path) = Path::split("http://example.com/webmozart")
     * // => array("http://example.com", "/webmozart")
     *
     * list ($root, $path) = Path::split("http://example.com")
     * // => array("http://example.com", "")
     * ```
     *
     * @param string $url The URL to split.
     *
     * @return string[] An array with the host and the path of the URL.
     *
     * @throws InvalidArgumentException If $url is not a URL.
     */
    private static function split($url)
    {
        $pos = strpos($url, '://');
        $scheme = substr($url, 0, $pos + 3);
        $url = substr($url, $pos + 3);

        if (false !== ($pos = strpos($url, '/'))) {
            $host = substr($url, 0, $pos);
            $url = substr($url, $pos);
        } else {
            // No path, only host
            $host = $url;
            $url = '/';
        }

        // At this point, we have $scheme, $host and $path
        $root = $scheme.$host;

        return array($root, $url);
    }
}
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInita10aa57c4ae20a31073bf4a2d3443e18::getLoader();
<?php

/**
 * @file
 *   An early implementation of Site Archive dump/restore. See
 *   http://groups.drupal.org/site-archive-format.
 */

use Drush\Log\LogLevel;

function archive_drush_command() {
  $items['archive-dump'] = array(
    'description' => 'Backup your code, files, and database into a single file.',
    'arguments' => array(
      'sites' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
    ),
    // Most options on sql-dump should not be shown, so just offer a subset.
    'options' => drush_sql_get_table_selection_options() + array(
      'description' => 'Describe the archive contents.',
      'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.',
      'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.',
      'overwrite' => 'Do not fail if the destination file exists; overwrite it instead. Default is --no-overwrite.',
      'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
      'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
      'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
      'preserve-symlinks' => 'Preserve symbolic links.',
      'no-core' => 'Exclude Drupal core, so the backup only contains the site specific stuff.',
      'tar-options' => 'Options passed thru to the tar command.',
    ),
    'examples' => array(
      'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
      'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
      'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
      'drush archive-dump --tar-options="--exclude=.git --exclude=sites/default/files"' => 'Omits any .git directories found in the tree as well as sites/default/files.',
      'drush archive-dump --tar-options="--exclude=%files"' => 'Placeholder %files is replaced with the real path for the current site, and that path is excluded.',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
    'aliases' => array('ard', 'archive-backup', 'arb'),
  );
  $items['archive-restore'] = array(
    'description' => 'Expand a site archive into a Drupal web site.',
    'arguments' => array(
      'file' => 'The site archive file that should be expanded.',
      'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
    ),
    'required-arguments' => 1,
    'options' => array(
      'destination' => 'Specify where the Drupal site should be expanded, including the docroot. Defaults to the current working directory.',
      'db-prefix' => 'An optional table prefix to use during restore.',
      'db-url' => array(
        'description' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
        'example-value' => 'mysql://root:pass@host/db',
      ),
      'db-su' => 'Account to use when creating the new database. Optional.',
      'db-su-pw' => 'Password for the "db-su" account. Optional.',
      'overwrite' => 'Allow drush to overwrite any files in the destination. Default is --no-overwrite.',
      'tar-options' => 'Options passed thru to the tar command.',
    ),
    'examples' => array(
      'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.',
      'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.',
      'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.',
      'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'aliases' => array('arr'),
  );
  return $items;
}

/**
 * Command callback. Generate site archive file.
 */
function drush_archive_dump($sites_subdirs = '@self') {
  $include_platform = !drush_get_option('no-core', FALSE);
  $tar = drush_get_tar_executable();

  $sites = array();
  list($aliases, $not_found) = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
  if (!empty($not_found)) {
    drush_log(dt("Some site subdirectories are not valid Drupal sites: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING);
  }
  foreach ($aliases as $key => $alias) {
    $sites[$key] = $alias;

    if (($db_record = sitealias_get_databases_from_record($alias))) {
      $sites[$key]['databases'] = $db_record;
    }
    else {
      $sites[$key]['databases'] = array();
      drush_log(dt('DB definition not found for !alias', array('!alias' => $key)), LogLevel::NOTICE);
    }
  }

  // The user can specify a destination filepath or not. That filepath might
  // end with .gz, .tgz, or something else. At the end of this command we will
  // gzip a file, and we want it to end up with the user-specified name (if
  // any), but gzip renames files and refuses to compress files ending with
  // .gz and .tgz, making our lives difficult. Solution:
  //
  // 1. Create a unique temporary base name to which gzip WILL append .gz.
  // 2. If no destination is provided, set $dest_dir to a backup directory and
  // $final_destination to be the unique name in that dir.
  // 3. If a destination is provided, set $dest_dir to that directory and
  // $final_destination to the exact name given.
  // 4. Set $destination, the actual working file we will build up, to the
  // unqiue name in $dest_dir.
  // 5. After gzip'ing $destination, rename $destination.gz to
  // $final_destination.
  //
  // Sheesh.

  // Create the unique temporary name.
  $prefix = 'none';
  if (!empty($sites)) {
    $first = current($sites);
    if ( !empty($first['databases']['default']['default']['database']) ) {
      $prefix = count($sites) > 1 ? 'multiple_sites' : str_replace('/', '-', $first['databases']['default']['default']['database']);
    }
  }
  $date = gmdate('Ymd_His');
  $temp_dest_name = "$prefix.$date.tar";

  $final_destination = drush_get_option('destination');
  if (!$final_destination) {
    // No destination provided.
    $backup = drush_include_engine('version_control', 'backup');
    // TODO: this standard Drush pattern leads to a slightly obtuse directory structure.
    $dest_dir = $backup->prepare_backup_dir('archive-dump');
    if (empty($dest_dir)) {
      $dest_dir = drush_tempdir();
    }
    $final_destination = "$dest_dir/$temp_dest_name.gz";
  }
  else {
    // Use the supplied --destination. If it is relative, resolve it
    // relative to the directory in which drush was invoked.
    $command_cwd = getcwd();
    drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
    // This doesn't perform realpath on the basename, but that's okay. This is
    // not path-based security. We just use it for checking for perms later.
    drush_mkdir(dirname($final_destination));
    $dest_dir = realpath(dirname($final_destination));
    $final_destination = $dest_dir . '/' . basename($final_destination);
    drush_op('chdir', $command_cwd);
  }

  // $dest_dir is either the backup directory or specified directory. Set our
  // working file.
  $destination = "$dest_dir/$temp_dest_name";

  // Validate the FINAL destination. It should be a file that does not exist
  // (unless --overwrite) in a writable directory (and a writable file if
  // it exists). We check all this up front to avoid failing after a long
  // dump process.
  $overwrite = drush_get_option('overwrite');
  $dest_dir = dirname($final_destination);
  $dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
  if (is_dir($final_destination)) {
    drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('Destination !file must be a file, not a directory.', $dt_args));
    return;
  }
  else if (file_exists($final_destination)) {
    if (!$overwrite) {
      drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('Destination !file exists; specify --overwrite to overwrite.', $dt_args));
      return;
    }
    else if (!is_writable($final_destination)) {
      drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('Destination !file is not writable.', $dt_args));
      return;
    }
  }
  else if (!is_writable($dest_dir)) {
    drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('Destination directory !dir is not writable.', $dt_args));
    return;
  }

  // Get the extra options for tar, if any
  $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));

  // Start adding codebase to the archive.
  $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
  $docroot = basename($docroot_path);
  $workdir = dirname($docroot_path);

  if ($include_platform) {
    $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference ';
    // Convert destination path to Unix style for tar on MinGW - see http://drupal.org/node/1844224
    if (drush_is_mingw()) {
      $destination_orig = $destination;
      $destination = str_replace('\\', '/', $destination);
      $destination = preg_replace('$^([a-zA-Z]):$', '/$1', $destination);
    }
    // Archive Drupal core, excluding sites dir.
    drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot);
    // Add sites/all to the same archive.
    drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all");
    // Add special files in sites/ to the archive. Last 2 items are new in Drupal8.
    $files_to_add = array('sites/README.txt', 'sites/sites.php', 'sites/example.sites.php', 'sites/development.services.yml', 'sites/example.settings.local.php');
    foreach ($files_to_add as $file_to_add) {
      if (file_exists($file_to_add)) {
        drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, $docroot . '/' . $file_to_add);
      }
    }
  }

  $tmp = drush_tempdir();
  $all_dbs = array();
  // Dump the default database for each site and add to the archive.
  foreach ($sites as $key => $alias) {
    if (isset($alias['databases']['default']['default'])) {
      $db_spec = $alias['databases']['default']['default'];
      // Use a subdirectory name matching the docroot name.
      drush_mkdir("{$tmp}/{$docroot}");

      // Ensure uniqueness by prefixing key if needed. Remove path delimiters.
      $dbname = str_replace(DIRECTORY_SEPARATOR, '-', $db_spec['database']);
      $result_file = count($sites) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql");

      $all_dbs[$key] = array(
        'file' => basename($result_file),
        'driver' => $db_spec['driver'],
      );
      $sql = drush_sql_get_class($db_spec);
      $sql->dump($result_file);
      drush_shell_cd_and_exec($tmp, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, basename($result_file));
    }
  }

  // Build a manifest file AND add sites/$subdir to archive as we go.
  $platform = array(
    'datestamp' => time(),
    'formatversion' => '1.0',
    'generator' => drush_get_option('generator', 'Drush archive-dump'),
    'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
    'description' => drush_get_option('description', ''),
    'tags' => drush_get_option('tags', ''),
    'archiveformat' => ($include_platform ? 'platform' : 'site'),
  );
  $contents = drush_export_ini(array('Global' => $platform));

  $i=0;
  foreach ($sites as $key => $alias) {
    $status = drush_invoke_process($alias, 'core-status', array(), array(), array('integrate' => FALSE));
    if (!$status || $status['error_log']) {
      drush_log(dt('Unable to determine sites directory for !alias', array('!alias' => $key)), LogLevel::WARNING);
    }

    // Add the site specific directory to archive.
    if (!empty($status['object']['%paths']['%site'])) {
      drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination,  "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
    }

    $site = array(
      'docroot' => DRUPAL_ROOT,
      'sitedir' => @$status['object']['%paths']['%site'],
      'files-public' => @$status['object']['%paths']['%files'],
      'files-private' => @$status['object']['%paths']['%private'],
    );
    $site["database-default-file"] = $all_dbs[$key]['file'];
    $site["database-default-driver"] = $all_dbs[$key]['driver'];
    // The section title is the sites subdirectory name.
    $info[basename($site['sitedir'])] = $site;
    $contents .= "\n" . drush_export_ini($info);
    unset($info);
    $i++;
  }
  file_put_contents("{$tmp}/MANIFEST.ini", $contents);

  // Add manifest to archive.
  drush_shell_cd_and_exec($tmp, "$tar --dereference -rf %s %s", $destination, 'MANIFEST.ini');

  // Ensure that default/default.settings.php is in the archive. This is needed
  // by site-install when restoring a site without any DB.
  // NOTE: Windows tar file replace operation is broken so we have to check if file already exists.
  // Otherwise it will corrupt the archive.
  $res = drush_shell_cd_and_exec($workdir, "$tar -tf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
  $output = drush_shell_exec_output();
  if (!$res || !isset($output[0]) || empty($output[0])) {
    drush_shell_cd_and_exec($workdir, "$tar --dereference -vrf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
  }

  // Switch back to original destination in case it was modified for tar on MinGW.
  if (!empty($destination_orig)) {
    $destination = $destination_orig;
  }

  // Compress the archive
  if (!drush_shell_exec("gzip --no-name -f %s", $destination)) {
    // Clean up the tar file
    drush_register_file_for_deletion($destination);
    return drush_set_error(DRUSH_APPLICATION_ERROR, dt('Failed to gzip !destination', ['!destination' => $destination]));
  }

  // gzip appends .gz unless the name already ends in .gz, .tgz, or .taz.
  if ("{$destination}.gz" != $final_destination) {
    drush_move_dir("{$destination}.gz", $final_destination, $overwrite);
  }

  drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), LogLevel::OK);
  return $final_destination;
}

/**
 * Command argument complete callback.
 *
 * @return
 *   List of site names/aliases for archival.
 */
function archive_archive_dump_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/**
 * Command callback. Restore web site(s) from a site archive file.
 */
function drush_archive_restore($file, $site_id = NULL) {
  $tmp = drush_tempdir();

  // Get the extra options for tar, if any
  $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));

  if (!$files = drush_tarball_extract($file, $tmp, FALSE, $tar_extra_options)) {
    return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
  }

  $manifest = $tmp . '/MANIFEST.ini';
  if (file_exists($manifest)) {
    if (!$ini = parse_ini_file($manifest, TRUE)) {
      return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
    }
  }
  else {
    $ini = drush_archive_guess_manifest($tmp);
  }

  // Backward compatibility: 'archiveformat' did not exist
  // in older versions of archive-dump.
  if (!isset( $ini['Global']['archiveformat'])) {
    $ini['Global']['archiveformat'] = 'platform';
  }

  // Grab the first site in the Manifest and move docroot to destination.
  $ini_tmp = $ini;
  unset($ini_tmp['Global']);
  $first = array_shift($ini_tmp);
  $docroot = basename($first['docroot']);
  $destination = drush_get_option('destination', realpath('.') . "/$docroot");

  if ($ini['Global']['archiveformat'] == 'platform') {
    // Move the whole platform in-place at once.
    if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
      return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination)));
    }
  }
  else {
    // When no platform is included we do this on a per-site basis.
  }

  // Loop over sites and restore databases and append to settings.php.
  foreach ($ini as $section => $site) {
    if ($section != 'Global' && (!isset($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
      $site_destination = $destination . '/' . $site['sitedir'];
      // Restore site, in case not already done above.
      if ($ini['Global']['archiveformat'] == 'site') {
        if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) {
          return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination)));
        }
      }

      // Restore database.
      $sql_file = $tmp . '/' . $site['database-default-file'];
      if ($db_url = drush_get_option('db-url')) {
        if (empty($site_id) && count($ini) >= 3) {
          // TODO: Use drushrc to provide multiple db-urls for multi-restore?
          return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
        }
        $db_spec = drush_convert_db_from_db_url($db_url);
      }
      else {
        $site_specification = $destination . '#' . $section;
        if ($return = drush_invoke_process($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
          $databases = $return['object'];
          $db_spec = $databases['default']['default'];
        }
        else {
          return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
        }
      }
      $sql = drush_sql_get_class($db_spec);
      $sql->drop_or_create();
      $sql->query(NULL, $sql_file);

      // Append new DB info to settings.php.
      if ($db_url) {
        $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
        //If settings.php doesn't exist in the archive, create it from default.settings.php.
        if (!file_exists($settingsfile)) {
          drush_op('copy', $destination . '/sites/default/default.settings.php', $settingsfile);
        }
        // Need to do something here or else we can't write.
        chmod($settingsfile, 0664);
        file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
        if (drush_drupal_major_version($destination) >= 7) {
          file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND);
        }
        else {
          file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
        }
        drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), LogLevel::OK);
      }
    }
  }
  drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), LogLevel::OK);

  return $destination;
}


/**
 * Command argument complete callback.
 *
 * @return
 *   Strong glob of files to complete on.
 */
function archive_archive_restore_complete() {
  return array(
    'files' => array(
      'directories' => array(
        'pattern' => '*',
        'flags' => GLOB_ONLYDIR,
      ),
      'tar' => array(
        'pattern' => '*.tar.gz',
      ),
    ),
  );
}

/**
 * Try to find docroot and DB dump file in an extracted archive.
 *
 * @param string $path The location of the extracted archive.
 * @return array The manifest data.
 */
function drush_archive_guess_manifest($path) {
  $db_file = drush_scan_directory($path, '/\.sql$/',  array('.', '..', 'CVS'), 0, 0);

  if (file_exists($path . '/index.php')) {
    $docroot = './';
  }
  else {
    $directories = glob($path . '/*' , GLOB_ONLYDIR);
    $docroot = reset($directories);
  }

  $ini = array(
    'Global' => array(
        // Very crude detection of a platform...
        'archiveformat' => (drush_drupal_version($docroot) ? 'platform' : 'site'),
    ),
    'default' => array(
      'docroot' => $docroot,
      'sitedir' => 'sites/default',
      'database-default-file' => key($db_file),
    ),
  );

  return $ini;
}
<?php

use Drush\Log\LogLevel;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request;

/**
 * Implementation of hook_drush_help().
 */
function cache_drush_help($section) {
  switch ($section) {
    case 'meta:cache:title':
      return dt('Cache commands');
    case 'meta:cache:summary':
      return dt('Interact with Drupal\'s cache API.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function cache_drush_command() {
  $items = array();

  // We specify command callbacks here because the defaults would collide with
  // the drush cache api functions.
  $items['cache-get'] = array(
    'description' => 'Fetch a cached object and display it.',
    'examples' => array(
      'drush cache-get schema' => 'Display the data for the cache id "schema" from the "cache" bin.',
      'drush cache-get update_available_releases update' => 'Display the data for the cache id "update_available_releases" from the "update" bin.',
    ),
    'arguments' => array(
      'cid' => 'The id of the object to fetch.',
      'bin' => 'Optional. The cache bin to fetch from.',
    ),
    'required-arguments' => 1,
    'callback' => 'drush_cache_command_get',
    'outputformat' => array(
      'default' => 'print-r',
      'pipe-format' => 'var_export',
      'output-data-type' => TRUE,
    ),
    'aliases' => array('cg'),
  );
  $items['cache-clear'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'description' => 'Clear a specific cache, or all drupal caches.',
    'arguments' => array(
      'type' => 'The particular cache to clear. Omit this argument to choose from available caches.',
    ),
    'callback' => 'drush_cache_command_clear',
    'aliases' => array('cc'),
  );
  $items['cache-set'] = array(
    'description' => 'Cache an object expressed in JSON or var_export() format.',
    'arguments' => array(
      'cid' => 'The id of the object to set.',
      'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.',
      'bin' => 'Optional. The cache bin to store the object in.',
      'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.',
      'tags' => 'An array of cache tags.',
    ),
    'required-arguments' => 2,
    'options' => array(
      // Note that this is not an outputformat option.
      'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.',
      'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.',
    ),
    'callback' => 'drush_cache_command_set',
    'aliases' => array('cs'),
  );
  $items['cache-rebuild'] = array(
    'description' => 'Rebuild a Drupal 8 site and clear all its caches.',
    'options' => array(),
    'arguments' => array(),
    // Bootstrap to DRUSH_BOOTSTAP_DRUPAL_SITE to pick the correct site.
    // Further bootstrap is done by the rebuild script.
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
    'core' => array('8+'),
    'aliases' => array('cr', 'rebuild'),
  );

  return $items;
}

/**
 * Command argument complete callback.
 *
 * @return
 *   Array of clear types.
 */
function cache_cache_clear_complete() {
  // Bootstrap as far as possible so that Views and others can list their caches.
  drush_bootstrap_max();
  return array('values' => array_keys(drush_cache_clear_types(TRUE)));
}

function drush_cache_clear_pre_validate($type = NULL) {
  $types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL));
  // Check if the provided type ($type) is a valid cache type.
  if ($type && !array_key_exists($type, $types)) {
    if ($type === 'all' && drush_drupal_major_version() >= 8) {
      return drush_set_error(dt('`cache-clear all` is deprecated for Drupal 8 and later. Please use the `cache-rebuild` command instead.'));
    }
    // If we haven't done a full bootstrap, provide a more
    // specific message with instructions to the user on
    // bootstrapping a Drupal site for more options.
    if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      $all_types = drush_cache_clear_types(TRUE);
      if (array_key_exists($type, $all_types)) {
        return drush_set_error(dt("'!type' cache requires a working Drupal site to operate on. Use the --root and --uri options, or a site @alias, or cd to a directory containing a Drupal settings.php file.", array('!type' => $type)));
      }
      else {
        return drush_set_error(dt("'!type' cache is not a valid cache type. There may be more cache types available if you select a working Drupal site.", array('!type' => $type)));
      }
    }
    return drush_set_error(dt("'!type' cache is not a valid cache type.", array('!type' => $type)));
  }
}

/**
 * Command callback for drush cache-clear.
 */
function drush_cache_command_clear($type = NULL) {
  if (!drush_get_option('cache-clear', TRUE)) {
    drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK);
    return TRUE;
  }
  $types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL));

  if (!isset($type)) {
    // Don't offer 'all' unless Drush has bootstrapped the Drupal site
    if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      unset($types['all']);
    }
    $type = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key');
    if (empty($type)) {
      return drush_user_abort();
    }
  }
  // Do it.
  drush_op($types[$type]);
  if ($type == 'all' && !drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    drush_log(dt("No Drupal site found, only 'drush' cache was cleared."), LogLevel::WARNING);
  }
  else {
    drush_log(dt("'!name' cache was cleared.", array('!name' => $type)), LogLevel::SUCCESS);
  }
}

/**
 * Print an object returned from the cache.
 *
 * @param $cid
 *   The cache ID of the object to fetch.
 * @param $bin
 *   A specific bin to fetch from. If not specified, the default bin is used.
 */
function drush_cache_command_get($cid = NULL, $bin = NULL) {
  drush_include_engine('drupal', 'cache');
  $result = drush_op('_drush_cache_command_get', $cid, $bin);

  if (empty($result)) {
    return drush_set_error('DRUSH_CACHE_OBJECT_NOT_FOUND', dt('The !cid object in the !bin bin was not found.', array('!cid' => $cid, '!bin' => $bin ? $bin : _drush_cache_bin_default())));
  }
  return $result;
}

/**
 * Set an object in the cache.
 *
 * @param $cid
 *   The cache ID of the object to fetch.
 * @param $data
 *   The data to save to the cache, or '-' to read from STDIN.
 * @param $bin
 *   A specific bin to fetch from. If not specified, the default bin is used.
 * @param $expire
 *   The expiry timestamp for the cached object.
 * @param $tags
 *   Cache tags for the cached object.
 */
function drush_cache_command_set($cid = NULL, $data = '', $bin = NULL, $expire = NULL, $tags = array()) {
  // In addition to prepare, this also validates. Can't easily be in own validate callback as
  // reading once from STDIN empties it.
  $data = drush_cache_set_prepare_data($data);
  if ($data === FALSE && drush_get_error()) {
    // An error was logged above.
    return;
  }

  drush_include_engine('drupal', 'cache');
  return drush_op('_drush_cache_command_set', $cid, $data, $bin, $expire, $tags);
}

function drush_cache_set_prepare_data($data) {
  if ($data == '-') {
    $data = file_get_contents("php://stdin");
  }

  // Now, we parse the object.
  switch (drush_get_option('format', 'string')) {
    case 'json':
      $data = drush_json_decode($data);
      break;
  }

  if (drush_get_option('cache-get')) {
    // $data might be an object.
    if (is_object($data) && $data->data) {
      $data = $data->data;
    }
    // But $data returned from `drush cache-get --format=json` will be an array.
    elseif (is_array($data) && isset($data['data'])) {
      $data = $data['data'];
    }
    else {
      // If $data is neither object nor array and cache-get was specified, then
      // there is a problem.
      return drush_set_error('CACHE_INVALID_FORMAT', dt("'cache-get' was specified as an option, but the data is neither an object or an array."));
    }
  }

  return $data;
}

/**
 * All types of caches available for clearing. Contrib commands can alter in their own.
 */
function drush_cache_clear_types($include_bootstrapped_types = FALSE) {
  drush_include_engine('drupal', 'cache');
  $types = _drush_cache_clear_types($include_bootstrapped_types);

  // Include the appropriate environment engine, so callbacks can use core
  // version specific cache clearing functions directly.
  drush_include_engine('drupal', 'environment');

  // Command files may customize $types as desired.
  drush_command_invoke_all_ref('drush_cache_clear', $types, $include_bootstrapped_types);

  return $types;
}

/**
 * Clear caches internal to drush core.
 */
function drush_cache_clear_drush() {
  drush_cache_clear_all(NULL, 'default'); // commandfiles, etc.
  drush_cache_clear_all(NULL, 'complete'); // completion
  // Release XML. We don't clear tarballs since those never change.
  $matches = drush_scan_directory(drush_directory_cache('download'), "/^https---updates.drupal.org-release-history/", array('.', '..'));
  array_map('unlink', array_keys($matches));
}

/**
 * Rebuild a Drupal 8 site.
 *
 * This is a transpose of core/rebuild.php. Additionally
 * it also clears drush cache and drupal render cache.
 */
function drush_cache_rebuild() {
  if (!drush_get_option('cache-clear', TRUE)) {
    drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK);
    return TRUE;
  }
  chdir(DRUPAL_ROOT);

  // Clear the APC cache to ensure APC class loader is reset.
  if (function_exists('apc_fetch')) {
    apc_clear_cache('user');
  }
  // Clear user cache for all major platforms.
  $user_caches = [
    'apcu_clear_cache',
    'wincache_ucache_clear',
    'xcache_clear_cache',
  ];
  foreach (array_filter($user_caches, 'is_callable') as $cache) {
    call_user_func($cache);
  }

  $autoloader = drush_drupal_load_autoloader(DRUPAL_ROOT);
  require_once DRUSH_DRUPAL_CORE . '/includes/utility.inc';

  $request = Request::createFromGlobals();
  // Ensure that the HTTP method is set, which does not happen with Request::createFromGlobals().
  $request->setMethod('GET');
  // Manually resemble early bootstrap of DrupalKernel::boot().
  require_once DRUSH_DRUPAL_CORE . '/includes/bootstrap.inc';
  DrupalKernel::bootEnvironment();
  // Avoid 'Only variables should be passed by reference'
  $root  = DRUPAL_ROOT;
  $site_path = DrupalKernel::findSitePath($request);
  Settings::initialize($root, $site_path, $autoloader);

  // Use our error handler since _drupal_log_error() depends on an unavailable theme system (ugh).
  set_error_handler('drush_error_handler');

  // drupal_rebuild() calls drupal_flush_all_caches() itself, so we don't do it manually.
  drupal_rebuild($autoloader, $request);
  drush_log(dt('Cache rebuild complete.'), LogLevel::OK);

  // As this command replaces `drush cache-clear all` for Drupal 8 users, clear
  // the Drush cache as well, for consistency with that behavior.
  drush_cache_clear_drush();
}

<?php

use Drush\Log\LogLevel;
use Drupal\Component\Assertion\Handle;
use Drush\Psysh\DrushHelpCommand;
use Drush\Psysh\DrushCommand;
use Drush\Psysh\Shell;
use Psy\VersionUpdater\Checker;

/**
 * Implements hook_drush_command().
 */
function cli_drush_command() {
  $items['core-cli'] = array(
    'description' => 'Open an interactive shell on a Drupal site.',
    'remote-tty' => TRUE,
    'aliases' => array('php'),
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'topics' => array('docs-repl'),
    'options' => array(
      'version-history' => 'Use command history based on Drupal version (Default is per site).',
    ),
  );
  $items['docs-repl'] = array(
    'description' => 'repl.md',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array(drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH) . '/docs/repl.md'),
  );
  return $items;
}

/**
 * Command callback.
 */
function drush_cli_core_cli() {
  $drupal_major_version = drush_drupal_major_version();
  $configuration = new \Psy\Configuration();

  // Set the Drush specific history file path.
  $configuration->setHistoryFile(drush_history_path_cli());

  // Disable checking for updates. Our dependencies are managed with Composer.
  $configuration->setUpdateCheck(Checker::NEVER);

  $shell = new Shell($configuration);

  if ($drupal_major_version >= 8) {
    // Register the assertion handler so exceptions are thrown instead of errors
    // being triggered. This plays nicer with PsySH.
    Handle::register();
    $shell->setScopeVariables(['container' => \Drupal::getContainer()]);

    // Add Drupal 8 specific casters to the shell configuration.
    $configuration->addCasters(_drush_core_cli_get_casters());
  }

  // Add Drush commands to the shell.
  $commands = [new DrushHelpCommand()];

  foreach (drush_commands_categorize(_drush_core_cli_get_commands()) as $category_data) {
    $category_title = (string) $category_data['title'];
    foreach ($category_data['commands'] as $command_config) {
      $command = new DrushCommand($command_config);
      // Set the category label on each.
      $command->setCategory($category_title);
      $commands[] = $command;
    }
  }

  $shell->addCommands($commands);

  // PsySH will never return control to us, but our shutdown handler will still
  // run after the user presses ^D.  Mark this command as completed to avoid a
  // spurious error message.
  drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);

  // Run the terminate event before the shell is run. Otherwise, if the shell
  // is forking processes (the default), any child processes will close the
  // database connection when they are killed. So when we return back to the
  // parent process after, there is no connection. This will be called after the
  // command in preflight still, but the subscriber instances are already
  // created from before. Call terminate() regardless, this is a no-op for all
  // DrupalBoot classes except DrupalBoot8.
  if ($bootstrap = drush_get_bootstrap_object()) {
    $bootstrap->terminate();
  }

  // To fix the above problem in Drupal 7, the connection can be closed manually.
  // This will make sure a new connection is created again in child loops. So
  // any shutdown functions will still run ok after the shell has exited.
  if ($drupal_major_version == 7) {
    Database::closeConnection();
  }

  $shell->run();
}

/**
 * Returns a filtered list of Drush commands used for CLI commands.
 *
 * @return array
 */
function _drush_core_cli_get_commands() {
  $commands = drush_get_commands();
  $ignored_commands = ['help', 'drush-psysh', 'php-eval', 'core-cli', 'php'];
  $php_keywords = _drush_core_cli_get_php_keywords();

  foreach ($commands as $name => $config) {
    // Ignore some commands that don't make sense inside PsySH, are PHP keywords
    // are hidden, or are aliases.
    if (in_array($name, $ignored_commands) || in_array($name, $php_keywords) || !empty($config['hidden']) || ($name !== $config['command'])) {
      unset($commands[$name]);
    }
    else {
      // Make sure the command aliases don't contain any PHP keywords.
      if (!empty($config['aliases'])) {
        $commands[$name]['aliases'] = array_diff($commands[$name]['aliases'], $php_keywords);
      }
    }
  }

  return $commands;
}

/**
 * Returns a mapped array of casters for use in the shell.
 *
 * These are Symfony VarDumper casters.
 * See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
 * for more information.
 *
 * @return array.
 *   An array of caster callbacks keyed by class or interface.
 */
function _drush_core_cli_get_casters() {
  return [
    'Drupal\Core\Entity\ContentEntityInterface' => 'Drush\Psysh\Caster::castContentEntity',
    'Drupal\Core\Field\FieldItemListInterface' => 'Drush\Psysh\Caster::castFieldItemList',
    'Drupal\Core\Field\FieldItemInterface' => 'Drush\Psysh\Caster::castFieldItem',
    'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'Drush\Psysh\Caster::castConfigEntity',
    'Drupal\Core\Config\ConfigBase' => 'Drush\Psysh\Caster::castConfig',
    'Drupal\Component\DependencyInjection\Container' => 'Drush\Psysh\Caster::castContainer',
  ];
}

/**
 * Returns the file path for the CLI history.
 *
 * This can either be site specific (default) or Drupal version specific.
 *
 * @return string.
 */
function drush_history_path_cli() {
  $cli_directory = drush_directory_cache('cli');

  // If only the Drupal version is being used for the history.
  if (drush_get_option('version-history', FALSE)) {
    $drupal_major_version = drush_drupal_major_version();
    $file_name = "drupal-$drupal_major_version";
  }
  // If there is an alias, use that in the site specific name. Otherwise,
  // use a hash of the root path.
  else {
    if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
      $site = drush_sitealias_get_record($alias);
      $site_suffix = $site['#name'];
    }
    else {
      $site_suffix = md5(DRUPAL_ROOT);
    }

    $file_name = "drupal-site-$site_suffix";
  }

  $full_path = "$cli_directory/$file_name";

  // Output the history path if verbose is enabled.
  if (drush_get_context('DRUSH_VERBOSE')) {
    drush_log(dt('History: @full_path', ['@full_path' => $full_path]), LogLevel::INFO);
  }

  return $full_path;
}

/**
 * Returns a list of PHP keywords.
 *
 * This will act as a blacklist for command and alias names.
 *
 * @return array
 */
function _drush_core_cli_get_php_keywords() {
  return [
    '__halt_compiler',
    'abstract',
    'and',
    'array',
    'as',
    'break',
    'callable',
    'case',
    'catch',
    'class',
    'clone',
    'const',
    'continue',
    'declare',
    'default',
    'die',
    'do',
    'echo',
    'else',
    'elseif',
    'empty',
    'enddeclare',
    'endfor',
    'endforeach',
    'endif',
    'endswitch',
    'endwhile',
    'eval',
    'exit',
    'extends',
    'final',
    'for',
    'foreach',
    'function',
    'global',
    'goto',
    'if',
    'implements',
    'include',
    'include_once',
    'instanceof',
    'insteadof',
    'interface',
    'isset',
    'list',
    'namespace',
    'new',
    'or',
    'print',
    'private',
    'protected',
    'public',
    'require',
    'require_once',
    'return',
    'static',
    'switch',
    'throw',
    'trait',
    'try',
    'unset',
    'use',
    'var',
    'while',
    'xor',
  ];
}
<?php

/**
 * @file
 *   Provides Configuration Management commands.
 */

use Drupal\config\StorageReplaceDataWrapper;
use Drush\Log\LogLevel;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\Config\FileStorage;
use Drupal\Component\Utility\NestedArray;
use Drush\Config\StorageWrapper;
use Drush\Config\CoreExtensionFilter;
use Symfony\Component\Yaml\Parser;

/**
 * Implementation of hook_drush_help().
 */
function config_drush_help($section) {
  switch ($section) {
    case 'meta:config:title':
      return dt('Config commands');
    case 'meta:config:summary':
      return dt('Interact with the configuration system.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function config_drush_command() {
  $deps = array('drupal dependencies' => array('config'));
  $items['config-get'] = array(
    'description' => 'Display a config value, or a whole configuration object.',
    'arguments' => array(
      'config-name' => 'The config object name, for example "system.site".',
      'key' => 'The config key, for example "page.front". Optional.',
    ),
    'required-arguments' => 1,
    'options' => array(
      'source' => array(
        'description' => 'The config storage source to read. Additional labels may be defined in settings.php',
        'example-value' => 'sync',
        'value' => 'required',
      ),
      'include-overridden' => array(
        'description' => 'Include overridden values.',
      )
    ),
    'examples' => array(
      'drush config-get system.site' => 'Displays the system.site config.',
      'drush config-get system.site page.front' => 'gets system.site:page.front value.',
    ),
    'outputformat' => array(
      'default' => 'yaml',
      'pipe-format' => 'var_export',
    ),
    'aliases' => array('cget'),
    'core' => array('8+'),
  );

  $items['config-set'] = array(
    'description' => 'Set config value directly. Does not perform a config import.',
    'arguments' => array(
      'config-name' => 'The config object name, for example "system.site".',
      'key' => 'The config key, for example "page.front".',
      'value' => 'The value to assign to the config key. Use \'-\' to read from STDIN.',
    ),
    'options' => array(
      'format' => array(
        'description' => 'Format to parse the object. Use "string" for string (default), and "yaml" for YAML.',
        'example-value' => 'yaml',
        'value' => 'required',
      ),
      // A convenient way to pass a multiline value within a backend request.
      'value' => array(
        'description' => 'The value to assign to the config key (if any).',
        'hidden' => TRUE,
      ),
    ),
    'examples' => array(
      'drush config-set system.site page.front node' => 'Sets system.site:page.front to "node".',
    ),
    'aliases' => array('cset'),
    'core' => array('8+'),
  );

  $items['config-export'] = array(
    'description' => 'Export configuration to a directory.',
    'core' => array('8+'),
    'aliases' => array('cex'),
    'arguments' => array(
      'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
    ),
    'options' => array(
      'add' => 'Run `git add -p` after exporting. This lets you choose which config changes to sync for commit.',
      'commit' => 'Run `git add -A` and `git commit` after exporting.  This commits everything that was exported without prompting.',
      'message' => 'Commit comment for the exported configuration.  Optional; may only be used with --commit or --push.',
      'push' => 'Run `git push` after committing.  Implies --commit.',
      'remote' => array(
        'description' => 'The remote git branch to use to push changes.  Defaults to "origin".',
        'example-value' => 'origin',
      ),
      'branch' => array(
        'description' => 'Make commit on provided working branch. Ignored if used without --commit or --push.',
        'example-value' => 'branchname',
      ),
      'destination' => 'An arbitrary directory that should receive the exported files. An alternative to label argument.',
    ),
    'examples' => array(
      'drush config-export --destination' => 'Export configuration; Save files in a backup directory named config-export.',
    ),
  );

  $items['config-import'] = array(
    'description' => 'Import config from a config directory.',
    'arguments' => array(
      'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
    ),
    'options' => array(
      'preview' => array(
        'description' => 'Format for displaying proposed changes. Recognized values: list, diff. Defaults to list.',
        'example-value' => 'list',
      ),
      'source' => array(
        'description' => 'An arbitrary directory that holds the configuration files. An alternative to label argument',
      ),
      'partial' => array(
        'description' => 'Allows for partial config imports from the source directory. Only updates and new configs will be processed with this flag (missing configs will not be deleted).',
      ),
    ),
    'core' => array('8+'),
    'examples' => array(
      'drush config-import --partial' => 'Import configuration; do not remove missing configuration.',
    ),
    'aliases' => array('cim'),
  );

  $items['config-list'] = array(
    'description' => 'List config names by prefix.',
    'core' => array('8+'),
    'aliases' => array('cli'),
    'arguments' => array(
      'prefix' => 'The config prefix. For example, "system". No prefix will return all names in the system.',
    ),
    'examples' => array(
      'drush config-list system' => 'Return a list of all system config names.',
      'drush config-list "image.style"' => 'Return a list of all image styles.',
      'drush config-list --format="json"' => 'Return all config names as json.',
    ),
    'outputformat' => array(
      'default' => 'list',
      'pipe-format' => 'var_export',
      'output-data-type' => 'format-list',
    ),
  );

  $items['config-edit'] = $deps + array(
    'description' => 'Open a config file in a text editor. Edits are imported into active configuration after closing editor.',
    'core' => array('8+'),
    'aliases' => array('cedit'),
    'arguments' => array(
      'config-name' => 'The config object name, for example "system.site".',
    ),
    'global-options' => array('editor', 'bg'),
    'allow-additional-options' => array('config-import'),
    'examples' => array(
      'drush config-edit image.style.large' => 'Edit the image style configurations.',
      'drush config-edit' => 'Choose a config file to edit.',
      'drush config-edit --choice=2' => 'Edit the second file in the choice list.',
      'drush --bg config-edit image.style.large' => 'Return to shell prompt as soon as the editor window opens.',
    ),
  );

  $items['config-delete'] = array(
    'description' => 'Delete a configuration object.',
    'core' => array('8+'),
    'aliases' => array('cdel'),
    'arguments' => array(
      'config-name' => 'The config object name, for example "system.site".',
      'key' => 'A config key to clear, for example "page.front".',
    ),
    'required-arguments' => 1,
  );

  $items['config-pull'] = array(
    'description' => 'Export and transfer config from one environment to another.',
    // 'core' => array('8+'), Operates on remote sites so not possible to declare this locally.
    'drush dependencies' => array('config', 'core'), // core-rsync, core-execute.
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'aliases' => array('cpull'),
    'arguments' => array(
      'source' => 'A site-alias or the name of a subdirectory within /sites whose config you want to copy from.',
      'target' => 'A site-alias or the name of a subdirectory within /sites whose config you want to replace.',
    ),
    'required-arguments' => TRUE,
    'allow-additional-options' => array(), // Most options from config-export and core-rsync unusable.
    'examples' => array(
      'drush config-pull @prod @stage' => "Export config from @prod and transfer to @stage.",
      'drush config-pull @prod @self --label=vcs' => "Export config from @prod and transfer to the 'vcs' config directory of current site.",
    ),
    'options' => array(
      'safe' => 'Validate that there are no git uncommitted changes before proceeding',
      'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
      'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".',
    ),
    'topics' => array('docs-aliases', 'docs-config-exporting'),
  );

  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function config_drush_help_alter(&$command) {
  // Hide additional-options which are for internal use only.
  if ($command['command'] == 'config-edit') {
    $command['options']['source']['hidden'] = TRUE;
    $command['options']['partial']['hidden'] = TRUE;
  }
}

/**
 * Config list command callback
 *
 * @param string $prefix
 *   The config prefix to retrieve, or empty to return all.
 */
function drush_config_list($prefix = '') {
  $names = \Drupal::configFactory()->listAll($prefix);

  if (empty($names)) {
    // Just in case there is no config.
    if (!$prefix) {
      return drush_set_error(dt('No config storage names found.'));
    }
    else {
      return drush_set_error(dt('No config storage names found matching @prefix', array('@prefix' => $prefix)));
    }
  }

  return $names;
}

/**
 * Config get command callback.
 *
 * @param $config_name
 *   The config name.
 * @param $key
 *   The config key.
 */
function drush_config_get($config_name, $key = NULL) {
  if (!isset($key)) {
    return drush_config_get_object($config_name);
  }
  else {
    return drush_config_get_value($config_name, $key);
  }
}

/**
 * Config delete command callback.
 *
 * @param $config_name
 *   The config name.
 * @param $key
 *   A config key to clear, for example "page.front".
 */
function drush_config_delete($config_name, $key = null) {
  $config =\Drupal::service('config.factory')->getEditable($config_name);
  if ($config->isNew()) {
    return drush_set_error('DRUSH_CONFIG_ERROR', dt('Configuration name not recognized. Use config-list to see all names.'));
  }
  else {
    if ($key) {
      if ($config->get($key) === null) {
        return drush_set_error('DRUSH_CONFIG_ERROR', dt('Configuration key !key not found.', array('!key' => $key)));
      }
      $config->clear($key)->save();
    }
    else {
      $config->delete();
    }
  }
}

/**
 * Config set command callback.
 *
 * @param $config_name
 *   The config name.
 * @param $key
 *   The config key.
 * @param $data
 *    The data to save to config.
 */
function drush_config_set($config_name, $key = NULL, $data = NULL) {
  // This hidden option is a convenient way to pass a value without passing a key.
  $data = drush_get_option('value', $data);

  if (!isset($data)) {
    return drush_set_error('DRUSH_CONFIG_ERROR', dt('No config value specified.'));
  }

  $config = \Drupal::configFactory()->getEditable($config_name);
  // Check to see if config key already exists.
  if ($config->get($key) === NULL) {
    $new_key = TRUE;
  }
  else {
    $new_key = FALSE;
  }

  // Special flag indicating that the value has been passed via STDIN.
  if ($data === '-') {
    $data = stream_get_contents(STDIN);
  }

  // Now, we parse the value.
  switch (drush_get_option('format', 'string')) {
    case 'yaml':
      $parser = new Parser();
      $data = $parser->parse($data, TRUE);
  }

  if (is_array($data) && drush_confirm(dt('Do you want to update or set multiple keys on !name config.', array('!name' => $config_name)))) {
    foreach ($data as $key => $value) {
      $config->set($key, $value);
    }
    return $config->save();
  }
  else {
    $confirmed = FALSE;
    if ($config->isNew() && drush_confirm(dt('!name config does not exist. Do you want to create a new config object?', array('!name' => $config_name)))) {
      $confirmed = TRUE;
    }
    elseif ($new_key && drush_confirm(dt('!key key does not exist in !name config. Do you want to create a new config key?', array('!key' => $key, '!name' => $config_name)))) {
      $confirmed = TRUE;
    }
    elseif (drush_confirm(dt('Do you want to update !key key in !name config?', array('!key' => $key, '!name' => $config_name)))) {
      $confirmed = TRUE;
    }
    if ($confirmed && !drush_get_context('DRUSH_SIMULATE')) {
      return $config->set($key, $data)->save();
    }
  }
}

/*
 * If provided $destination is not TRUE and not empty, make sure it is writable.
 */
function drush_config_export_validate() {
  $destination = drush_get_option('destination');
  if ($destination === TRUE) {
    // We create a dir in command callback. No need to validate.
    return;
  }

  if (!empty($destination)) {
    $additional = array();
    $values = drush_sitealias_evaluate_path($destination, $additional, TRUE);
    if (!isset($values['path'])) {
      return drush_set_error('config_export_target', 'The destination directory could not be evaluated.');
    }
    $destination = $values['path'];
    drush_set_option('destination', $destination);
    if (!file_exists($destination)) {
      $parent = dirname($destination);
      if (!is_dir($parent)) {
        return drush_set_error('config_export_target', 'The destination parent directory does not exist.');
      }
      if (!is_writable($parent)) {
        return drush_set_error('config_export_target', 'The destination parent directory is not writable.');
      }
    }
    else {
      if (!is_dir($destination)) {
        return drush_set_error('config_export_target', 'The destination is not a directory.');
      }
      if (!is_writable($destination)) {
        return drush_set_error('config_export_target', 'The destination directory is not writable.');
      }
    }
  }
}

/**
 * Command callback: Export config to specified directory (usually sync).
 */
function drush_config_export($destination = NULL) {
  global $config_directories;

  // Determine which target directory to use.
  if ($target = drush_get_option('destination')) {
    if ($target === TRUE) {
      // User did not pass a specific value for --destination. Make one.
      /** @var drush_version_control_backup $backup */
      $backup = drush_include_engine('version_control', 'backup');
      $destination_dir = $backup->prepare_backup_dir('config-export');
    }
    else {
      $destination_dir = $target;
      // It is important to be able to specify a destination directory that
      // does not exist yet, for exporting on remote systems
      drush_mkdir($destination_dir);
    }
  }
  else {
    $choices = drush_map_assoc(array_keys($config_directories));
    unset($choices[CONFIG_ACTIVE_DIRECTORY]);
    if (!isset($destination) && count($choices) >= 2) {
      $destination = drush_choice($choices, 'Choose a destination.');
      if (empty($destination)) {
        return drush_user_abort();
      }
    }
    elseif (!isset($destination)) {
      $destination = CONFIG_SYNC_DIRECTORY;
    }
    $destination_dir = config_get_config_directory($destination);
  }

  // Prepare a new branch, if applicable
  $remote = drush_get_option('push', FALSE);
  $original_branch = FALSE;
  $branch = FALSE;
  if ($remote) {
    // Get the branch that we're on at the moment
    $result = drush_shell_cd_and_exec($destination_dir, 'git rev-parse --abbrev-ref HEAD');
    if (!$result) {
      return drush_set_error('DRUSH_CONFIG_EXPORT_NO_GIT', dt("The drush config-export command requires that the selected configuration directory !dir be under git revision control when using --commit or --push options.", array('!dir' => $destination_dir)));
    }
    $output = drush_shell_exec_output();
    $original_branch = $output[0];
    $branch = drush_get_option('branch', FALSE);
    if (!$branch) {
      $branch = $original_branch;
    }
    if ($branch != $original_branch) {
      // Switch to the working branch; create it if it does not exist.
      // We do NOT want to use -B here, as we do NOT want to reset the
      // branch if it already exists.
      $result = drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $branch);
      if (!$result) {
        $result = drush_shell_cd_and_exec($destination_dir, 'git checkout -b %s', $branch);
      }
    }
  }

  // Do the actual config export operation
  $result = _drush_config_export($destination, $destination_dir, $branch);

  // Regardless of the result of the export, reset to our original branch.
  if ($branch != $original_branch) {
    drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $original_branch);
  }

  return $result;
}

function _drush_config_export($destination, $destination_dir, $branch) {
  $commit = drush_get_option('commit');
  $comment = drush_get_option('message', 'Exported configuration.');
  if (count(glob($destination_dir . '/*')) > 0) {
    // Retrieve a list of differences between the active and target configuration (if any).
    if ($destination == CONFIG_SYNC_DIRECTORY) {
      $target_storage = \Drupal::service('config.storage.sync');
    }
    else {
      $target_storage = new FileStorage($destination_dir);
    }
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
    $active_storage = \Drupal::service('config.storage');
    $comparison_source = $active_storage;

    $config_comparer = new StorageComparer($comparison_source, $target_storage, \Drupal::service('config.manager'));
    if (!$config_comparer->createChangelist()->hasChanges()) {
      return drush_log(dt('The active configuration is identical to the configuration in the export directory (!target).', array('!target' => $destination_dir)), LogLevel::OK);
    }

    drush_print("Differences of the active config to the export directory:\n");
    $change_list = array();
    foreach ($config_comparer->getAllCollectionNames() as $collection) {
      $change_list[$collection] = $config_comparer->getChangelist(NULL, $collection);
    }
    // Print a table with changes in color, then re-generate again without
    // color to place in the commit comment.
    _drush_print_config_changes_table($change_list);
    $tbl = _drush_format_config_changes_table($change_list);
    $output = $tbl->getTable();
    if (!stristr(PHP_OS, 'WIN')) {
      $output = str_replace("\r\n", PHP_EOL, $output);
    }
    $comment .= "\n\n$output";

    if (!$commit && !drush_confirm(dt('The .yml files in your export directory (!target) will be deleted and replaced with the active config.', array('!target' => $destination_dir)))) {
      return drush_user_abort();
    }
    // Only delete .yml files, and not .htaccess or .git.
    $target_storage->deleteAll();
  }

  // Write all .yml files.
  $source_storage = \Drupal::service('config.storage');
  if ($destination == CONFIG_SYNC_DIRECTORY) {
    $destination_storage = \Drupal::service('config.storage.sync');
  }
  else {
    $destination_storage = new FileStorage($destination_dir);
  }

  foreach ($source_storage->listAll() as $name) {
    $destination_storage->write($name, $source_storage->read($name));
  }

  // Export configuration collections.
  foreach (\Drupal::service('config.storage')->getAllCollectionNames() as $collection) {
    $source_storage = $source_storage->createCollection($collection);
    $destination_storage = $destination_storage->createCollection($collection);
    foreach ($source_storage->listAll() as $name) {
      $destination_storage->write($name, $source_storage->read($name));
    }
  }

  drush_log(dt('Configuration successfully exported to !target.', array('!target' => $destination_dir)), LogLevel::SUCCESS);
  drush_backend_set_result($destination_dir);

  // Commit and push, or add exported configuration if requested.
  $remote = drush_get_option('push', FALSE);
  if ($commit || $remote) {
    // There must be changed files at the destination dir; if there are not, then
    // we will skip the commit-and-push step
    $result = drush_shell_cd_and_exec($destination_dir, 'git status --porcelain .');
    if (!$result) {
      return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git status` failed."));
    }
    $uncommitted_changes = drush_shell_exec_output();
    if (!empty($uncommitted_changes)) {
      $result = drush_shell_cd_and_exec($destination_dir, 'git add -A .');
      if (!$result) {
        return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git add -A` failed."));
      }
      $comment_file = drush_save_data_to_temp_file($comment);
      $result = drush_shell_cd_and_exec($destination_dir, 'git commit --file=%s', $comment_file);
      if (!$result) {
        return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git commit` failed.  Output:\n\n!output", array('!output' => implode("\n", drush_shell_exec_output()))));
      }
      if ($remote) {
        // Remote might be FALSE, if --push was not specified, or
        // it might be TRUE if --push was not given a value.
        if (!is_string($remote)) {
          $remote = 'origin';
        }
        $result = drush_shell_cd_and_exec($destination_dir, 'git push --set-upstream %s %s', $remote, $branch);
        if (!$result) {
          return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git push` failed."));
        }
      }
    }
  }
  elseif (drush_get_option('add')) {
    drush_shell_exec_interactive('git add -p %s', $destination_dir);
  }

  $values = array(
    'destination' => $destination_dir,
  );
  return $values;
}

function drush_config_import_validate() {
  drush_include_engine('drupal', 'environment');
  if (drush_get_option('partial') && !drush_module_exists('config')) {
    return drush_set_error('config_import_partial', 'Enable the config module in order to use the --partial option.');
  }
  if ($source = drush_get_option('source')) {
    if (!file_exists($source)) {
      return drush_set_error('config_import_target', 'The source directory does not exist.');
    }
    if (!is_dir($source)) {
      return drush_set_error('config_import_target', 'The source is not a directory.');
    }
  }
}

/**
 * Command callback. Import from specified config directory (defaults to sync).
 */
function drush_config_import($source = NULL) {
  global $config_directories;

  // Determine source directory.
  if ($target = drush_get_option('source')) {
    $source_dir = $target;
  }
  else {
    $choices = drush_map_assoc(array_keys($config_directories));
    unset($choices[CONFIG_ACTIVE_DIRECTORY]);
    if (!isset($source) && count($choices) >= 2) {
      $source= drush_choice($choices, 'Choose a source.');
      if (empty($source)) {
        return drush_user_abort();
      }
    }
    elseif (!isset($source)) {
      $source = CONFIG_SYNC_DIRECTORY;
    }
    $source_dir = config_get_config_directory($source);
  }

  if ($source == CONFIG_SYNC_DIRECTORY) {
    $source_storage = \Drupal::service('config.storage.sync');
  }
  else {
    $source_storage = new FileStorage($source_dir);
  }

  // Determine $source_storage in partial and non-partial cases.
  /** @var \Drupal\Core\Config\StorageInterface $active_storage */
  $active_storage = \Drupal::service('config.storage');
  if (drush_get_option('partial')) {
    $replacement_storage = new StorageReplaceDataWrapper($active_storage);
    foreach ($source_storage->listAll() as $name) {
      $data = $source_storage->read($name);
      $replacement_storage->replaceData($name, $data);
    }
    $source_storage = $replacement_storage;
  }

  /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  $config_manager = \Drupal::service('config.manager');
  $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager);

  if (!$storage_comparer->createChangelist()->hasChanges()) {
    return drush_log(dt('There are no changes to import.'), LogLevel::OK);
  }

  if (drush_get_option('preview', 'list') == 'list') {
    $change_list = array();
    foreach ($storage_comparer->getAllCollectionNames() as $collection) {
      $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
    }
    _drush_print_config_changes_table($change_list);
  }
  else {
    // Copy active storage to the temporary directory.
    $temp_dir = drush_tempdir();
    $temp_storage = new FileStorage($temp_dir);
    $source_dir_storage = new FileStorage($source_dir);
    foreach ($source_dir_storage->listAll() as $name) {
      if ($data = $active_storage->read($name)) {
        $temp_storage->write($name, $data);
      }
    }
    drush_shell_exec('diff -x %s -u %s %s', '*.git', $temp_dir, $source_dir);
    $output = drush_shell_exec_output();
    drush_print(implode("\n", $output));
  }

  if (drush_confirm(dt('Import the listed configuration changes?'))) {
    return drush_op('_drush_config_import', $storage_comparer);
  }
}

// Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php
function _drush_config_import(StorageComparer $storage_comparer) {
  $config_importer = new ConfigImporter(
    $storage_comparer,
    \Drupal::service('event_dispatcher'),
    \Drupal::service('config.manager'),
    \Drupal::lock(),
    \Drupal::service('config.typed'),
    \Drupal::moduleHandler(),
    \Drupal::service('module_installer'),
    \Drupal::service('theme_handler'),
    \Drupal::service('string_translation')
  );
  if ($config_importer->alreadyImporting()) {
    drush_log('Another request may be synchronizing configuration already.', LogLevel::WARNING);
  }
  else{
    try {
      // This is the contents of \Drupal\Core\Config\ConfigImporter::import.
      // Copied here so we can log progress.
      if ($config_importer->hasUnprocessedConfigurationChanges()) {
        $sync_steps = $config_importer->initialize();
        foreach ($sync_steps as $step) {
          $context = array();
          do {
            $config_importer->doSyncStep($step, $context);
            if (isset($context['message'])) {
              drush_log(str_replace('Synchronizing', 'Synchronized', (string)$context['message']), LogLevel::OK);
            }
          } while ($context['finished'] < 1);
        }
      }
      if ($config_importer->getErrors()) {
        throw new \Drupal\Core\Config\ConfigException('Errors occurred during import');
      }
      else {
        drush_log('The configuration was imported successfully.', LogLevel::SUCCESS);
      }
    }
    catch (ConfigException $e) {
      // Return a negative result for UI purposes. We do not differentiate
      // between an actual synchronization error and a failed lock, because
      // concurrent synchronizations are an edge-case happening only when
      // multiple developers or site builders attempt to do it without
      // coordinating.
      $message = 'The import failed due for the following reasons:' . "\n";
      $message .= implode("\n", $config_importer->getErrors());

      watchdog_exception('config_import', $e);
      return drush_set_error('config_import_fail', $message);
    }
  }
}

/**
 * Edit command callback.
 */
function drush_config_edit($config_name = '') {
  // Identify and validate input.
  if ($config_name) {
    $config = \Drupal::configFactory()->get($config_name);
    if ($config->isNew()) {
      return drush_set_error(dt('Config !name does not exist', array('!name' => $config_name)));
    }
  }
  else {
    $config_names = \Drupal::configFactory()->listAll();
    $choice = drush_choice($config_names, 'Choose a configuration.');
    if (empty($choice)) {
      return drush_user_abort();
    }
    else {
      $config_name = $config_names[$choice];
      $config = \Drupal::configFactory()->get($config_name);
    }
  }

  $active_storage = $config->getStorage();
  $contents = $active_storage->read($config_name);

  // Write tmp YAML file for editing
  $temp_dir = drush_tempdir();
  $temp_storage = new FileStorage($temp_dir);
  $temp_storage->write($config_name, $contents);

  $exec = drush_get_editor();
  drush_shell_exec_interactive($exec, $temp_storage->getFilePath($config_name));

  // Perform import operation if user did not immediately exit editor.
  if (!drush_get_option('bg', FALSE)) {
    $options = drush_redispatch_get_options() + array('partial' => TRUE, 'source' => $temp_dir);
    $backend_options = array('interactive' => TRUE);
    return (bool) drush_invoke_process('@self', 'config-import', array(), $options, $backend_options);
  }
}

/**
 * Config pull validate callback
 *
 */
function drush_config_pull_validate($source, $destination) {
  if (drush_get_option('safe')) {
    $return = drush_invoke_process($destination, 'core-execute', array('git diff --quiet'), array('escape' => 0));
    if ($return['error_status']) {
      return drush_set_error('DRUSH_GIT_DIRTY', 'There are uncommitted changes in your git working copy.');
    }
  }
}

/**
 * Config pull command callback
 *
 * @param string $label
 *   The config label which receives the transferred files.
 */
function drush_config_pull($source, $destination) {
  // @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
  $global_options = drush_redispatch_get_options() + array(
    'strict' => 0,
  );

  // @todo If either call is made interactive, we don't get an $return['object'] back.
  $backend_options = array('interactive' => FALSE);
  if (drush_get_context('DRUSH_SIMULATE')) {
    $backend_options['backend-simulate'] = TRUE;
  }

  $export_options = array(
    // Use the standard backup directory on Destination.
    'destination' => TRUE,
  );
  drush_log(dt('Starting to export configuration on Target.'), LogLevel::OK);
  $return = drush_invoke_process($source, 'config-export', array(), $global_options + $export_options, $backend_options);
  if ($return['error_status']) {
    return drush_set_error('DRUSH_CONFIG_PULL_EXPORT_FAILED', dt('Config-export failed.'));
  }
  else {
    // Trailing slash assures that transfer files and not the containing dir.
    $export_path = $return['object'] . '/';
  }

  $rsync_options = array(
    'remove-source-files' => TRUE,
    'delete' => TRUE,
    'exclude-paths' => '.htaccess',
    'yes' => TRUE,  // No need to prompt as destination is always the target config directory.
  );
  $label = drush_get_option('label', 'sync');
  $runner = drush_get_runner($source, $destination, drush_get_option('runner', FALSE));
  drush_log(dt('Starting to rsync configuration files from !source to !dest.', array('!source' => $source, '!dest' => $destination)), LogLevel::OK);
  // This comment applies similarly to sql-sync's use of core-rsync.
  // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
  // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
  $return = drush_invoke_process($runner, 'core-rsync', array("$source:$export_path", "$destination:%config-$label"), $rsync_options);
  if ($return['error_status']) {
    return drush_set_error('DRUSH_CONFIG_PULL_RSYNC_FAILED', dt('Config-pull rsync failed.'));
  }

  drush_backend_set_result($return['object']);
}

/**
 * Show and return a config object
 *
 * @param $config_name
 *   The config object name.
 */
function drush_config_get_object($config_name) {
  $source = drush_get_option('source', 'active');
  $include_overridden = drush_get_option('include-overridden', FALSE);

  if ($include_overridden) {
    // Displaying overrides only applies to active storage.
    $config = \Drupal::config($config_name);
    $data = $config->get();
  }
  elseif ($source == 'active') {
    $config = \Drupal::service('config.storage');
    $data = $config->read($config_name);
  }
  elseif ($source == 'sync') {
    $config = \Drupal::service('config.storage.sync');
    $data = $config->read($config_name);
  }
  else {
    return drush_set_error(dt('Unknown value !value for config source.', array('!value' => $source)));
  }

  if ($data === FALSE) {
    return drush_set_error(dt('Config !name does not exist in !source configuration.', array('!name' => $config_name, '!source' => $source)));
  }
  if (empty($data)) {
    drush_log(dt('Config !name exists but has no data.', array('!name' => $config_name)), LogLevel::NOTICE);
    return;
  }
  return $data;
}

/**
 * Show and return a value from config system.
 *
 * @param $config_name
 *   The config name.
 * @param $key
 *   The config key.
 */
function drush_config_get_value($config_name, $key) {
  $data = drush_config_get_object($config_name);
  $parts = explode('.', $key);
  if (count($parts) == 1) {
    $value =  isset($data[$key]) ? $data[$key] : NULL;
  }
  else {
    $value = NestedArray::getValue($data, $parts, $key_exists);
    $value = $key_exists ? $value : NULL;
  }

  $returns[$config_name . ':' . $key] = $value;

  if ($value === NULL) {
    return drush_set_error('DRUSH_CONFIG_ERROR', dt('No matching key found in !name config.', array('!name' => $config_name)));
  }
  else {
    return $returns;
  }
}

/**
 * Print a table of config changes.
 *
 * @param array $config_changes
 *   An array of changes keyed by collection.
 */
function _drush_format_config_changes_table(array $config_changes, $use_color = FALSE) {
  if (!$use_color) {
    $red = "%s";
    $yellow = "%s";
    $green = "%s";
  }
  else {
    $red = "\033[31;40m\033[1m%s\033[0m";
    $yellow = "\033[1;33;40m\033[1m%s\033[0m";
    $green = "\033[1;32;40m\033[1m%s\033[0m";
  }

  $rows = array();
  $rows[] = array('Collection', 'Config', 'Operation');
  foreach ($config_changes as $collection => $changes) {
    foreach ($changes as $change => $configs) {
      switch ($change) {
        case 'delete':
          $colour = $red;
          break;
        case 'update':
          $colour = $yellow;
          break;
        case 'create':
          $colour = $green;
          break;
        default:
          $colour = "%s";
          break;
      }
      foreach($configs as $config) {
        $rows[] = array(
          $collection,
          $config,
          sprintf($colour, $change)
        );
      }
    }
  }
  $tbl = _drush_format_table($rows);
  return $tbl;
}

/**
 * Print a table of config changes.
 *
 * @param array $config_changes
 *   An array of changes keyed by collection.
 */
function _drush_print_config_changes_table(array $config_changes) {
  $tbl =  _drush_format_config_changes_table($config_changes, !drush_get_context('DRUSH_NOCOLOR'));

  $output = $tbl->getTable();
  if (!stristr(PHP_OS, 'WIN')) {
    $output = str_replace("\r\n", PHP_EOL, $output);
  }

  drush_print(rtrim($output));
  return $tbl;
}

/**
 * Command argument complete callback.
 */
function config_config_get_complete() {
  return _drush_config_names_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_set_complete() {
  return _drush_config_names_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_view_complete() {
  return _drush_config_names_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_edit_complete() {
  return _drush_config_names_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_import_complete() {
  return _drush_config_directories_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_export_complete() {
  return _drush_config_directories_complete();
}

/**
 * Command argument complete callback.
 */
function config_config_pull_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/**
 * Helper function for command argument complete callback.
 *
 * @return
 *   Array of available config directories.
 */
function _drush_config_directories_complete() {
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  global $config_directories;
  return array('values' => array_keys($config_directories));
}

/**
 * Helper function for command argument complete callback.
 *
 * @return
 *   Array of available config names.
 */
function _drush_config_names_complete() {
  drush_bootstrap_max();
  return array('values' => $storage = \Drupal::service('config.storage')->listAll());
}
<?php

/**
 * @file
 *   Core drush commands.
 */

use Drush\Log\LogLevel;

/**
 * Implementation of hook_drush_help().
 *
 * This function is called whenever a drush user calls
 * 'drush help <name-of-your-command>'
 *
 * @param
 *   A string with the help section (prepend with 'drush:')
 *
 * @return
 *   A string with the help text for your command.
 */
function core_drush_help($section) {
  switch ($section) {
    case 'meta:core:title':
      return dt("Core Drush commands");
    case 'drush:php-script':
      return dt("Runs the given php script(s) after a full Drupal bootstrap. A useful alternative to eval command when your php is lengthy or you can't be bothered to figure out bash quoting. If you plan to share a script with others, consider making a full drush command instead, since that's more self-documenting.  Drush provides commandline options to the script via drush_get_option('option-name'), and commandline arguments can be accessed either via drush_get_arguments(), which returns all arguments in an array, or drush_shift(), which removes the next argument from the list and returns it.");
    case 'drush:rsync':
      return dt("Sync the entire drupal directory or a subdirectory to a <destination> using ssh. Excludes reserved files and directories for supported VCSs. Useful for pushing copies of your tree to a staging server, or retrieving a files directory from a remote site. Relative paths start from the Drupal root directory if a site alias is used; otherwise they start from the current working directory.");
    case 'error:DRUSH_DRUPAL_DB_ERROR':
      $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n");
      $message .= dt("Hint: This may occur when Drush is trying to:\n");
      $message .= dt(" * bootstrap a site that has not been installed or does not have a configured database. In this case you can select another site with a working database setup by specifying the URI to use with the --uri parameter on the command line. See `drush topic docs-aliases` for details.\n");
      $message .= dt(" * connect the database through a socket. The socket file may be wrong or the php-cli may have no access to it in a jailed shell. See http://drupal.org/node/1428638 for details.\n");
      $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials(12)));
      return $message;
    case 'error:DRUSH_DRUPAL_BOOTSTRAP_ERROR':
      $message = dt("Drush was not able to start (bootstrap) Drupal.\n");
      $message .= dt("Hint: This error can only occur once the database connection has already been successfully initiated, therefore this error generally points to a site configuration issue, and not a problem connecting to the database.\n");
      $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials(12)));
      return $message;
      break;
  }
}

/**
 * Implements hook_drush_help_alter().
 */
function core_drush_help_alter(&$command) {
  // Drupal 8+ only options.
  if (drush_drupal_major_version() < 8) {
    if ($command['commandfile'] == 'core' && $command['command'] == 'updatedb') {
      unset($command['options']['entity-updates']);
    }
  }
}

/**
 * Implementation of hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * @return
 *   An associative array describing your command(s).
 */
function core_drush_command() {
  $items = array();

  $items['version'] = array(
    'description' => 'Show drush version.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap.
    'options' => array(
      'pipe' => 'Print just the version number, and nothing else.',
    ),
    'outputformat' => array(
      'default' => 'key-value',
      'pipe-format' => 'string',
      'label' => 'Drush Version',
      'output-data-type' => 'format-single',
    ),
  );
  $items['core-cron'] = array(
    'description' => 'Run all cron hooks in all active modules for specified site.',
    'aliases' => array('cron'),
    'topics' => array('docs-cron'),
  );
  $items['updatedb'] = array(
    'description' => 'Apply any database updates required (as with running update.php).',
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
    'global-options' => array(
      'cache-clear',
    ),
    'options' => array(
      'entity-updates' => 'Run automatic entity schema updates at the end of any update hooks. Defaults to --no-entity-updates.',
    ),
    'aliases' => array('updb'),
  );
  $items['entity-updates'] = array(
    'description' => 'Apply pending entity schema updates.',
    'aliases' => array('entup'),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
    'core' => array('8+'),
  );
  $items['twig-compile'] = array(
    'description' => 'Compile all Twig template(s).',
    'aliases' => array('twigc'),
    'core' => array('8+'),
  );
  $items['updatedb-status'] = array(
    'description' => 'List any pending database updates.',
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'csv',
      'field-labels' => array('module' => 'Module', 'update_id' => 'Update ID', 'description' => 'Description'),
      'fields-default' => array('module', 'update_id', 'description'),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('updbst'),
  );
  $items['core-config'] = array(
    'description' => 'Edit drushrc, site alias, and Drupal settings.php files.',
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'arguments' => array(
      'filter' => 'A substring for filtering the list of files. Omit this argument to choose from loaded files.',
    ),
    'global-options' => array('editor', 'bg'),
    'examples' => array(
      'drush core-config' => 'Pick from a list of config/alias/settings files. Open selected in editor.',
      'drush --bg core-config' => 'Return to shell prompt as soon as the editor window opens.',
      'drush core-config etc' => 'Edit the global configuration file.',
      'drush core-config demo.alia' => 'Edit a particular alias file.',
      'drush core-config sett' => 'Edit settings.php for the current Drupal site.',
      'drush core-config --choice=2' => 'Edit the second file in the choice list.',
    ),
    'aliases' => array('conf', 'config'),
  );
  $items['core-status'] = array(
    'description' => 'Provides a birds-eye view of the current Drupal installation, if any.',
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'aliases' => array('status', 'st'),
    'examples' => array(
      'drush core-status version' => 'Show all status lines that contain version information.',
      'drush core-status --pipe' => 'A list key=value items separated by line breaks.',
      'drush core-status drush-version --pipe' => 'Emit just the drush version with no label.',
      'drush core-status config-sync --pipe' => 'Emit just the sync Config directory with no label.',
    ),
    'arguments' => array(
      'item' => 'Optional.  The status item line(s) to display.',
    ),
    'options' => array(
      'show-passwords' => 'Show database password.  Defaults to --no-show-passwords.',
      'full' => 'Show all file paths and drush aliases in the report, even if there are a lot.',
      'project' => array(
        'description' => 'One or more projects that should be added to the path list',
        'example-value' => 'foo,bar',
      ),
    ),
    'outputformat' => array(
      'default' => 'key-value',
      'pipe-format' => 'json',
      'field-labels' => array('drupal-version' => 'Drupal version', 'uri' => 'Site URI', 'db-driver' => 'Database driver', 'db-hostname' => 'Database hostname', 'db-port' => 'Database port', 'db-username' => 'Database username', 'db-password' => 'Database password', 'db-name' => 'Database name', 'db-status' => 'Database', 'bootstrap' => 'Drupal bootstrap', 'user' => 'Drupal user', 'theme' => 'Default theme', 'admin-theme' => 'Administration theme', 'php-bin' => 'PHP executable', 'php-conf' => 'PHP configuration', 'php-os' => 'PHP OS', 'drush-script' => 'Drush script', 'drush-version' => 'Drush version', 'drush-temp' => 'Drush temp directory', 'drush-conf' => 'Drush configuration', 'drush-alias-files' => 'Drush alias files', 'install-profile' => 'Install profile', 'root' => 'Drupal root', 'drupal-settings-file' => 'Drupal Settings File', 'site-path' => 'Site path', 'root' => 'Drupal root', 'site' => 'Site path', 'themes' => 'Themes path', 'modules' => 'Modules path', 'files' => 'File directory path', 'private' => 'Private file directory path', 'temp' => 'Temporary file directory path', 'config-sync' => 'Sync config path', 'files-path' => 'File directory path', 'temp-path' => 'Temporary file directory path', '%paths' => 'Other paths'),
      'formatted-filter' => '_drush_core_status_format_table_data',
      'private-fields' => 'db-password',
      'simplify-single' => TRUE,
      'table-metadata' => array(
        'list-separator' => ' ',
      ),
      'output-data-type' => 'format-list',
    ),
    'topics' => array('docs-readme'),
  );

  $items['core-requirements'] = array(
    'description' => 'Provides information about things that may be wrong in your Drupal installation, if any.',
    'aliases' => array('status-report','rq'),
    'examples' => array(
      'drush core-requirements' => 'Show all status lines from the Status Report admin page.',
      'drush core-requirements --severity=2' => 'Show only the red lines from the Status Report admin page.',
      'drush core-requirements --pipe' => 'Print out a short report in JSON format, where severity 2=error, 1=warning, and 0/-1=OK',
    ),
    'options' => array(
      'severity' => array(
        'description' => 'Only show status report messages with a severity greater than or equal to the specified value.',
        'value' => 'required',
        'example-value' => '3',
      ),
      'ignore' => 'Comma-separated list of requirements to remove from output. Run with --pipe to see key values to use.',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'json',
      'field-labels' => array('title' => 'Title', 'severity' => 'Severity', 'sid' => 'SID', 'description' => 'Description', 'value' => 'Summary', 'reason' => 'Reason', 'weight' => 'Weight'),
      'fields-default' => array('title', 'severity', 'description'),
      'column-widths' => array('severity' => 8),
      'concatenate-columns' => array('description' => array('value', 'description')),
      'strip-tags' => TRUE,
      'ini-item' => 'sid',
      'key-value-item' => 'severity',
      'list-metadata' => array(
        'list-item' => 'severity',
      ),
      'output-data-type' => 'format-table',
    ),
  );
  $items['php-eval'] = array(
    'description' => 'Evaluate arbitrary php code after bootstrapping Drupal (if available).',
    'examples' => array(
      'drush php-eval \'variable_set("hello", "world");\'' => 'Sets the hello variable using Drupal API.',
      'drush php-eval \'$node = node_load(1); print $node->title;\'' => 'Loads node with nid 1 and then prints its title.',
      'drush php-eval "file_unmanaged_copy(\'$HOME/Pictures/image.jpg\', \'public://image.jpg\');"' => 'Copies a file whose path is determined by an environment\'s variable. Note the use of double quotes so the variable $HOME gets replaced by its value.',
      'drush php-eval "node_access_rebuild();"' => 'Rebuild node access permissions.',
    ),
    'arguments' => array(
      'code' => 'PHP code',
    ),
    'required-arguments' => TRUE,
    'allow-additional-options' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'aliases' => array('eval', 'ev'),
    'outputformat' => array(
      'default' => 'var_export',
    ),
  );
  $items['php-script'] = array(
    'description' => "Run php script(s).",
    'examples' => array(
      'drush php-script scratch' => 'Run scratch.php script. See commands/core directory.',
      'drush php-script example --script-path=/path/to/scripts:/another/path' => 'Run script from specified paths',
      'drush php-script' => 'List all available scripts.',
      '' => '',
      "#!/usr/bin/env drush\n<?php\nvariable_set('key', drush_shift());" => "Execute php code with a full Drupal bootstrap directly from a shell script.",
    ),
    'arguments' => array(
      'filename' => 'Optional. The file you wish to execute (without extension). If omitted, list files ending in .php in the current working directory and specified script-path. Some might not be real drush scripts. Beware.',
    ),
    'options' => array(
      'script-path' => array(
        'description' => "Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).",
        'example-value' => '~/scripts',
      ),
    ),
    'allow-additional-options' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'aliases' => array('scr'),
    'topics' => array('docs-examplescript', 'docs-scripts'),
  );
  $items['core-execute'] = array(
    'description' => 'Execute a shell command. Usually used with a site alias.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap.
    'arguments' => array(
      'command' => 'The shell command to be executed.',
    ),
    'options' => array(
      'escape' => 'Escape parameters before executing them with the shell. Default is escape; use --no-escape to disable.',
    ) + drush_shell_exec_proc_build_options(),
    'required-arguments' => TRUE,
    'allow-additional-options' => TRUE,
    'handle-remote-commands' => TRUE,
    'strict-option-handling' => TRUE,
    'examples' => array(
      'drush core-execute git pull origin rebase' => 'Retrieve latest code from git',
    ),
    'aliases' => array('exec', 'execute'),
    'topics' => array('docs-aliases'),
  );
  $items['core-rsync'] = array(
    'description' => 'Rsync the Drupal tree to/from another server using ssh.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE, // No bootstrap.
    'arguments' => array(
      'source' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.',
      'destination' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.',
    ),
    'options' => array(
      'mode' => 'The unary flags to pass to rsync; --mode=rultz implies rsync -rultz.  Default is -akz.',
      'exclude-conf' => 'Excludes settings.php from being rsynced.  Default.',
      'include-conf' => 'Allow settings.php to be rsynced. Default is to exclude settings.php.',
      'include-vcs' => 'Include special version control directories (e.g. .svn).  Default is to exclude vcs files.',
      'exclude-files' => 'Exclude the files directory.',
      'exclude-sites' => 'Exclude all directories in "sites/" except for "sites/all".',
      'exclude-other-sites' => 'Exclude all directories in "sites/" except for "sites/all" and the site directory for the site being synced.  Note: if the site directory is different between the source and destination, use --exclude-sites followed by "drush rsync @from:%site @to:%site"',
      'exclude-paths' => 'List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).',
      'include-paths' => 'List of paths to include, seperated by : (Unix-based systems) or ; (Windows).',
      '{rsync-option-name}' => "Replace {rsync-option-name} with the rsync option (or option='value') that you would like to pass through to rsync. Examples include --delete, --exclude=*.sql, --filter='merge /etc/rsync/default.rules', etc. See the rsync documentation for a complete explanation of all the rsync options and values.",
      'rsync-version' => 'Set to the version of rsync you are using to signal Drush to go into backwards-compatibility mode when using very old versions of rsync. For example, --rsync-version=2.6.8 or earlier will cause Drush to avoid the --remove-source-files flag.',

    ),
    'strict-option-handling' => TRUE,
    'examples' => array(
      'drush rsync @dev @stage' => 'Rsync Drupal root from Drush alias dev to the alias stage. Either or both may be remote.',
      'drush rsync ./ @stage:%files/img' => 'Rsync all files in the current directory to the \'img\' directory in the file storage folder on the Drush alias stage.',
      'drush -s rsync @dev @stage --exclude=*.sql --delete' => "Simulate Rsync Drupal root from the Drush alias dev to the alias stage (one of which must be local), excluding all files that match the filter '*.sql' and delete all files on the destination that are no longer on the source.",
    ),
    'aliases' => array('rsync'),
    'topics' => array('docs-aliases'),
  );
  $items['drupal-directory'] = array(
    'description' => 'Return the filesystem path for modules/themes and other key folders.',
    'arguments' => array(
      'target' => 'A module/theme name, or special names like root, files, private, or an alias : path alias string such as @alias:%files. Defaults to root.',
    ),
    'options' => array(
      'component' => "The portion of the evaluated path to return.  Defaults to 'path'; 'name' returns the site alias of the target.",
      'local-only' => "Reject any target that specifies a remote site.",
    ),
    'examples' => array(
      'cd `drush dd devel`' => 'Navigate into the devel module directory',
      'cd `drush dd` ' => 'Navigate to the root of your Drupal site',
      'cd `drush dd files`' => 'Navigate to the files directory.',
      'drush dd @alias:%files' => 'Print the path to the files directory on the site @alias.',
      'edit `drush dd devel`/devel.module' => "Open devel module in your editor (customize 'edit' for your editor)",
    ),
    'aliases' => array('dd'),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );

  $items['batch-process'] = array(
    'description' => 'Process operations in the specified batch set',
    'hidden' => TRUE,
    'arguments' => array(
      'batch-id' => 'The batch id that will be processed.',
    ),
    'required-arguments' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
  );

  $items['updatedb-batch-process'] = array(
    'description' => 'Perform update functions',
    'hidden' => TRUE,
    'arguments' => array(
      'batch-id' => 'The batch id that will be processed',
    ),
    'required-arguments' => TRUE,
    // Drupal 7 needs DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, while Drupal 8 needs _FULL.
    // Therefore we bootstrap to _FULL in commands/core/drupal/update.inc.
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
  );
  $items['core-global-options'] = array(
    'description' => 'All global options',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'csv',
      'field-labels' => array('label' => 'Label', 'description' => 'Description'),
      'output-data-type' => 'format-table',
    ),
  );
  $items['core-quick-drupal'] = array(
    'description' => 'Download, install, serve and login to Drupal with minimal configuration and dependencies.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'aliases' => array('qd', 'cutie'),
    'arguments' => array(
      'site' => 'Short name for the site to be created - used as a directory name and as sqlite file name. Optional - if omitted timestamped "quick-drupal" directory will be used instead.',
      'projects' => 'A list of projects to download into the new site. If projects contain extensions (modules or themes) with the same name they will be enabled by default. See --enable option to control this behaviour further.',
    ),
    'examples' => array(
      'drush qd' => 'Download and install stable release of Drupal into a timestamped directory, start server, and open the site logged in as admin.',
      'drush qd --profile=minimal --cache --core=drupal-8.0.x --yes' => 'Fire up dev release of Drupal site with minimal install profile.',
      'drush qd testsite devel --server=:8081/admin --browser=firefox --cache --yes' => 'Fire up stable release (using the cache) of Drupal site called "testsite", download and enable devel module, start a server on port 8081 and open /admin in firefox.',
      'drush qd commercesite --core=commerce_kickstart --profile=commerce_kickstart --cache --yes --watchdog' => 'Download and install the "Commerce Kickstart" distribution/install profile, display watchdog messages on the server console.',
      'drush qd --makefile=mysite.make' => 'Create and install a site from a makefile.',
    ),
  );
  // Add in options/engines.
  drush_core_quick_drupal_options($items);
  // Add in topics for engines
  $items += drush_get_engine_topics();
  return $items;
}

/**
 * Command argument complete callback.
 *
 * @return
 *   Array of available profile names.
 */
function core_site_install_complete() {
  $max = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_ROOT);
  if ($max >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
    return array('values' => array_keys(drush_find_profiles(DRUPAL_ROOT)));
  }
}

/**
 * Command argument complete callback.
 *
 * @return
 *  Array of available site aliases.
 */
function core_core_rsync_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/**
 * @defgroup engines Engine types
 * @{
 */

/**
 * Implementation of hook_drush_engine_type_info().
 */
function core_drush_engine_type_info() {
  $info = array();
  $info['drupal'] = array();
  return $info;
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 */
function core_drush_engine_drupal() {
  $engines = array(
    'batch' => array(),
    'update'=> array(),
    'environment' => array(),
    'site_install' => array(),
    'pm' => array(),
    'cache' => array(),
    'image' => array(),
  );
  return $engines;
}

/**
 * @} End of "Engine types".
 */

/**
 * Command handler. Apply pending entity schema updates.
 */
function drush_core_entity_updates() {
  if (drush_get_context('DRUSH_SIMULATE')) {
    drush_log(dt('entity-updates command does not support --simulate option.'), LogLevel::OK);
  }

  drush_include_engine('drupal', 'update');
  if (entity_updates_main() === FALSE) {
    return FALSE;
  }

  drush_drupal_cache_clear_all();

  drush_log(dt('Finished performing updates.'), LogLevel::OK);
}

/**
 * Command handler. Execute update.php code from drush.
 */
function drush_core_updatedb() {
  if (drush_get_context('DRUSH_SIMULATE')) {
    drush_log(dt('updatedb command does not support --simulate option.'), LogLevel::OK);
    return TRUE;
  }

  drush_include_engine('drupal', 'update');
  $result = update_main();
  if ($result === FALSE) {
    return FALSE;
  }
  elseif ($result > 0) {
    // Clear all caches in a new process. We just performed major surgery.
    drush_drupal_cache_clear_all();

    drush_log(dt('Finished performing updates.'), LogLevel::OK);
  }
}

/**
 * Command handler. List pending DB updates.
 */
function drush_core_updatedb_status() {
  require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
  drupal_load_updates();
  drush_include_engine('drupal', 'update');
  list($pending, $start) = updatedb_status();
  if (empty($pending)) {
    drush_log(dt("No database updates required"), LogLevel::OK);
  }
  return $pending;
}

function _core_site_credentials($right_margin = 0) {
  // Leave some space on the right so that we can put the result into the
  // drush_log, which will again wordwrap the result.
  $original_columns = drush_get_context('DRUSH_COLUMNS', 80);
  drush_set_context('DRUSH_COLUMNS', $original_columns - $right_margin);
  $status_table = _core_site_status_table();
  $metadata = drush_get_command_format_metadata('core-status');
  $output = drush_format($status_table, $metadata, 'key-value');
  drush_set_context('DRUSH_COLUMNS', $original_columns);
  return $output;
}

function _core_path_aliases($project = '') {
  $paths = array();
  $site_wide = drush_drupal_sitewide_directory();
  $boot = drush_get_bootstrap_object();
  if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
    $paths['%root'] = $drupal_root;
    if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
      $paths['%site'] = $site_root;
      if (is_dir($modules_path = $boot->conf_path() . '/modules')) {
        $paths['%modules'] = $modules_path;
      }
      else {
        $paths['%modules'] = ltrim($site_wide . '/modules', '/');
      }
      if (is_dir($themes_path = $boot->conf_path() . '/themes')) {
        $paths['%themes'] = $themes_path;
      }
      else {
        $paths['%themes'] = ltrim($site_wide . '/themes', '/');
      }
      if (drush_drupal_major_version() >= 8 && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) {
        try {
          if (isset($GLOBALS['config_directories'])) {
            foreach ($GLOBALS['config_directories'] as $label => $unused) {
              $paths["%config-$label"] = config_get_config_directory($label);
            }
          }
        }
        catch (Exception $e) {
          // Nothing to do.
        }
      }

      if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
        $paths['%files'] = drush_file_get_public();
        if ($private_path = drush_file_get_private()) {
          $paths['%private'] = $private_path;
        }
      }

      if (function_exists('file_directory_temp')) {
        $paths['%temp'] = file_directory_temp();
      }
      // If the 'project' parameter was specified, then search
      // for a project (or a few) and add its path to the path list
      if (!empty($project)) {
        drush_include_engine('drupal', 'environment');
        $projects = array_merge(drush_get_modules(), drush_get_themes());
        foreach(explode(',', $project) as $target) {
          if (array_key_exists($target, $projects)) {
            $paths['%' . $target] = $drupal_root . '/' . _drush_extension_get_path($projects[$target]);
          }
        }
      }
    }
  }

  // Add in all of the global paths from $options['path-aliases']
  $paths = array_merge($paths, drush_get_option('path-aliases', array()));

  return $paths;
}

function _core_site_status_table($project = '') {
  $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
    $status_table['drupal-version'] = drush_drupal_version();
    $boot_object = drush_get_bootstrap_object();
    $conf_dir = $boot_object->conf_path();
    $settings_file = "$conf_dir/settings.php";
    $status_table['drupal-settings-file'] = file_exists($settings_file) ? $settings_file : '';
    if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
      $status_table['uri'] = drush_get_context('DRUSH_URI');
      try {
        $sql = drush_sql_get_class();
        $db_spec = $sql->db_spec();
        $status_table['db-driver'] = $db_spec['driver'];
        if (!empty($db_spec['unix_socket'])) {
          $status_table['db-socket'] = $db_spec['unix_socket'];
        }
        elseif (isset($db_spec['host'])) {
          $status_table['db-hostname'] = $db_spec['host'];
        }
        $status_table['db-username'] = isset($db_spec['username']) ? $db_spec['username'] : NULL;
        $status_table['db-password'] = isset($db_spec['password']) ? $db_spec['password'] : NULL;
        $status_table['db-name'] = isset($db_spec['database']) ? $db_spec['database'] : NULL;
        $status_table['db-port'] = isset($db_spec['port']) ? $db_spec['port'] : NULL;
        if ($phase > DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION) {
          $status_table['install-profile'] = $boot_object->get_profile();
          if ($phase > DRUSH_BOOTSTRAP_DRUPAL_DATABASE) {
            $status_table['db-status'] = dt('Connected');
            if ($phase > DRUSH_BOOTSTRAP_DRUPAL_FULL) {
              $status_table['bootstrap'] = dt('Successful');
              if ($phase == DRUSH_BOOTSTRAP_DRUPAL_LOGIN) {
                $status_table['user'] = drush_user_get_class()->getCurrentUserAsSingle()->getUsername();
              }
            }
          }
        }
      }
      catch (Exception $e) {
        // Don't worry be happy.
      }
    }
    if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      $status_table['theme'] = drush_theme_get_default();
      $status_table['admin-theme'] = drush_theme_get_admin();
    }
  }
  if ($php_bin = drush_get_option('php')) {
    $status_table['php-bin'] = $php_bin;
  }
  $status_table['php-os'] = PHP_OS;
  if ($php_ini_files = _drush_core_config_php_ini_files()) {
    $status_table['php-conf'] = $php_ini_files;
  }
  $status_table['drush-script'] = DRUSH_COMMAND;
  $status_table['drush-version'] = DRUSH_VERSION;
  $status_table['drush-temp'] = drush_find_tmp();
  $status_table['drush-conf'] = drush_flatten_array(drush_get_context_options('context-path', ''));
  $alias_files = _drush_sitealias_find_alias_files();
  $status_table['drush-alias-files'] = $alias_files;

  $paths = _core_path_aliases($project);
  if (!empty($paths)) {
    foreach ($paths as $target => $one_path) {
      $name = $target;
      if (substr($name,0,1) == '%') {
        $name = substr($name,1);
      }
      $status_table[$name] = $one_path;
    }
  }

  // Store the paths into the '%paths' index; this will be
  // used by other code, but will not be included in the output
  // of the drush status command.
  $status_table['%paths'] = $paths;

  return $status_table;
}

// Adjust the status output for any non-pipe output format
function _drush_core_status_format_table_data($output, $metadata) {
  if (drush_get_option('full', FALSE) == FALSE) {
    // Hide any key that begins with a %
    foreach ($output as $key => $value) {
      if ($key[0] == '%') {
        unset($output[$key]);
      }
    }
    // Hide 'Modules path' and 'Themes path' as well
    unset($output['modules']);
    unset($output['themes']);
    // Shorten the list of alias files if there are too many
    if (isset($output['drush-alias-files']) && count($output['drush-alias-files']) > 24) {
      $msg = dt("\nThere are !count more alias files. Run with --full to see the full list.", array('!count' => count($output['drush-alias-files']) - 1));
      $output['drush-alias-files'] = array($output['drush-alias-files'][0] , $msg);
    }
    if (isset($output['drupal-settings-file']) && empty($output['drupal-settings-file'])) {
      $output['drupal-settings-file'] = dt('MISSING');
    }
  }
  return $output;
}

/**
 * Command callback. Runs all cron hooks.
 */
function drush_core_cron() {
  if (drush_drupal_major_version() < 8) {
    $result = drupal_cron_run();
  }
  else {
    $result = \Drupal::service('cron')->run();
  }
  if ($result) {
    drush_log(dt('Cron run successful.'), LogLevel::SUCCESS);
  }
  else {
    return drush_set_error('DRUSH_CRON_FAILED', dt('Cron run failed.'));
  }
}

/**
 * Command callback. Edit drushrc and alias files.
 */
function drush_core_config($filter = NULL) {
  $all = drush_core_config_load();

  // Apply any filter that was supplied.
  if ($filter) {
    foreach ($all as $key => $file) {
      if (strpos($file, $filter) === FALSE) {
        unset($all[$key]);
      }
    }
  }
  $all = drush_map_assoc(array_values($all));

  $exec = drush_get_editor();
  if (count($all) == 1) {
    $filepath = current($all);
  }
  else {
    $choice = drush_choice($all, 'Enter a number to choose which file to edit.', '!key');
    if (!$choice) {
      return drush_user_abort();
    }
    $filepath = $all[$choice];
  }
  return drush_shell_exec_interactive($exec, $filepath, $filepath);
}

/**
 * Command argument complete callback.
 *
 * @return
 *   Array of available configuration files for editing.
 */
function core_core_config_complete() {
  return array('values' => drush_core_config_load(FALSE));
}

function drush_core_config_load($headers = TRUE) {
  $php_header = $php = $rcs_header = $rcs = $aliases_header = $aliases = $drupal_header = $drupal = array();
  $php = _drush_core_config_php_ini_files();
  if (!empty($php)) {
    if ($headers) {
      $php_header = array('phpini' => '-- PHP ini files --');
    }
  }

  $bash = _drush_core_config_bash_files();
  if (!empty($bash)) {
    if ($headers) {
      $bash_header = array('bash' => '-- Bash files --');
    }
  }

  drush_sitealias_load_all();
  if ($rcs = drush_get_context_options('context-path', TRUE)) {
    if ($headers) {
      $rcs_header = array('drushrc' => '-- Drushrc --');
    }
  }
  if ($aliases = drush_get_context('drush-alias-files')) {
    if ($headers) {
      $aliases_header = array('aliases' => '-- Aliases --');
    }
  }
  if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
    $drupal[] = realpath($site_root . '/settings.php');
    if (file_exists($site_root . '/settings.local.php')) {
      $drupal[] = realpath($site_root . '/settings.local.php');
    }
    $drupal[] = realpath(DRUPAL_ROOT . '/.htaccess');
    if ($headers) {
      $drupal_header = array('drupal' => '-- Drupal --');
    }
  }
  return array_merge($php_header, $php, $bash_header, $bash, $rcs_header, $rcs, $aliases_header, $aliases, $drupal_header, $drupal);
}

function _drush_core_config_php_ini_files() {
  $ini_files = array();
  $ini_files[] = php_ini_loaded_file();
  if ($drush_ini = getenv('DRUSH_INI')) {
    if (file_exists($drush_ini)) {
      $ini_files[] = $drush_ini;
    }
  }
  foreach (array(DRUSH_BASE_PATH, '/etc/drush', drush_server_home() . '/.drush') as $ini_dir) {
    if (file_exists($ini_dir . "/drush.ini")) {
      $ini_files[] = realpath($ini_dir . "/drush.ini");
    }
  }
  return array_unique($ini_files);
}

function _drush_core_config_bash_files() {
  $bash_files = array();
  $home = drush_server_home();
  if ($bashrc = drush_init_find_bashrc($home)) {
    $bash_files[] = $bashrc;
  }
  $prompt = $home . '/.drush/drush.prompt.sh';
  if (file_exists($prompt)) {
    $bash_files[] = $prompt;
  }
  return $bash_files;
}

/**
 * Command callback. Provides information from the 'Status Reports' admin page.
 */
function drush_core_requirements() {
  include_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
  $severities = array(
    REQUIREMENT_INFO => t('Info'),
    REQUIREMENT_OK => t('OK'),
    REQUIREMENT_WARNING => t('Warning'),
    REQUIREMENT_ERROR => t('Error'),
  );

  drupal_load_updates();

  drush_include_engine('drupal', 'environment');
  $requirements = drush_module_invoke_all('requirements', 'runtime');
  // If a module uses "$requirements[] = " instead of
  // "$requirements['label'] = ", then build a label from
  // the title.
  foreach($requirements as $key => $info) {
    if (is_numeric($key)) {
      unset($requirements[$key]);
      $new_key = strtolower(str_replace(' ', '_', $info['title']));
      $requirements[$new_key] = $info;
    }
  }
  $ignore_requirements = drush_get_option_list('ignore');
  foreach ($ignore_requirements as $ignore) {
    unset($requirements[$ignore]);
  }
  ksort($requirements);

  $min_severity = drush_get_option('severity', -1);
  foreach($requirements as $key => $info) {
    $severity = array_key_exists('severity', $info) ? $info['severity'] : -1;
    $requirements[$key]['sid'] = $severity;
    $requirements[$key]['severity'] = $severities[$severity];
    if ($severity < $min_severity) {
      unset($requirements[$key]);
    }
    if (isset($requirements[$key]['description'])) {
      $requirements[$key]['description'] = drush_render($requirements[$key]['description']);
    }
  }
  return $requirements;
}

/**
 * Command callback. Provides a birds-eye view of the current Drupal
 * installation.
 */
function drush_core_status() {
  $status_table = _core_site_status_table(drush_get_option('project',''));
  // If args are specified, filter out any entry that is not named
  // (in other words, only show lines named by one of the arg values)
  $args = func_get_args();
  if (!empty($args)) {
    $field_list = $args;
    $metadata = drush_get_command_format_metadata('core-status');
    foreach ($metadata['field-labels'] as $field_key => $field_label) {
      if (_drush_core_is_named_in_array($field_label, $args)) {
        $field_list[] = $field_key;
      }
    }
    foreach ($status_table as $key => $value) {
      if (!_drush_core_is_named_in_array($key, $field_list)) {
        unset($status_table[$key]);
      }
    }
  }
  return $status_table;
}

// Command callback. Show all global options. Exposed via topic command.
function drush_core_global_options() {
  drush_print(dt('These options are applicable to most drush commands. Most options can be disabled by using --no-option (i.e. --no-debug to disable --debug.)'));
  drush_print();
  $fake = drush_global_options_command(FALSE);
  return drush_format_help_section($fake, 'options');
}

function _drush_core_is_named_in_array($key, $the_array) {
  $is_named = FALSE;

  $simplified_key = str_replace(array(' ', '_', '-'), array('', '', ''), $key);

  foreach ($the_array as $name) {
    if (stristr($simplified_key, str_replace(array(' ', '_', '-'), array('', '', ''), $name))) {
      $is_named = TRUE;
    }
  }

  return $is_named;
}

/**
 * Callback for core-quick-drupal command.
 */
function drush_core_quick_drupal() {
  $requests = FALSE;
  $make_projects = array();
  $args = func_get_args();
  $name = drush_get_option('use-name');
  drush_set_option('backend', TRUE);
  drush_set_option('strict', FALSE); // We fail option validation because do so much internal drush_invoke().
  $makefile = drush_get_option('makefile');
  $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  if (drush_get_option('use-existing', ($root != FALSE))) {
    if (!$root) {
      return drush_set_error('QUICK_DRUPAL_NO_ROOT_SPECIFIED', 'Must specify site with --root when using --use-existing.');
    }
    // If a --db-url was not explicitly provided, and if there is already
    // a settings.php file provided, then use the db-url defined inside it.
    if (!drush_get_option('db-url', FALSE)) {
      $values = drush_invoke_process('@self', 'site-alias', array('@self'), array('with-db-url' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE));
      if (!empty($values['object']['self']['db-url'])) {
        drush_set_option('db-url', $values['object']['self']['db-url']);
      }
    }
    if (empty($name)) {
      $name = basename($root);
    }
    $base = dirname($root);
  }
  else {
    if (!empty($args) && empty($name)) {
      $name = array_shift($args);
    }
    if (empty($name)) {
      $name = 'quick-drupal-' . gmdate('YmdHis', $_SERVER['REQUEST_TIME']);
    }
    $root = drush_get_option('root', FALSE);
    $core = drush_get_option('core', 'drupal');
    $project_rename = $core;
    if ($root) {
      $base = dirname($root);
      $project_rename = basename($root);
    }
    else {
      $base = getcwd() . '/' . $name;
      $root = $base . '/' . $core;
    }
    if (!empty($makefile)) {
      // Invoke 'drush make $makefile'.
      $result = drush_invoke_process('@none', 'make', array($makefile, $root), array('core-quick-drupal' => TRUE));
      if ($result['error_status'] != 0) {
        return drush_set_error('DRUSH_QD_MAKE', 'Could not make; aborting.');
      }
      $make_projects = array_diff(array_keys($result['object']['projects']), array('drupal'));
    }
    else {
      drush_mkdir($base);
      drush_set_option('destination', $base);
      drush_set_option('drupal-project-rename', $project_rename);
      if (drush_invoke('pm-download', array($core)) === FALSE) {
        return drush_set_error('QUICK_DRUPAL_CORE_DOWNLOAD_FAIL', 'Drupal core download/extract failed.');
      }
    }
  }
  if (!drush_get_option('db-url', FALSE)) {
    drush_set_option('db-url', 'sqlite://sites/' . strtolower(drush_get_option('sites-subdir', 'default')) . "/files/$name.sqlite");
  }
  // We have just created a site root where one did not exist before.
  // We therefore must manually reset DRUSH_SELECTED_DRUPAL_ROOT to
  // our new root, and force a bootstrap to DRUSH_BOOTSTRAP_DRUPAL_ROOT.
  drush_set_context('DRUSH_SELECTED_DRUPAL_ROOT', $root);
  if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_ROOT)) {
    return drush_set_error('QUICK_DRUPAL_ROOT_LOCATE_FAIL', 'Unable to locate Drupal root directory.');
  }
  if (!empty($args)) {
    $requests = pm_parse_arguments($args, FALSE);
  }
  if ($requests) {
    // Unset --destination, so that downloads go to the site directories.
    drush_unset_option('destination');
    if (drush_invoke('pm-download', $requests) === FALSE) {
      return drush_set_error('QUICK_DRUPAL_PROJECT_DOWNLOAD_FAIL', 'Project download/extract failed.');
    }
  }
  drush_invoke('site-install', array(drush_get_option('profile')));
  // Log in with the admin user.
  // TODO: If site-install is given a sites-subdir other than 'default',
  // then it will bootstrap to DRUSH_BOOTSTRAP_DRUPAL_SITE get the installer
  // to recognize the desired site directory. This somehow interferes
  // with our desire to bootstrap to DRUSH_BOOTSTRAP_DRUPAL_LOGIN here.
  // We could do the last few steps in a new process if uri is not 'default'.
  drush_set_option('user', '1');
  if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_LOGIN)) {
    return drush_set_error('QUICK_DRUPAL_INSTALL_FAIL', 'Drupal core install failed.');
  }
  $enable = array_merge(pm_parse_arguments(drush_get_option('enable', $requests)), $make_projects);
  if (!empty($enable)) {
    if (drush_invoke('pm-enable', $enable) === FALSE) {
     return drush_set_error('QUICK_DRUPAL_PROJECT_ENABLE_FAIL', 'Project enable failed.');
    }
  }
  $server = drush_get_option('server', '/');
  if ($server) {
    $server_uri = runserver_uri($server);
    _drush_core_qd_cache_uri($server_uri);
  }
  if (!drush_get_option('no-server', FALSE)) {
    if ($server) {
      // Current CLI user is also the web server user, which is for development
      // only. Hence we can safely make the site directory writable. This makes
      // it easier to delete and edit settings.php.
      $boot = drush_get_bootstrap_object();
      @chmod($boot->conf_path(), 0700);
      drush_invoke_process(array('root' => $root, 'uri' => $server_uri), 'runserver', array($server));
    }
  }
  else {
    drush_print(dt('Login URL: ') . drush_invoke('user-login'));
  }
}

// Write a drushrc.php to cache the server information for future Drush calls
function _drush_core_qd_cache_uri($uri) {
  $server = $uri['host'];
  if (!empty($uri["port"])) {
    $server .= ':' . $uri["port"];
  }
  $dir = DRUPAL_ROOT . '/drush';
  $target_file = $dir . '/drushrc.php';
  drush_log(dt("Caching 'uri' !uri in !target", array('!uri' => $server, '!target' => $target_file)), LogLevel::OK);
  $create_file = TRUE;
  if (file_exists($target_file)) {
    // Don't bother to ask with --use-existing; just
    // continue.
    if (drush_get_option('use-existing', FALSE)) {
      $create_file = FALSE;
    }
    else {
      $create_file = drush_confirm(dt('!target already exists. Overwrite?', array('!target' => $target_file)));
    }
  }
  $content = <<<EOT
<?php

// Inserted by `drush quick-drupal`.  This allows `drush user-login`
// and similar commands to work seemlessly.  Remove if using
// Drupal multisite feature.
\$options['uri'] = "$server";
EOT;
  if ($create_file) {
    drush_mkdir($dir);
    file_put_contents($target_file, $content);
  }
}

/**
 * Include options and engines for core-quick-drupal command, aggregated from
 * other command options that are available. We prefix option descriptons,
 * to make the long list more navigable.
 *
 * @param $items
 *   The core commandfile command array, by reference. Used to include
 *   site-install options and add options and engines for core-quick-drupal.
 */
function drush_core_quick_drupal_options(&$items) {
  $options = array(
    'core' => 'Drupal core to download. Defaults to "drupal" (latest stable version).',
    'use-existing' => 'Use an existing Drupal root, specified with --root. Overrides --core. Defaults to true when run from an existing site.',
    'profile' => 'The install profile to use. Defaults to standard.',
    'enable' => 'Specific extensions (modules or themes) to enable. By default, extensions with the same name as requested projects will be enabled automatically.',
    'server' => 'Host IP address and port number to bind to and path to open in web browser (hyphen to clear a default path), all elements optional. See runserver examples for shorthand.',
    'no-server' => 'Avoid starting runserver (and browser) for the created Drupal site.',
    'browser' => 'Optional name of a browser to open site in. If omitted the OS default browser will be used. Set --no-browser to disable.',
    'use-name' => array('hidden' => TRUE, 'description' => 'Overrides "name" argument.'),
    'makefile' => array('description' => 'Makefile to use. Makefile must specify which version of Drupal core to build.', 'example-value' => 'mysite.make', 'value' => 'optional'),
    'root' => array('description' => 'Path to Drupal root.', 'example-value' => '/path/to/root', 'value' => 'optional'),
  );
  $pm = pm_drush_command();
  foreach ($pm['pm-download']['options'] as $option => $description) {
    if (is_array($description)) {
      $description = $description['description'];
    }
    $options[$option] = 'Download option: ' . $description;
  }
  // Unset a few options that are not usable here, as we control them ourselves
  // or they are otherwise implied by the environment.
  unset($options['destination']);
  unset($options['drupal-project-rename']);
  unset($options['default-major']);
  unset($options['use-site-dir']);
  $si = site_install_drush_command();
  foreach ($si['site-install']['options'] as $option => $description) {
    if (is_array($description)) {
      $description = $description['description'];
    }
    $options[$option] = 'Site install option: ' . $description;
  }
  unset($options['sites-subdir']);
  $runserver = runserver_drush_command();
  foreach ($runserver['runserver']['options'] as $option => $description) {
    $options[$option] = 'Runserver option: ' . $description;
  }
  unset($options['user']);
  $items['core-quick-drupal']['options'] = $options;
  $items['core-quick-drupal']['engines'] = $pm['pm-download']['engines'];
}

/**
 * Command callback. Runs "naked" php scripts
 * and drush "shebang" scripts ("#!/usr/bin/env drush").
 */
function drush_core_php_script() {
  $found = FALSE;
  $script = NULL;
  if ($args = func_get_args()) {
    $script = $args[0];
  }

  if ($script == '-') {
    return eval(stream_get_contents(STDIN));
  }
  elseif (file_exists($script)) {
    $found = $script;
  }
  else {
    // Array of paths to search for scripts
    $searchpath['DIR'] = dirname(__FILE__);
    $searchpath['cwd'] = drush_cwd();

    // Additional script paths, specified by 'script-path' option
    if ($script_path = drush_get_option('script-path', FALSE)) {
      foreach (explode(PATH_SEPARATOR, $script_path) as $path) {
        $searchpath[] = $path;
      }
    }
    drush_log(dt('Searching for scripts in ') . implode(',', $searchpath), LogLevel::DEBUG);

    if (!isset($script)) {
      // List all available scripts.
      $all = array();
      foreach($searchpath as $key => $path) {
        $recurse = !(($key == 'cwd') || ($path == '/'));
        $all = array_merge( $all , array_keys(drush_scan_directory($path, '/\.php$/', array('.', '..', 'CVS'), NULL, $recurse)) );
      }
      drush_print(implode("\n", $all));
    }
    else {
      // Execute the specified script.
      foreach($searchpath as $path) {
        $script_filename = $path . '/' . $script;
        if (file_exists($script_filename . '.php')) {
          $script_filename .= '.php';
        }
        if (file_exists($script_filename)) {
          $found = $script_filename;
          break;
        }
        $all[] = $script_filename;
      }
      if (!$found) {
        return drush_set_error('DRUSH_TARGET_NOT_FOUND', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all))));
      }
    }
  }

  if ($found) {
    // Set the DRUSH_SHIFT_SKIP to two; this will cause
    // drush_shift to skip the next two arguments the next
    // time it is called.  This allows scripts to get all
    // arguments, including the 'php-script' and script
    // pathname, via drush_get_arguments(), or it can process
    // just the arguments that are relevant using drush_shift().
    drush_set_context('DRUSH_SHIFT_SKIP', 2);
    if (_drush_core_eval_shebang_script($found) === FALSE) {
      return include($found);
    }
  }
}

function drush_core_php_eval($php) {
  return eval($php . ';');
}

/**
 * Evaluate a script that begins with #!drush php-script
 */
function _drush_core_eval_shebang_script($script_filename) {
  $found = FALSE;
  $fp = fopen($script_filename, "r");
  if ($fp !== FALSE) {
    $line = fgets($fp);
    if (_drush_is_drush_shebang_line($line)) {
      $first_script_line = '';
      while ($line = fgets($fp)) {
        $line = trim($line);
        if ($line == '<?php') {
          $found = TRUE;
          break;
        }
        elseif (!empty($line)) {
          $first_script_line = $line . "\n";
          break;
        }
      }
      $script = stream_get_contents($fp);
      // Pop off the first two arguments, the
      // command (php-script) and the path to
      // the script to execute, as a service
      // to the script.
      eval($first_script_line . $script);
      $found = TRUE;
    }
    fclose($fp);
  }
  return $found;
}

/**
 * Command callback. Process sets from the specified batch.
 *
 * This is the default batch processor that will be used if the $command parameter
 * to drush_backend_batch_process() has not been specified.
 */
function drush_core_batch_process($id) {
  drush_batch_command($id);
}

/**
 * Command callback. Process outstanding updates during updatedb.
 *
 * This is a batch processing command that makes use of the drush_backend_invoke
 * api.
 *
 * This command includes the version specific update engine, which correctly
 * initialises the environment to be able to successfully handle minor and major
 * upgrades.
 */
function drush_core_updatedb_batch_process($id) {
  drush_include_engine('drupal', 'update');
  _update_batch_command($id);
}

/**
 * Given a target (e.g. @site:%modules), return the evaluated directory path.
 *
 * @param $target
 *   The target to evaluate.  Can be @site or /path or @site:path
 *   or @site:%pathalias, etc. (just like rsync parameters)
 * @param $component
 *   The portion of the evaluated path to return.  Possible values:
 *   'path' - the full path to the target (default)
 *   'name' - the name of the site from the path (e.g. @site1)
 *   'user-path' - the part after the ':' (e.g. %modules)
 *   'root' & 'uri' - the Drupal root and URI of the site from the path
 *   'path-component' - The ':' and the path
 */
function _drush_core_directory($target = 'root', $component = 'path', $local_only = FALSE) {
  // Normalize to a sitealias in the target.
  $normalized_target = $target;
  if (strpos($target, ':') === FALSE) {
    if (substr($target, 0, 1) != '@') {
      // drush_sitealias_evaluate_path() requires bootstrap to database.
      if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) {
        return drush_set_error('DRUPAL_SITE_NOT_FOUND', dt('You need to specify an alias or run this command within a drupal site.'));
      }
      $normalized_target = '@self:';
      if (substr($target, 0, 1) != '%') {
        $normalized_target .= '%';
      }
      $normalized_target .= $target;
    }
  }
  $additional_options = array();
  $values = drush_sitealias_evaluate_path($normalized_target, $additional_options, $local_only);
  if (isset($values[$component])) {
    // Hurray, we found the destination.
    return $values[$component];
  }
}

/**
 * Command callback.
 */
function drush_core_drupal_directory($target = 'root') {
  $path = _drush_core_directory($target, drush_get_option('component', 'path'), drush_get_option('local-only', FALSE));

  // If _drush_core_directory is working right, it will turn
  // %blah into the path to the item referred to by the key 'blah'.
  // If there is no such key, then no replacement is done.  In the
  // case of the dd command, we will consider it an error if
  // any keys are -not- replaced in _drush_core_directory.
  if ($path && (strpos($path, '%') === FALSE)) {
    return $path;
  }
  else {
    return drush_set_error('DRUSH_TARGET_NOT_FOUND', dt("Target '!target' not found.", array('!target' => $target)));
  }
}

/**
 * Called for `drush version` or `drush --version`
 */
function drush_core_version() {
  return DRUSH_VERSION;
}

/**
 * Command callback. Execute specified shell code. Often used by shell aliases
 * that start with !.
 */
function drush_core_execute() {
  $result = TRUE;
  $escape = drush_get_option('escape', TRUE);
  // Get all of the args and options that appear after the command name.
  $args = drush_get_original_cli_args_and_options();
  if ($escape) {
    for ($x = 0; $x < count($args); $x++) {
      // escape all args except for command separators.
      if (!in_array($args[$x], array('&&', '||', ';'))) {
        $args[$x] = drush_escapeshellarg($args[$x]);
      }
    }
  }
  $cmd = implode(' ', $args);
  // If we selected a Drupal site, then cwd to the site root prior to exec
  $cwd = FALSE;
  if ($selected_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT')) {
    if (is_dir($selected_root)) {
      $cwd = getcwd();
      drush_op('chdir', $selected_root);
    }
  }
  if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
    $site = drush_sitealias_get_record($alias);
    if (!empty($site['site-list'])) {
      $sites = drush_sitealias_resolve_sitelist($site);
      foreach ($sites as $site_name => $site_spec) {
        $result = _drush_core_execute_cmd($site_spec, $cmd);
        if (!$result) {
          break;
        }
      }
    }
    else {
      $result = _drush_core_execute_cmd($site, $cmd);
    }
  }
  else {
    // Must be a local command.
    $result = (drush_shell_proc_open($cmd) == 0);
  }
  // Restore the cwd if we changed it
  if ($cwd) {
    drush_op('chdir', $selected_root);
  }
  if (!$result) {
    return drush_set_error('CORE_EXECUTE_FAILED', dt("Command !command failed.", array('!command' => $cmd)));
  }
  return $result;
}

function drush_core_twig_compile() {
  require_once DRUSH_DRUPAL_CORE . "/themes/engines/twig/twig.engine";
  // Scan all enabled modules and themes.
  // @todo refactor since \Drush\Boot\DrupalBoot::commandfile_searchpaths is similar.
  $ignored_modules = drush_get_option_list('ignored-modules', array());
  $cid = drush_cid_install_profile();
  if ($cached = drush_cache_get($cid)) {
    $ignored_modules[] = $cached->data;
  }
  foreach (array_diff(drush_module_list(), $ignored_modules) as $module) {
    $searchpaths[] = drupal_get_path('module', $module);
  }

  $themes = drush_theme_list();
  foreach ($themes as $name => $theme) {
    $searchpaths[] = $theme->getPath();
  }

  foreach ($searchpaths as $searchpath) {
    foreach ($file = drush_scan_directory($searchpath, '/\.html.twig/', array('tests')) as $file) {
      $relative = str_replace(drush_get_context('DRUSH_DRUPAL_ROOT'). '/', '', $file->filename);
      // @todo Dynamically disable twig debugging since there is no good info there anyway.
      twig_render_template($relative, array('theme_hook_original' => ''));
      drush_log(dt('Compiled twig template !path', array('!path' => $relative)), LogLevel::NOTICE);
    }
  }
}

/**
 * Helper function for drush_core_execute: run one shell command
 */
function _drush_core_execute_cmd($site, $cmd) {
  if (!empty($site['remote-host'])) {
    // Remote, so execute an ssh command with a bash fragment at the end.
    $exec = drush_shell_proc_build($site, $cmd, TRUE);
    return (drush_shell_proc_open($exec) == 0);
  }
  elseif (!empty($site['root']) && is_dir($site['root'])) {
    return (drush_shell_proc_open('cd ' . drush_escapeshellarg($site['root']) . ' && ' . $cmd) == 0);
  }
  return (drush_shell_proc_open($cmd) == 0);
}
<?php

/**
 * @file
 *   Documentation commands providing various topics.
 */

/**
 * Implementation of hook_drush_help().
 */
function docs_drush_help($section) {
  switch ($section) {
    case 'meta:docs:title':
      return dt('Documentation commands');
    case 'meta:docs:summary':
      return dt('Show information on various drush topics.');
  }
}

/**
 * Implementation of hook_drush_command().
 *
 * @return
 *   An associative array describing your command(s).
 */
function docs_drush_command() {
  $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);

  //
  // Topic commands.
  // Any commandfile may add topics.
  // Set 'topic' => TRUE to indicate the command is a topic (REQUIRED)
  // Begin the topic name with the name of the commandfile (just like
  // any other command).
  //
  $items['docs-readme'] = array(
    'description' => 'README.md',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/README.md'),
  );
  $items['docs-bisect'] = array(
    'description' => 'git bisect and Drush may be used together to find the commit an error was introduced in.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/git-bisect.example.sh'),
  );
  $items['docs-bashrc'] = array(
    'description' => 'Bashrc customization examples for Drush.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/example.bashrc'),
  );
  $items['docs-configuration'] = array(
    'description' => 'Configuration overview with examples from example.drushrc.php.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'),
  );
  $items['docs-config-exporting'] = array(
    'description' => 'Drupal configuration export instructions, including customizing configuration by environment.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/config-exporting.md'),
  );
  $items['docs-aliases'] = array(
    'description' => 'Site aliases overview on creating your own aliases for commonly used Drupal sites with examples from example.aliases.drushrc.php.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'),
  );
  $items['docs-ini-files'] = array(
    'description' => 'php.ini or drush.ini configuration to set PHP values for use with Drush.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/example.drush.ini'),
  );
  $items['docs-bastion'] = array(
    'description' => 'Bastion server configuration: remotely operate on a Drupal sites behind a firewall.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/bastion.md'),
  );
  $items['docs-bootstrap'] = array(
    'description' => 'Bootstrap explanation: how Drush starts up and prepares the Drupal environment for use with the command.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/bootstrap.md'),
  );
  $items['docs-cron'] = array(
    'description' => 'Crontab instructions for running your Drupal cron tasks via `drush cron`.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/cron.md'),
  );
  $items['docs-scripts'] = array(
    'description' => 'Shell script overview on writing simple sequences of Drush statements.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/shellscripts.md'),
  );
  $items['docs-shell-aliases'] = array(
    'description' => 'Shell alias overview on creating your own aliases for commonly used Drush commands.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/shellaliases.md'),
  );
  $items['docs-commands'] = array(
    'description' => 'Drush command instructions on creating your own Drush commands.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/commands.md'),
  );
  $items['docs-errorcodes'] = array(
    'description' => 'Error code list containing all identifiers used with drush_set_error.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );
  $items['docs-api'] = array(
    'description' => 'Drush API',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/drush.api.php'),
  );
  $items['docs-context'] = array(
    'description' => 'Contexts overview explaining how Drush manages command line options and configuration file settings.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/context.md'),
  );
  $items['docs-examplescript'] = array(
    'description' => 'Example Drush script.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/helloworld.script'),
  );
  $items['docs-examplecommand'] = array(
    'description' => 'Example Drush command file.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'),
  );
  $items['docs-example-sync-extension'] = array(
    'description' => 'Example Drush commandfile that extends sql-sync to enable development modules in the post-sync hook.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/sync_enable.drush.inc'),
  );
  $items['docs-example-sync-via-http'] = array(
    'description' => 'Example Drush commandfile that extends sql-sync to allow transfer of the sql dump file via http rather than ssh and rsync.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/sync_via_http.drush.inc'),
  );
  $items['docs-policy'] = array(
    'description' => 'Example policy file.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'),
  );
  $items['docs-strict-options'] = array(
    'description' => 'Strict option handling, and how commands that use it differ from regular Drush commands.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/strict-options.md'),
  );
  return $items;
}

/**
 * docs-errorcodes command.  Print a list of all error codes
 * that can be found.
 */
function drush_docs_errorcodes() {
  $header = <<<EOD
==== Drush Error Codes ====

Drush error codes are alphanumeric constants that represent an unrecoverable error condition that may arise during the execution of some command.  They are set by the following function:

  return drush_set_error('DRUSH_ERROR_CODE', dt('Error message.'));

In general, any drush command that calls drush_set_error is expected to also return FALSE as its function result.  The drush_set_error function returns FALSE to make it easy to exit with an error code.  Error codes are returned as part of the drush backend invoke process, which is used by drush API functions such as drush_invoke_process.  An example of how to test for a specific error code is shown below:

  \$result = drush_invoke_process('@self', 'some-command');
  if (array_key_exists('DRUSH_ERROR_CODE', \$result['error_log'])) {
    // handle ocurrances of DRUSH_ERROR_CODE here
  }

Some of the available drush error codes are listed in the table below.


EOD;

  // Find all of the files that we will search for error messages.
  // Start with all of the commandfiles.
  $commandfiles = drush_commandfile_list();
  $files = array_flip($commandfiles);
  // In addition to the commandfiles, we will also look for files
  // that drush will load when executing a command (e.g
  // updatecode.pm.inc)
  $commands = drush_get_commands();
  foreach ($commands as $command_name => $command) {
    $files = array_merge($files, drush_command_get_includes($command_name));
  }
  // We will also search through all of the .inc files in the
  // drush includes directory
  $drush_include_files = drush_scan_directory(DRUSH_BASE_PATH . '/includes', '/.*\.inc$/', array('.', '..', 'CVS'), 0, FALSE);
  foreach ($drush_include_files as $filename => $info) {
    $files[$filename] = 'include';
  }

  // Extract error messages from all command files
  $error_list = array();
  foreach ($files as $file => $commandfile) {
    _drush_docs_find_set_error_calls($error_list, $file, $commandfile);
  }
  // Order error messages alphabetically by key
  ksort($error_list);
  // Convert to a table
  $data = array();
  foreach ($error_list as $error_code => $error_messages) {
    $data[] = array($error_code, '-', implode("\n", $error_messages));
  }

  $tmpfile = drush_tempnam('drush-errorcodes.');
  file_put_contents($tmpfile, $header);
  $handle = fopen($tmpfile, 'a');
  drush_print_table($data, FALSE, array(0 => 35), $handle);
  fclose($handle);
  drush_print_file($tmpfile);
}

/**
 * Search through a php source file looking for calls to
 * the function drush_set_error.  If found, and if the
 * first parameter is an uppercase alphanumeric identifier,
 * then record the error code and the error message in our table.
 */
function _drush_docs_find_set_error_calls(&$error_list, $filename, $shortname) {
  $lines = file($filename);
  foreach ($lines as $line) {
    $matches = array();
    // Find the error code after the drush_set_error call.  The error code
    // should consist of uppercase letters and underscores only (numbers thrown in just in case)
    $match_result = preg_match("/.*drush_set_error[^'\"]['\"]([A-Z0-9_]*)['\"][^,]*,[^'\"]*(['\"])/", $line, $matches);
    if ($match_result) {
      $error_code = $matches[1];
      $quote_char = $matches[2];
      $error_message = "";
      $message_start = strlen($matches[0]) - 1;

      // Regex adapted from http://stackoverflow.com/questions/1824325/regex-expression-for-escaped-quoted-string-wont-work-in-phps-preg-match-allif ($quote_char == '"') {
      if ($quote_char == '"') {
        $regex = '/"((?:[^\\\]*?(?:\\\")?)*?)"/';
      }
      else {
        $regex = "/'((?:[^\\\]*?(?:\\\')?)*?)'/";
      }
      $match_result = preg_match($regex, $line, $matches, 0, $message_start);

      if ($match_result) {
        $error_message = $matches[1];
      }
      $error_list[$error_code] = array_key_exists($error_code, $error_list) ? array_merge($error_list[$error_code], array($error_message)) : array($error_message);
    }
  }
}
<?php
/**
 * @file
 *   Drupal 7 engine for the Batch API
 */

use Drush\Log\LogLevel;

/**
 * Main loop for the Drush batch API.
 *
 * Saves a record of the batch into the database, and progressively call $command to
 * process the operations.
 *
 * @param command
 *    The command to call to process the batch.
 *
 */
function _drush_backend_batch_process($command = 'batch-process', $args, $options) {
  $batch =& batch_get();

  if (isset($batch)) {
    $process_info = array(
      'current_set' => 0,
    );
    $batch += $process_info;

    // The batch is now completely built. Allow other modules to make changes
    // to the batch so that it is easier to reuse batch processes in other
    // enviroments.
    if (drush_drupal_major_version() >= 8) {
      \Drupal::moduleHandler()->alter('batch', $batch);
    }
    else {
      drupal_alter('batch', $batch);
    }

    // Assign an arbitrary id: don't rely on a serial column in the 'batch'
    // table, since non-progressive batches skip database storage completely.
    $batch['id'] = db_next_id();
    $args[] = $batch['id'];

    $batch['progressive'] = TRUE;

    // Move operations to a job queue. Non-progressive batches will use a
    // memory-based queue.
    foreach ($batch['sets'] as $key => $batch_set) {
      _batch_populate_queue($batch, $key);
    }

    drush_include_engine('drupal', 'environment');
    // Store the batch.
    if (drush_drupal_major_version() >= 8) {
      /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
      $batch_storage = \Drupal::service('batch.storage');
      $batch_storage->create($batch);
    }
    else {
      db_insert('batch')
        ->fields(array(
          'bid' => $batch['id'],
          'timestamp' => REQUEST_TIME,
          'token' => drush_get_token($batch['id']),
          'batch' => serialize($batch),
        ))
        ->execute();
    }
    $finished = FALSE;

    // Not used in D8+ and possibly earlier.
    global $user;

    while (!$finished) {
      $data = drush_invoke_process('@self', $command, $args, array('user' => drush_user_get_class()->getCurrentUserAsSingle()->id()));

      $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
    }
  }
}


/**
 * Initialize the batch command and call the worker function.
 *
 * Loads the batch record from the database and sets up the requirements
 * for the worker, such as registering the shutdown function.
 *
 * @param id
 *   The batch id of the batch being processed.
 */
function _drush_batch_command($id) {
  $batch =& batch_get();

  $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(
    ':bid' => $id))->fetchField();

  if ($data) {
    $batch = unserialize($data);
  }
  else {
    return FALSE;
  }

  if (!isset($batch['running'])) {
    $batch['running'] = TRUE;
  }

  // Register database update for end of processing.
  register_shutdown_function('_drush_batch_shutdown');

  if (_drush_batch_worker()) {
    _drush_batch_finished();
  }
}


/**
 * Process batch operations
 *
 * Using the current $batch process each of the operations until the batch
 * has been completed or half of the available memory for the process has been
 * reached.
 */
function _drush_batch_worker() {
  $batch =& batch_get();
  $current_set =& _batch_current_set();
  $set_changed = TRUE;

  if (empty($current_set['start'])) {
    $current_set['start'] = microtime(TRUE);
  }
  $queue = _batch_queue($current_set);
  while (!$current_set['success']) {
    // If this is the first time we iterate this batch set in the current
    // request, we check if it requires an additional file for functions
    // definitions.
    if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
      include_once DRUPAL_ROOT . '/' . $current_set['file'];
    }

    $task_message = '';
    // Assume a single pass operation and set the completion level to 1 by
    // default.
    $finished = 1;

    if ($item = $queue->claimItem()) {
      list($function, $args) = $item->data;

      // Build the 'context' array and execute the function call.
      $batch_context = array(
        'sandbox'  => &$current_set['sandbox'],
        'results'  => &$current_set['results'],
        'finished' => &$finished,
        'message'  => &$task_message,
      );
      // Magic wrap to catch changes to 'message' key.
      $batch_context = new DrushBatchContext($batch_context);

      // Tolerate recoverable errors.
      // See https://github.com/drush-ops/drush/issues/1930
      $halt_on_error = drush_get_option('halt-on-error', TRUE);
      drush_set_option('halt-on-error', FALSE);
      call_user_func_array($function, array_merge($args, array(&$batch_context)));
      drush_set_option('halt-on-error', $halt_on_error);

      $finished = $batch_context['finished'];
      if ($finished >= 1) {
        // Make sure this step is not counted twice when computing $current.
        $finished = 0;
        // Remove the processed operation and clear the sandbox.
        $queue->deleteItem($item);
        $current_set['count']--;
        $current_set['sandbox'] = array();
      }
    }

    // When all operations in the current batch set are completed, browse
    // through the remaining sets, marking them 'successfully processed'
    // along the way, until we find a set that contains operations.
    // _batch_next_set() executes form submit handlers stored in 'control'
    // sets (see form_execute_handlers()), which can in turn add new sets to
    // the batch.
    $set_changed = FALSE;
    $old_set = $current_set;
    while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
      $current_set = &_batch_current_set();
      $current_set['start'] = microtime(TRUE);
      $set_changed = TRUE;
    }

    // At this point, either $current_set contains operations that need to be
    // processed or all sets have been completed.
    $queue = _batch_queue($current_set);

    // If we are in progressive mode, break processing after 1 second.
    if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) {
      drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH);
      // Record elapsed wall clock time.
      $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
      break;
    }
  }

  // Reporting 100% progress will cause the whole batch to be considered
  // processed. If processing was paused right after moving to a new set,
  // we have to use the info from the new (unprocessed) set.
  if ($set_changed && isset($current_set['queue'])) {
    // Processing will continue with a fresh batch set.
    $remaining        = $current_set['count'];
    $total            = $current_set['total'];
    $progress_message = $current_set['init_message'];
    $task_message     = '';
  }
  else {
    // Processing will continue with the current batch set.
    $remaining        = $old_set['count'];
    $total            = $old_set['total'];
    $progress_message = $old_set['progress_message'];
  }

  $current    = $total - $remaining + $finished;
  $percentage = _batch_api_percentage($total, $current);
  return ($percentage == 100);
}

/**
 * End the batch processing:
 * Call the 'finished' callbacks to allow custom handling of results,
 * and resolve page redirection.
 */
function _drush_batch_finished() {
  $batch = &batch_get();

  // Execute the 'finished' callbacks for each batch set, if defined.
  foreach ($batch['sets'] as $batch_set) {
    if (isset($batch_set['finished'])) {
      // Check if the set requires an additional file for function definitions.
      if (isset($batch_set['file']) && is_file($batch_set['file'])) {
        include_once DRUPAL_ROOT . '/' . $batch_set['file'];
      }
      if (is_callable($batch_set['finished'])) {
        $queue = _batch_queue($batch_set);
        $operations = $queue->getAllItems();
        $elapsed = $batch_set['elapsed'] / 1000;
        $elapsed = drush_drupal_major_version() >=8 ? \Drupal::service('date.formatter')->formatInterval($elapsed) : format_interval($elapsed);
        $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, $elapsed);
      }
    }
  }

  // Clean up the batch table and unset the static $batch variable.
  if (drush_drupal_major_version() >= 8) {
    /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
    $batch_storage = \Drupal::service('batch.storage');
    $batch_storage->delete($batch['id']);
  }
  else {
    db_delete('batch')
      ->condition('bid', $batch['id'])
      ->execute();
  }

  foreach ($batch['sets'] as $batch_set) {
    if ($queue = _batch_queue($batch_set)) {
      $queue->deleteQueue();
    }
  }
  $_batch = $batch;
  $batch = NULL;
  drush_set_option('drush_batch_process_finished', TRUE);
}

/**
 * Shutdown function: store the batch data for next request,
 * or clear the table if the batch is finished.
 */
function _drush_batch_shutdown() {
 if ($batch = batch_get()) {
   if (drush_drupal_major_version() >= 8) {
     /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
     $batch_storage = \Drupal::service('batch.storage');
     $batch_storage->update($batch);
   }
   else {
     db_update('batch')
       ->fields(array('batch' => serialize($batch)))
       ->condition('bid', $batch['id'])
       ->execute();
   }
  }
}
<?php
/**
 * @file
 *   Drupal 6 engine for the Batch API
 */

use Drush\Log\LogLevel;

/**
 * Main loop for the Drush batch API.
 *
 * Saves a record of the batch into the database, and progressively call $command to
 * process the operations.
 *
 * @param command
 *    The command to call to process the batch.
 *
 */
function _drush_backend_batch_process($command = 'batch-process', $args, $options) {
  $batch =& batch_get();

  if (isset($batch)) {
    $process_info = array(
      'current_set' => 0,
    );
    $batch += $process_info;

    // Initiate db storage in order to get a batch id. We have to provide
    // at least an empty string for the (not null) 'token' column.
    db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time());
    $batch['id'] = db_last_insert_id('batch', 'bid');
    $args[] = $batch['id'];

    // Actually store the batch data and the token generated form the batch id.
    db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']);

    $finished = FALSE;

    while (!$finished) {
      $data = drush_invoke_process('@self', $command, $args, $options);
      $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
    }
  }
}

/**
 * Initialize the batch command and call the worker function.
 *
 * Loads the batch record from the database and sets up the requirements
 * for the worker, such as registering the shutdown function.
 *
 * @param id
 *   The batch id of the batch being processed.
 */
function _drush_batch_command($id) {
  $batch =& batch_get();
  // Retrieve the current state of batch from db.
  if ($data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id))) {
    $batch = unserialize($data);
  }
  else {
    return FALSE;
  }
  if (!isset($batch['running'])) {
    $batch['running'] = TRUE;
  }

  // Register database update for end of processing.
  register_shutdown_function('_drush_batch_shutdown');

  if (_drush_batch_worker()) {
    _drush_batch_finished();
  }
}

/**
 * Process batch operations
 *
 * Using the current $batch process each of the operations until the batch
 * has been completed or half of the available memory for the process has been
 * reached.
 */
function _drush_batch_worker() {
  $batch =& batch_get();
  $current_set =& _batch_current_set();
  $set_changed = TRUE;

  timer_start('batch_processing');

  while (!$current_set['success']) {
    // If this is the first time we iterate this batch set in the current
    // request, we check if it requires an additional file for functions
    // definitions.
    if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
      include_once($current_set['file']);
    }

    $finished = 1;
    $task_message = '';
    if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
      // Build the 'context' array, execute the function call,
      // and retrieve the user message.
      $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
      // Magic wrap to catch changes to 'message' key.
      $batch_context = new DrushBatchContext($batch_context);
      // Process the current operation.
      call_user_func_array($function, array_merge($args, array(&$batch_context)));
      $finished = $batch_context['finished'];
    }

    if ($finished >= 1) {
      // Make sure this step isn't counted double when computing $current.
      $finished = 0;
      // Remove the operation and clear the sandbox.
      array_shift($current_set['operations']);
      $current_set['sandbox'] = array();
    }

    // If the batch set is completed, browse through the remaining sets,
    // executing 'control sets' (stored form submit handlers) along the way -
    // this might in turn insert new batch sets.
    // Stop when we find a set that actually has operations.
    $set_changed = FALSE;
    $old_set = $current_set;
    while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
      $current_set =& _batch_current_set();
      $set_changed = TRUE;
    }
    // At this point, either $current_set is a 'real' batch set (has operations),
    // or all sets have been completed.


    // TODO - replace with memory check!
    // If we're in progressive mode, stop after 1 second.
    if ((memory_get_usage() * 2) >= drush_memory_limit()) {
      drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH);
      break;
    }
  }

  // Gather progress information.

  // Reporting 100% progress will cause the whole batch to be considered
  // processed. If processing was paused right after moving to a new set,
  // we have to use the info from the new (unprocessed) one.
  if ($set_changed && isset($current_set['operations'])) {
    // Processing will continue with a fresh batch set.
    $remaining = count($current_set['operations']);
    $total = $current_set['total'];
    $task_message = '';
  }
  else {
    $remaining = count($old_set['operations']);
    $total = $old_set['total'];
  }

  $current    = $total - $remaining + $finished;
  $percentage = $total ? floor($current / $total * 100) : 100;

  return ($percentage == 100);
}

/**
 * End the batch processing:
 * Call the 'finished' callbacks to allow custom handling of results,
 * and resolve page redirection.
 */
function _drush_batch_finished() {
  $batch =& batch_get();

  // Execute the 'finished' callbacks for each batch set.
  foreach ($batch['sets'] as $key => $batch_set) {
    if (isset($batch_set['finished'])) {
      // Check if the set requires an additional file for functions definitions.
      if (isset($batch_set['file']) && is_file($batch_set['file'])) {
        include_once($batch_set['file']);
      }
      if (function_exists($batch_set['finished'])) {
        $batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']);
      }
    }
  }

  // Cleanup the batch table and unset the global $batch variable.
  db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
  $_batch = $batch;
  $batch = NULL;
  drush_set_option('drush_batch_process_finished', TRUE);
}

/**
 * Shutdown function: store the batch data for next request,
 * or clear the table if the batch is finished.
 */
function _drush_batch_shutdown() {
  if ($batch = batch_get()) {
    db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']);
  }
}
<?php
/**
 * @file
 *   Engine for the cache commands.
 */

function _drush_cache_command_get($cid, $bin) {
  if (is_null($bin)) {
    $bin = _drush_cache_bin_default();
  }
  return cache_get($cid, $bin);
}

/**
 * The default bin.
 *
 * @return string
 */
function _drush_cache_bin_default() {
  return 'cache';
}

function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) {
  // Convert the "expire" argument to a valid value for Drupal's cache_set().
  if (is_null($bin)) {
    $bin = _drush_cache_bin_default();
  }
  if ($expire == 'CACHE_TEMPORARY') {
    $expire = CACHE_TEMPORARY;
  }
  if (!isset($expire) || $expire == 'CACHE_PERMANENT') {
    $expire = CACHE_PERMANENT;
  }

  // D6/D7 don't natively support cache tags.
  return cache_set($cid, $data, $bin, $expire);
}

function _drush_cache_clear_types($include_bootstrapped_types) {
  $types = array(
    'drush' => 'drush_cache_clear_drush',
    'all' => 'drush_cache_clear_both',
  );
  if ($include_bootstrapped_types) {
    $types += array(
      'theme-registry' => 'drush_cache_clear_theme_registry',
      'menu' => 'menu_rebuild',
      'css-js' => 'drush_cache_clear_css_js',
      'block' => 'drush_cache_clear_block',
      'module-list' => 'drush_get_modules',
      'theme-list' => 'drush_get_themes',
    );
  }
  $drupal_version = drush_drupal_major_version();

  if ($drupal_version >= 7) {
    $types['registry'] = 'registry_update';
  }
  elseif ($drupal_version == 6 && function_exists('module_exists') && module_exists('autoload')) {
    // TODO: move this to autoload module.
    $types['registry'] = 'autoload_registry_update';
  }

  return $types;
}

function drush_cache_clear_theme_registry() {
  if (drush_drupal_major_version() >= 7) {
    drupal_theme_rebuild();
  }
  else {
    cache_clear_all('theme_registry', 'cache', TRUE);
  }
}

function drush_cache_clear_menu() {
  return menu_router_rebuild();
}

function drush_cache_clear_css_js() {
  _drupal_flush_css_js();
  drupal_clear_css_cache();
  drupal_clear_js_cache();
}

/**
 * Clear the cache of the block output.
 */
function drush_cache_clear_block() {
  cache_clear_all(NULL, 'cache_block');
}

/**
 * Clear caches internal to Drush core and Drupal.
 */
function drush_cache_clear_both() {
  drush_cache_clear_drush();
  if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    drupal_flush_all_caches();
  }
}
<?php
/**
 * @file
 *   Engine for the cache commands.
 */

use Drupal\Core\Cache\Cache;

function _drush_cache_command_get($cid, $bin) {
  if (is_null($bin)) {
    $bin = _drush_cache_bin_default();
  }
  return \Drupal::cache($bin)->get($cid);
}

/**
 * The default bin.
 *
 * @return string
 */
function _drush_cache_bin_default() {
  return 'default';
}

function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) {
  if (is_null($bin)) {
    $bin = _drush_cache_bin_default();
  }

  // Convert the "expire" argument to a valid value for Drupal's cache_set().
  if ($expire == 'CACHE_TEMPORARY') {
    $expire = Cache::TEMPORARY;
  }
  if (!isset($expire) || $expire == 'CACHE_PERMANENT') {
    $expire = Cache::PERMANENT;
  }

  return \Drupal::cache($bin)->set($cid, $data, $expire, $tags);
}

function _drush_cache_clear_types($include_bootstrapped_types) {
  $types = array(
    'drush' => 'drush_cache_clear_drush',
  );
  if ($include_bootstrapped_types) {
    $types += array(
      'theme-registry' => 'drush_cache_clear_theme_registry',
      'router' => 'drush_cache_clear_router',
      'css-js' => 'drush_cache_clear_css_js',
      'module-list' => 'drush_get_modules',
      'theme-list' => 'drush_get_themes',
      'render' => 'drush_cache_clear_render',
    );
  }
  return $types;
}

function drush_cache_clear_theme_registry() {
  \Drupal::service('theme.registry')->reset();
}

function drush_cache_clear_router() {
  /** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
  $router_builder = \Drupal::service('router.builder');
  $router_builder->rebuild();
}

function drush_cache_clear_css_js() {
  _drupal_flush_css_js();
  drupal_clear_css_cache();
  drupal_clear_js_cache();
}

/**
 * Clear the cache of the block output.
 */
function drush_cache_clear_block() {
  // There is no distinct block cache in D8. See https://github.com/drush-ops/drush/issues/1531.
  // \Drupal::cache('block')->deleteAll();
}

/**
 * Clears the render cache entries.
 */
function drush_cache_clear_render() {
  Cache::invalidateTags(['rendered']);
}
<?php
/**
 * @file
 *   Specific functions for a drupal 8+ environment.
 *   drush_include_engine() magically includes either this file
 *   or environment_X.inc depending on which version of drupal Drush
 *   is called from.
 */

use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
use Drupal\Core\StreamWrapper\PublicStream;
use Drush\Log\LogLevel;
use Drupal\Core\Logger\RfcLogLevel;

/**
 * Get complete information for all available modules.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden modules should be excluded or not.
 * @return
 *   An array containing module info for all available modules.
 */
function drush_get_modules($include_hidden = TRUE) {
  $modules = system_rebuild_module_data();

  foreach ($modules as $key => $module) {
    if ((!$include_hidden) && (!empty($module->info['hidden']))) {
      unset($modules[$key]);
    }
    else {
      $module->schema_version = drupal_get_installed_schema_version($key);
    }
  }

  return $modules;
}

/**
 * Returns drupal required modules, including modules declared as required dynamically.
 */
function _drush_drupal_required_modules($module_info) {
  $required = drupal_required_modules();
  foreach ($module_info as $name => $module) {
    if (isset($module->info['required']) && $module->info['required']) {
      $required[] = $name;
    }
  }
  return array_unique($required);
}

/**
 * Return dependencies and its status for modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependencies and status for $modules
 */
function drush_check_module_dependencies($modules, $module_info) {
  $status = array();
  foreach ($modules as $key => $module) {
    $dependencies = array_reverse($module_info[$module]->requires);
    $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info));
    if (!empty($unmet_dependencies)) {
      $status[$key]['error'] = array(
          'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
          'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
      );
    }
    else {
      // check for version incompatibility
      foreach ($dependencies as $dependency_name => $v) {
        $current_version = $module_info[$dependency_name]->info['version'];
        $current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version);
        $incompatibility = drupal_check_incompatibility($v, $current_version);
        if (isset($incompatibility)) {
          $status[$key]['error'] = array(
            'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH',
            'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version))
          );
        }
      }
    }
    $status[$key]['unmet-dependencies'] = $unmet_dependencies;
    $status[$key]['dependencies'] = $dependencies;
  }

  return $status;
}

/**
 * Return dependents of modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependents for each one of $modules
 */
function drush_module_dependents($modules, $module_info) {
  $dependents = array();
  foreach ($modules as $module) {
    $keys = array_keys($module_info[$module]->required_by);
    $dependents = array_merge($dependents, array_combine($keys, $keys));
  }

  return array_unique($dependents);
}

/**
 * Returns a list of enabled modules.
 *
 * This is a wrapper for module_list().
 */
function drush_module_list() {
  $modules = array_keys(\Drupal::moduleHandler()->getModuleList());
  return array_combine($modules, $modules);
}

/**
 * Installs a given list of modules.
 *
 * @see \Drupal\Core\Extension\ModuleInstallerInterface::install()
 *
 */
function drush_module_install($module_list, $enable_dependencies = TRUE) {
  return \Drupal::service('module_installer')->install($module_list, $enable_dependencies);
}

/**
 * Checks that a given module exists and is enabled.
 *
 * @see \Drupal\Core\Extension\ModuleHandlerInterface::moduleExists()
 *
 */
function drush_module_exists($module) {
  return \Drupal::moduleHandler()->moduleExists($module);
}

/**
 * Determines which modules are implementing a hook.
 *
 * @param string $hook
 *   The hook name.
 * @param bool $sort
 *  Not used in Drupal 8 environment.
 * @param bool $reset
 *  TRUE to reset the hook implementation cache.
 *
 * @see \Drupal\Core\Extension\ModuleHandlerInterface::getImplementations().
 * @see \Drupal\Core\Extension\ModuleHandlerInterface::resetImplementations().
 *
 */
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
  // $sort is there for consistency, but looks like Drupal 8 has no equilavient for it.
  // We can sort the list manually later if really needed.
  if ($reset == TRUE){
    \Drupal::moduleHandler()->resetImplementations();
  }
  return \Drupal::moduleHandler()->getImplementations($hook);
}

/**
 * Return a list of modules from a list of named modules.
 * Both enabled and disabled/uninstalled modules are returned.
 */
function drush_get_named_extensions_list($extensions) {
  $result = array();
  $modules = drush_get_modules();
  foreach($modules as $name => $module) {
    if (in_array($name, $extensions)) {
      $result[$name] = $module;
    }
  }
  $themes = drush_get_themes();
  foreach($themes as $name => $theme) {
    if (in_array($name, $extensions)) {
      $result[$name] = $theme;
    }
  }
  return $result;
}

/**
 * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_enable($modules) {
  // The list of modules already have all the dependencies, but they might not
  // be in the correct order. Still pass $enable_dependencies = TRUE so that
  // Drupal will enable the modules in the correct order.
  drush_module_install($modules);

  // Our logger got blown away during the container rebuild above.
  $boot = drush_select_bootstrap_class();
  $boot->add_logger();

  // Flush all caches. No longer needed in D8 per https://github.com/drush-ops/drush/issues/1207
  // drupal_flush_all_caches();
}

/**
 * Disable a list of modules. It is assumed the list contains all dependents not already disabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_disable($modules) {
  drush_set_error('DRUSH_MODULE_DISABLE', dt('Drupal 8 does not support disabling modules. Use pm-uninstall instead.'));
}

/**
 * Uninstall a list of modules.
 *
 * @param $modules
 *   Array of module names
 *
 * @see \Drupal\Core\Extension\ModuleInstallerInterface::uninstall()
 */
function drush_module_uninstall($modules) {
  \Drupal::service('module_installer')->uninstall($modules);
  // Our logger got blown away during the container rebuild above.
  $boot = drush_select_bootstrap_class();
  $boot->add_logger();
}

/**
  * Invokes a hook in a particular module.
  *
  */
function drush_module_invoke($module, $hook) {
  $args = func_get_args();
  // Remove $module and $hook from the arguments.
  unset($args[0], $args[1]);
  return \Drupal::moduleHandler()->invoke($module, $hook, $args);
}

/**
  * Invokes a hook in all enabled modules that implement it.
  *
  */
function drush_module_invoke_all($hook) {
  $args = func_get_args();
  // Remove $hook from the arguments.
  array_shift($args);
  return \Drupal::moduleHandler()->invokeAll($hook, $args);
}

/**
 * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
 * and include hidden as well.
 *
 * @return \Drupal\Core\Extension\Extension[]
 *  A list of themes keyed by name.
 */
function drush_theme_list() {
  $theme_handler = \Drupal::service('theme_handler');
  return $theme_handler->listInfo();
}

/**
 * Get complete information for all available themes.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden themes should be excluded or not.
 * @return
 *   An array containing theme info for all available themes.
 */
function drush_get_themes($include_hidden = TRUE) {
  $themes = \Drupal::service('theme_handler')->rebuildThemeData();
  foreach ($themes as $key => $theme) {
    if (!$include_hidden) {
      if (isset($theme->info['hidden'])) {
        // Don't exclude default or admin theme.
        if ($key != _drush_theme_default() && $key != _drush_theme_admin()) {
          unset($themes[$key]);
        }
      }
    }
  }

  return $themes;
}

/**
 * Enable a list of themes.
 *
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_enable($themes) {
  \Drupal::service('theme_handler')->install($themes);
}

/**
 * Disable a list of themes.
 *
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_disable($themes) {
  drush_set_error('DRUSH_THEME_DISABLE', dt('Drupal 8 does not support disabling themes. Use pm-uninstall instead.'));
}

/**
 * Uninstall a list of themes.
 *
 * @param $themes
 *  Array of theme names
 *
 * @see \Drupal\Core\Extension\ThemeHandlerInterface::uninstall()
 */
function drush_theme_uninstall($themes) {
  \Drupal::service('theme_handler')->uninstall($themes);
  // Our logger got blown away during the container rebuild above.
  $boot = drush_select_bootstrap_class();
  $boot->add_logger();
}

/**
 * Helper function to obtain the severity levels based on Drupal version.
 *
 * @return array
 *   Watchdog severity levels keyed by RFC 3164 severities.
 */
function drush_watchdog_severity_levels() {
  return array(
    RfcLogLevel::EMERGENCY => LogLevel::EMERGENCY,
    RfcLogLevel::ALERT => LogLevel::ALERT,
    RfcLogLevel::CRITICAL => LogLevel::CRITICAL,
    RfcLogLevel::ERROR => LogLevel::ERROR,
    RfcLogLevel::WARNING => LogLevel::WARNING,
    RfcLogLevel::NOTICE => LogLevel::NOTICE,
    RfcLogLevel::INFO => LogLevel::INFO,
    RfcLogLevel::DEBUG => LogLevel::DEBUG,
  );
}

/**
 * Helper function to obtain the message types based on drupal version.
 *
 * @return
 *   Array of watchdog message types.
 */
function drush_watchdog_message_types() {
  return _dblog_get_message_types();
}

function _drush_theme_default() {
  return \Drupal::config('system.theme')->get('default');
}

function _drush_theme_admin() {
  $theme = \Drupal::config('system.theme')->get('admin');
  return empty($theme) ? 'seven' : $theme;
}

function _drush_file_public_path() {
  return PublicStream::basePath();
}

function _drush_file_private_path() {
  return PrivateStream::basePath();
}

/**
 * Gets the extension name.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension name.
 */
function _drush_extension_get_name($info) {
  return $info->getName();
}

/**
 * Gets the extension type.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension type.
 */
function _drush_extension_get_type($info) {
  return $info->getType();
}

/**
 * Gets the extension path.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension path.
 */
function _drush_extension_get_path($info) {
  return $info->getPath();
}

/*
 * Wrapper for CSRF token generation.
 */
function drush_get_token($value = NULL) {
  return \Drupal::csrfToken()->get($value);
}

/*
 * Wrapper for _url().
 */
function drush_url($path = NULL, array $options = array()) {
  return \Drupal\Core\Url::fromUserInput('/' . $path, $options)->toString();
}

/**
 * Output a Drupal render array, object or string as plain text.
 *
 * @param string $data
 *   Data to render.
 *
 * @return string
 *   The plain-text representation of the input.
 */
function drush_render($data) {
  if (is_array($data)) {
    $data = \Drupal::service('renderer')->renderRoot($data);
  }

  $data = \Drupal\Core\Mail\MailFormatHelper::htmlToText($data);
  return $data;
}
<?php
/**
 * @file
 *   Specific functions for a drupal 6 environment.
 *   drush_include_engine() magically includes either this file
 *   or environment_X.inc depending on which version of drupal drush
 *   is called from.
 */

/**
 * Get complete information for all available modules.
 *
 * We need to set the type for those modules that are not already in the system table.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden modules should be excluded or not.
 * @return
 *   An array containing module info for all available modules.
 */
function drush_get_modules($include_hidden = TRUE) {
  $modules = module_rebuild_cache();
  foreach ($modules as $key => $module) {
    if (!isset($module->type)) {
      $module->type = 'module';
    }
    if ((!$include_hidden) && isset($module->info['hidden']) && ($module->info['hidden'])) {
      unset($modules[$key]);
    }
  }

  return $modules;
}

/**
 * Returns drupal required modules, including their dependencies.
 *
 * A module may alter other module's .info to set a dependency on it.
 * See for example http://drupal.org/project/phpass
 */
function _drush_drupal_required_modules($module_info) {
  $required = drupal_required_modules();
  foreach ($required as $module) {
    $required = array_merge($required, $module_info[$module]->info['dependencies']);
  }
  return $required;
}

/**
 * Return dependencies and its status for modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependencies and status for $modules
 */
function drush_check_module_dependencies($modules, $module_info) {
  $status = array();
  foreach ($modules as $key => $module) {
    $dependencies = array_reverse($module_info[$module]->info['dependencies']);
    $unmet_dependencies = array_diff($dependencies, array_keys($module_info));
    if (!empty($unmet_dependencies)) {
      $status[$key]['error'] = array(
          'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
          'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
      );
    }
    $status[$key]['unmet-dependencies'] = $unmet_dependencies;
    $status[$key]['dependencies'] = array();
    foreach ($dependencies as $dependency) {
      $status[$key]['dependencies'][$dependency] = array('name' => $dependency);
    }
  }

  return $status;
}

/**
 * Return dependents of modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependents for each one of $modules
 */
function drush_module_dependents($modules, $module_info) {
  $dependents = array();
  foreach ($modules as $module) {
    $dependents = array_merge($dependents, $module_info[$module]->info['dependents']);
  }

  return array_unique($dependents);
}

/**
 * Returns a list of enabled modules.
 *
 * This is a simplified version of module_list().
 */
function drush_module_list() {
  $enabled = array();
  $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
  while ($row = drush_db_result($rsc)) {
    $enabled[$row] = $row;
  }

  return $enabled;
}

/**
 * Return a list of extensions from a list of named extensions.
 * Both enabled and disabled/uninstalled extensions are returned.
 */
function drush_get_named_extensions_list($extensions) {
  $result = array();
  $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
  while ($row = drush_db_fetch_object($rsc)) {
    $result[$row->name] = $row;
  }
  return $result;
}

/**
 * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_enable($modules) {
  // Try to install modules previous to enabling.
  foreach ($modules as $module) {
    _drupal_install_module($module);
  }
  module_enable($modules);
  drush_system_modules_form_submit();
}

/**
 * Disable a list of modules. It is assumed the list contains all dependents not already disabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_disable($modules) {
  module_disable($modules, FALSE);
  drush_system_modules_form_submit();
}

/**
 * Uninstall a list of modules.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_uninstall($modules) {
  require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
  foreach ($modules as $module) {
    drupal_uninstall_module($module);
  }
}

/**
 * Checks that a given module exists and is enabled.
 *
 * @see module_exists().
 *
 */
function drush_module_exists($module) {
  return module_exists($module);
}

/**
 * Determines which modules are implementing a hook.
 *
 */
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
  return module_implements($hook, $sort, $reset);
}

/**
 * Invokes a hook in a particular module.
 *
 */
function drush_module_invoke($module, $hook) {
  $args = func_get_args();
  return call_user_func_array('module_invoke', $args);
}

/**
 * Invokes a hook in all enabled modules that implement it.
 *
 */
function drush_module_invoke_all($hook) {
  $args = func_get_args();
  return call_user_func_array('module_invoke_all', $args);
}

/**
 * Submit the system modules form.
 *
 * The modules should already be fully enabled/disabled before calling this
 * function. Calling this function just makes sure any activities triggered by
 * the form submit (such as admin_role) are completed.
 */
function drush_system_modules_form_submit() {
  $active_modules = array();
  foreach (drush_get_modules(FALSE) as $key => $module) {
    if ($module->status == 1) {
      $active_modules[$key] = $key;
    }
  }
  module_load_include('inc', 'system', 'system.admin');
  $form_state = array('values' => array('status' => $active_modules));
  drupal_execute('system_modules', $form_state);
}

/**
 * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
 * and include hidden/disabled as well.
 *
 * @return array
 *  A list of themes keyed by name.
 */
function drush_theme_list() {
  $enabled = array();
  foreach (list_themes() as $key => $info) {
    if ($info->status) {
      $enabled[$key] = $info;
    }
  }
  return $enabled;
}

/**
 * Get complete information for all available themes.
 *
 * We need to set the type for those themes that are not already in the system table.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden themes should be excluded or not.
 * @return
 *   An array containing theme info for all available themes.
 */
function drush_get_themes($include_hidden = TRUE) {
  $themes = system_theme_data();
  foreach ($themes as $key => $theme) {
    if (!isset($theme->type)) {
      $theme->type = 'theme';
    }
    if ((!$include_hidden) && isset($theme->info['hidden']) && ($theme->info['hidden'])) {
      unset($themes[$key]);
    }
  }

  return $themes;
}

/**
 * Enable a list of themes.
 *
 * This function is based on system_themes_form_submit().
 *
 * @see system_themes_form_submit()
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_enable($themes) {
  drupal_clear_css_cache();
  foreach ($themes as $theme) {
    system_initialize_theme_blocks($theme);
  }
  db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
  list_themes(TRUE);
  menu_rebuild();
  module_invoke('locale', 'system_update', $themes);
}

/**
 * Disable a list of themes.
 *
 * This function is based on system_themes_form_submit().
 *
 * @see system_themes_form_submit()
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_disable($themes) {
  drupal_clear_css_cache();
  db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
  list_themes(TRUE);
  menu_rebuild();
  drupal_rebuild_theme_registry();
  module_invoke('locale', 'system_update', $themes);
}

/**
 * Helper function to obtain the severity levels based on Drupal version.
 *
 * This is a copy of watchdog_severity_levels() without t().
 *
 * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
 *
 * @return
 *   Array of watchdog severity levels.
 */
function drush_watchdog_severity_levels() {
  return array(
    WATCHDOG_EMERG    => 'emergency',
    WATCHDOG_ALERT    => 'alert',
    WATCHDOG_CRITICAL => 'critical',
    WATCHDOG_ERROR    => 'error',
    WATCHDOG_WARNING  => 'warning',
    WATCHDOG_NOTICE   => 'notice',
    WATCHDOG_INFO     => 'info',
    WATCHDOG_DEBUG    => 'debug',
  );
}

/**
 * Helper function to obtain the message types based on drupal version.
 *
 * @return
 *   Array of watchdog message types.
 */
function drush_watchdog_message_types() {
  return drupal_map_assoc(_dblog_get_message_types());
}

function _drush_theme_default() {
  return variable_get('theme_default', 'garland');
}

function _drush_theme_admin() {
  return variable_get('admin_theme', drush_theme_get_default());
}

function _drush_file_public_path() {
  if (function_exists('file_directory_path')) {
    return file_directory_path();
  }
}

function _drush_file_private_path() {
  // @todo
}

/**
 * Gets the extension name.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension name.
 */
function _drush_extension_get_name($info) {
  return $info->name;
}

/**
 * Gets the extension type.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension type.
 */
function _drush_extension_get_type($info) {
  return $info->type;
}

/**
 * Gets the extension path.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension path.
 */
function _drush_extension_get_path($info) {
  return dirname($info->filename);
}

/*
 * Wrapper for CSRF token generation.
 */
function drush_get_token($value = NULL) {
  return drupal_get_token($value);
}

/*
 * Wrapper for _url().
 */
function drush_url($path = NULL, $options = array()) {
  return url($path, $options);
}

/**
 * Output a Drupal render array, object or plain string as plain text.
 *
 * @param string $data
 *   Data to render.
 *
 * @return string
 *   The plain-text representation of the input.
 */
function drush_render($data) {
  if (is_array($data)) {
    $data = drupal_render($data);
  }

  $data = drupal_html_to_text($data);
  return $data;
}
<?php
/**
 * @file
 *   Specific functions for a drupal 7 environment.
 *   drush_include_engine() magically includes either this file
 *   or environment_X.inc depending on which version of drupal drush
 *   is called from.
 */

/**
 * Get complete information for all available modules.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden modules should be excluded or not.
 * @return
 *   An array containing module info for all available modules.
 */
function drush_get_modules($include_hidden = TRUE) {
  $modules = system_rebuild_module_data();
  if (!$include_hidden) {
    foreach ($modules as $key => $module) {
      if (isset($module->info['hidden'])) {
        unset($modules[$key]);
      }
    }
  }

  return $modules;
}

/**
 * Returns drupal required modules, including modules declared as required dynamically.
 */
function _drush_drupal_required_modules($module_info) {
  $required = drupal_required_modules();
  foreach ($module_info as $name => $module) {
    if (isset($module->info['required']) && $module->info['required']) {
      $required[] = $name;
    }
  }
  return array_unique($required);
}

/**
 * Return dependencies and its status for modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependencies and status for $modules
 */
function drush_check_module_dependencies($modules, $module_info) {
  $status = array();
  foreach ($modules as $key => $module) {
    $dependencies = array_reverse($module_info[$module]->requires);
    $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info));
    if (!empty($unmet_dependencies)) {
      $status[$key]['error'] = array(
          'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
          'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
      );
    }
    else {
      // check for version incompatibility
      foreach ($dependencies as $dependency_name => $v) {
        $current_version = $module_info[$dependency_name]->info['version'];
        $current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version);
        $incompatibility = drupal_check_incompatibility($v, $current_version);
        if (isset($incompatibility)) {
          $status[$key]['error'] = array(
            'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH',
            'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version))
          );
        }
      }
    }
    $status[$key]['unmet-dependencies'] = $unmet_dependencies;
    $status[$key]['dependencies'] = $dependencies;
  }

  return $status;
}

/**
 * Return dependents of modules.
 *
 * @param $modules
 *   Array of module names
 * @param $module_info
 *   Drupal 'files' array for modules as returned by drush_get_modules().
 * @return
 *   Array with dependents for each one of $modules
 */
function drush_module_dependents($modules, $module_info) {
  $dependents = array();
  foreach ($modules as $module) {
    $dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by)));
  }

  return array_unique($dependents);
}

/**
 * Returns a list of enabled modules.
 *
 * This is a simplified version of module_list().
 */
function drush_module_list() {
  $enabled = array();
  $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
  while ($row = drush_db_result($rsc)) {
    $enabled[$row] = $row;
  }

  return $enabled;
}

/**
 * Return a list of extensions from a list of named extensions.
 * Both enabled and disabled/uninstalled extensions are returned.
 */
function drush_get_named_extensions_list($extensions) {
  $result = array();
  $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
  while ($row = drush_db_fetch_object($rsc)) {
    $result[$row->name] = $row;
  }
  return $result;
}

/**
 * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_enable($modules) {
  // The list of modules already have all the dependencies, but they might not
  // be in the correct order. Still pass $enable_dependencies = TRUE so that
  // Drupal will enable the modules in the correct order.
  module_enable($modules);
  // Flush all caches.
  drupal_flush_all_caches();
}

/**
 * Disable a list of modules. It is assumed the list contains all dependents not already disabled.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_disable($modules) {
  // The list of modules already have all the dependencies, but they might not
  // be in the correct order. Still pass $enable_dependencies = TRUE so that
  // Drupal will enable the modules in the correct order.
  module_disable($modules);
  // Flush all caches.
  drupal_flush_all_caches();
}

/**
 * Uninstall a list of modules.
 *
 * @param $modules
 *   Array of module names
 */
function drush_module_uninstall($modules) {
  require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
  // Break off 8.x functionality when we get another change.
  if (drush_drupal_major_version() >= 8) {
    module_uninstall($modules);
  }
  else {
    drupal_uninstall_modules($modules);
  }
}

/**
 * Checks that a given module exists and is enabled.
 *
 * @see module_exists().
 *
 */
function drush_module_exists($module) {
  return module_exists($module);
}

/**
 * Determines which modules are implementing a hook.
 *
 */
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
  return module_implements($hook, $sort, $reset);
}

/**
 * Invokes a hook in a particular module.
 *
 */
function drush_module_invoke($module, $hook) {
  $args = func_get_args();
  return call_user_func_array('module_invoke', $args);
}

/**
 * Invokes a hook in all enabled modules that implement it.
 *
 */
function drush_module_invoke_all($hook) {
  $args = func_get_args();
  return call_user_func_array('module_invoke_all', $args);
}

/**
 * Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
 * and include hidden/disabled as well.
 *
 * @return array
 *  A list of themes keyed by name.
 */
function drush_theme_list() {
  $enabled = array();
  foreach (list_themes() as $key => $info) {
    if ($info->status) {
      $enabled[$key] = $info;
    }
  }
  return $enabled;
}

/**
 * Get complete information for all available themes.
 *
 * @param $include_hidden
 *   Boolean to indicate whether hidden themes should be excluded or not.
 * @return
 *   An array containing theme info for all available themes.
 */
function drush_get_themes($include_hidden = TRUE) {
  $themes = system_rebuild_theme_data();
  if (!$include_hidden) {
    foreach ($themes as $key => $theme) {
      if (isset($theme->info['hidden'])) {
        unset($themes[$key]);
      }
    }
  }

  return $themes;
}

/**
 * Enable a list of themes.
 *
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_enable($themes) {
  theme_enable($themes);
}

/**
 * Disable a list of themes.
 *
 * @param $themes
 *  Array of theme names.
 */
function drush_theme_disable($themes) {
  theme_disable($themes);
}

/**
 * Helper function to obtain the severity levels based on Drupal version.
 *
 * This is a copy of watchdog_severity_levels() without t().
 *
 * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
 *
 * @return
 *   Array of watchdog severity levels.
 */
function drush_watchdog_severity_levels() {
  return array(
    WATCHDOG_EMERGENCY=> 'emergency',
    WATCHDOG_ALERT    => 'alert',
    WATCHDOG_CRITICAL => 'critical',
    WATCHDOG_ERROR    => 'error',
    WATCHDOG_WARNING  => 'warning',
    WATCHDOG_NOTICE   => 'notice',
    WATCHDOG_INFO     => 'info',
    WATCHDOG_DEBUG    => 'debug',
  );
}

/**
 * Helper function to obtain the message types based on drupal version.
 *
 * @return
 *   Array of watchdog message types.
 */
function drush_watchdog_message_types() {
  return drupal_map_assoc(_dblog_get_message_types());
}

function _drush_theme_default() {
  return variable_get('theme_default', 'garland');
}

function _drush_theme_admin() {
  return variable_get('admin_theme', drush_theme_get_default());
}

function _drush_file_public_path() {
  return variable_get('file_public_path', conf_path() . '/files');
}

function _drush_file_private_path() {
  return variable_get('file_private_path', FALSE);
}

/**
 * Gets the extension name.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension name.
 */
function _drush_extension_get_name($info) {
  return $info->name;
}

/**
 * Gets the extension type.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension type.
 */
function _drush_extension_get_type($info) {
  return $info->type;
}

/**
 * Gets the extension path.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension path.
 */
function _drush_extension_get_path($info) {
  return dirname($info->filename);
}

/*
 * Wrapper for CSRF token generation.
 */
function drush_get_token($value = NULL) {
  return drupal_get_token($value);
}

/*
 * Wrapper for _url().
 */
function drush_url($path = NULL, array $options = array()) {
  return url($path, $options);
}

/**
 * Output a Drupal render array, object or plain string as plain text.
 *
 * @param string $data
 *   Data to render.
 *
 * @return string
 *   The plain-text representation of the input.
 */
function drush_render($data) {
  if (is_array($data)) {
    $data = drupal_render($data);
  }

  $data = drupal_html_to_text($data);
  return $data;
}
<?php

use Drush\Log\LogLevel;

/**
 * @file
 *   Specific functions for a Drupal image handling.
 *   drush_include_engine() magically includes either this file
 *   or image_X.inc depending on which version of Drupal
 *   is in use.
 */

function drush_image_styles() {
  return \Drupal::entityManager()->getStorage('image_style')->loadMultiple();
}

function drush_image_style_load($style_name) {
  return \Drupal::entityManager()->getStorage('image_style')->load($style_name);
}

function drush_image_flush_single($style_name) {
  if ($style = drush_image_style_load($style_name)) {
    $style->flush();
    drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), LogLevel::SUCCESS);
  }
}

/*
 * Command callback. Create an image derivative.
 *
 * @param string $style_name
 *   The name of an image style.
 *
 * @param string $source
 *   The path to a source image, relative to Drupal root.
 */
function _drush_image_derive($style_name, $source) {
  $image_style = drush_image_style_load($style_name);
  $derivative_uri = $image_style->buildUri($source);
  if ($image_style->createDerivative($source, $derivative_uri)) {
    return $derivative_uri;
  }
}
<?php

use Drush\Log\LogLevel;

/**
 * @file
 *   Specific functions for a Drupal image handling.
 *   drush_include_engine() magically includes either this file
 *   or image_X.inc depending on which version of Drupal
 *   is in use.
 */

function drush_image_styles() {
  return image_styles();
}

function drush_image_style_load($style_name) {
  return image_style_load($style_name);
}

function drush_image_flush_single($style_name) {
  if ($style = image_style_load($style_name)) {
    image_style_flush($style);
    drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), LogLevel::SUCCESS);
  }
}

/*
 * Command callback. Create an image derivative.
 *
 * @param string $style_name
 *   The name of an image style.
 *
 * @param string $source
 *   The path to a source image, relative to Drupal root.
 */
function _drush_image_derive($style_name, $source) {
  $image_style = image_style_load($style_name);
  $scheme = file_default_scheme();
  $image_uri = $scheme . '://' . $source;
  $derivative_uri = image_style_path($image_style['name'], $image_uri);
  if (image_style_create_derivative($image_style, $source, $derivative_uri)) {
    return $derivative_uri;
  }
}
<?php

use Drush\Log\LogLevel;

/**
 * Command callback. Disable one or more extensions.
 */
function _drush_pm_disable($args) {
  $extension_info = drush_get_extensions();

  // classify $args in themes, modules or unknown.
  $modules = array();
  $themes = array();
  drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
  $extensions = array_merge($modules, $themes);
  $unknown = array_diff($args, $extensions);

  // Discard and set an error for each unknown extension.
  foreach ($unknown as $name) {
    drush_log('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name)), LogLevel::WARNING);
  }

  // Discard already disabled extensions.
  foreach ($extensions as $name) {
    if (!$extension_info[$name]->status) {
      if ($extension_info[$name]->type == 'module') {
        unset($modules[$name]);
      }
      else {
        unset($themes[$name]);
      }
      drush_log(dt('!extension is already disabled.', array('!extension' => $name)), LogLevel::OK);
    }
  }

  // Discard default theme.
  if (!empty($themes)) {
    $default_theme = drush_theme_get_default();
    if (in_array($default_theme, $themes)) {
      unset($themes[$default_theme]);
      drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), LogLevel::OK);
    }
  }

  if (!empty($modules)) {
    // Add enabled dependents to the list of modules to disable.
    $dependents = drush_module_dependents($modules, $extension_info);
    $dependents = array_intersect($dependents, drush_module_list());
    $modules = array_merge($modules, $dependents);

    // Discard required modules.
    $required = drush_drupal_required_modules($extension_info);
    foreach ($required as $module) {
      if (isset($modules[$module])) {
        unset($modules[$module]);
        $info = $extension_info[$module]->info;
        // No message for hidden modules.
        if (!isset($info['hidden'])) {
          $explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation'])))  : '';
          drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)) . $explanation, LogLevel::OK);
        }
      }
    }
  }

  // Inform the user which extensions will finally be disabled.
  $extensions = array_merge($modules, $themes);
  if (empty($extensions)) {
    return drush_log(dt('There were no extensions that could be disabled.'), LogLevel::OK);
  }
  else {
    drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions))));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  // Disable themes.
  if (!empty($themes)) {
    drush_theme_disable($themes);
  }

  // Disable modules and pass dependency validation in form submit.
  if (!empty($modules)) {
    drush_module_disable($modules);
  }

  // Inform the user of final status.
  $result_extensions = drush_get_named_extensions_list($extensions);
  $problem_extensions = array();
  foreach ($result_extensions as $extension) {
    if (!$extension->status) {
      drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), LogLevel::OK);
    }
    else {
      $problem_extensions[] = $extension->name;
    }
  }
  if (!empty($problem_extensions)) {
    return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions))));
  }
}

/**
 * Command callback. Uninstall one or more modules.
 */
function _drush_pm_uninstall($modules) {
  drush_include_engine('drupal', 'environment');
  $module_info = drush_get_modules();
  $required = drush_drupal_required_modules($module_info);

  // Discards modules which are enabled, not found or already uninstalled.
  foreach ($modules as $key => $module) {
    if (!isset($module_info[$module])) {
      // The module does not exist in the system.
      unset($modules[$key]);
      drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), LogLevel::WARNING);
    }
    else if ($module_info[$module]->status) {
      // The module is enabled.
      unset($modules[$key]);
      drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), LogLevel::WARNING);
    }
    else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED
      // The module is uninstalled.
      unset($modules[$key]);
      drush_log(dt('!module is already uninstalled.', array('!module' => $module)), LogLevel::OK);
    }
    else {
      $dependents = array();
      foreach (drush_module_dependents(array($module), $module_info) as $dependent) {
        if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) {
          $dependents[] = $dependent;
        }
      }
      if (count($dependents)) {
        drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $dependents))), LogLevel::ERROR);
        unset($modules[$key]);
      }
    }
  }

  // Inform the user which modules will finally be uninstalled.
  if (empty($modules)) {
    return drush_log(dt('There were no modules that could be uninstalled.'), LogLevel::OK);
  }
  else {
    drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules))));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  // Uninstall the modules.
  drush_module_uninstall($modules);

  // Inform the user of final status.
  foreach ($modules as $module) {
    drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), LogLevel::OK);
  }
}

<?php

use Drush\Log\LogLevel;

/**
 * Command callback. Drupal 8 does not support disabled modules.
 *
 * @param array $args
 *   Arguments from the command line.
 */
function _drush_pm_disable($args) {
  drush_include_engine('drupal', 'environment');
  // To be consistent call the environment.inc function which will show the user
  // an error.
  drush_module_disable($args);
}

/**
 * Command callback. Uninstall one or more extensions.
 *
 * @param array $args
 *   Arguments from the command line.
 */
function _drush_pm_uninstall($extensions) {
  $extension_info = drush_get_extensions();
  $required = drush_drupal_required_modules($extension_info);

  // Discards extensions which are enabled, not found or already uninstalled.
  $extensions = array_combine($extensions, $extensions);
  foreach ($extensions as $extension) {
    if (!isset($extension_info[$extension])) {
      unset($extensions[$extension]);
      drush_log(dt('Extension !extension was not found and will not be uninstalled.', array('!extension' => $extension)), LogLevel::WARNING);
    }
    elseif (in_array($extension, $required)) {
      unset($extensions[$extension]);
      $info = $extension_info[$extension]->info;
      $explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation'])))  : '';
      drush_log(dt('!extension is a required extension and can\'t be uninstalled.', array('!extension' => $extension)) . $explanation, LogLevel::OK);
    }
    elseif (!$extension_info[$extension]->status) {
      unset($extensions[$extension]);
      drush_log(dt('!extension is already uninstalled.', array('!extension' => $extension)), LogLevel::OK);
    }
    elseif (drush_extension_get_type($extension_info[$extension]) == 'module') {
      // Add installed dependencies to the list of modules to uninstall.
      foreach (drush_module_dependents(array($extension), $extension_info) as $dependent) {
        // Check if this dependency is not required, already enabled, and not already already in the list of modules to uninstall.
        if (!in_array($dependent, $required) && ($extension_info[$dependent]->status) && !in_array($dependent, $extensions)) {
          $extensions[] = $dependent;
        }
      }
    }
  }

  // Discard default theme.
  $default_theme = drush_theme_get_default();
  if (in_array($default_theme, $extensions)) {
    unset($extensions[$default_theme]);
    drush_log(dt('!theme is the default theme and can\'t be uninstalled.', array('!theme' => $default_theme)), LogLevel::OK);
  }

  // Inform the user which extensions will finally be disabled.
  if (empty($extensions)) {
    return drush_log(dt('There were no extensions that could be uninstalled.'), LogLevel::OK);
  }
  else {
    drush_print(dt('The following extensions will be uninstalled: !extensions', array('!extensions' => implode(', ', $extensions))));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  // Classify extensions in themes and modules.
  $modules = array();
  $themes = array();
  drush_pm_classify_extensions($extensions, $modules, $themes, $extension_info);

  drush_module_uninstall($modules);
  drush_theme_uninstall($themes);

  // Inform the user of final status.
  foreach ($extensions as $extension) {
    drush_log(dt('!extension was successfully uninstalled.', array('!extension' => $extension)), LogLevel::OK);
  }
}
<?php

use Drush\Log\LogLevel;

/**
 * Install Drupal 8+
 */
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
  require_once DRUSH_DRUPAL_CORE . '/includes/install.core.inc';
  $class_loader = drush_drupal_load_autoloader(DRUPAL_ROOT);

  if (!isset($profile)) {
    // If there is an installation profile that acts as a distribution, use that
    // one.
    $install_state = array('interactive' => FALSE) + install_state_defaults();
    try {
      install_begin_request($class_loader, $install_state);
      $profile = _install_select_profile($install_state);
    }
    catch (\Exception $e) {
      // This is only a best effort to provide a better default, no harm done
      // if it fails.
    }
    if (empty($profile)) {
      $profile = 'standard';
    }
  }

  $sql = drush_sql_get_class();
  $db_spec = $sql->db_spec();

  $account_name = drush_get_option('account-name', 'admin');
  $account_pass = drush_get_option('account-pass', FALSE);
  $show_password = drush_get_option('show-passwords', !$account_pass);
  if (!$account_pass) {
    $account_pass = drush_generate_password();
  }
  $settings = array(
    'parameters' => array(
      'profile' => $profile,
      'langcode' => drush_get_option('locale', 'en'),
    ),
    'forms' => array(
      'install_settings_form' => array(
        'driver' => $db_spec['driver'],
        $db_spec['driver'] => $db_spec,
        'op' => dt('Save and continue'),
      ),
      'install_configure_form' => array(
        'site_name' => drush_get_option('site-name', 'Site-Install'),
        'site_mail' => drush_get_option('site-mail', 'admin@example.com'),
        'account' => array(
          'name' => $account_name,
          'mail' => drush_get_option('account-mail', 'admin@example.com'),
          'pass' => array(
            'pass1' => $account_pass,
            'pass2' => $account_pass,
          ),
        ),
        'enable_update_status_module' => TRUE,
        'enable_update_status_emails' => TRUE,
        'clean_url' => drush_get_option('clean-url', TRUE),
        'op' => dt('Save and continue'),
      ),
    ),
  );

  // Merge in the additional options.
  foreach ($additional_form_options as $key => $value) {
    $current = &$settings['forms'];
    foreach (explode('.', $key) as $param) {
      $current = &$current[$param];
    }
    $current = $value;
  }

  $msg = 'Starting Drupal installation. This takes a while.';
  if (is_null(drush_get_option('notify'))) {
    $msg .= ' Consider using the --notify global option.';
  }
  drush_log(dt($msg), LogLevel::OK);
  drush_op('install_drupal', $class_loader, $settings);
  if ($show_password) {
    drush_log(dt('Installation complete.  User name: @name  User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
  }
  else {
    drush_log(dt('Installation complete.'), LogLevel::OK);
  }

}
<?php

use Drush\Log\LogLevel;

/**
 * Install Drupal 6.x
 */
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
  drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), LogLevel::OK);
  if (!isset($profile)) {
    $profile = 'default';
  }
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');

  // We need to disable reporting of E_NOTICE if we want to read the command's output
  // on Windows, because of how Windows is handling output order when using 2>&1
  // redirect added to the command in drush_shell_exec(). We will actually take out
  // all but fatal errors.  See http://drupal.org/node/985716 for more information.
  $phpcode = 'error_reporting(E_ERROR);' . _drush_site_install6_cookies($profile). ' include("'. $drupal_root .'/install.php");';
  drush_shell_exec('php -r %s', $phpcode);
  $cli_output = drush_shell_exec_output();
  $cli_cookie = end($cli_output);

  // We need to bootstrap the database to be able to check the progress of the
  // install batch process since we're not duplicating the install process using
  // drush_batch functions, but calling the process directly.
  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);

  $status = _drush_site_install6_stage($profile, $cli_cookie, "start");
  if ($status === FALSE) {
    return FALSE;
  }

  $status = _drush_site_install6_stage($profile, $cli_cookie, "do_nojs");
  if ($status === FALSE) {
    return FALSE;
  }

  $status = _drush_site_install6_stage($profile, $cli_cookie, "finished");
  if ($status === FALSE) {
    return FALSE;
  }

  $account_name = drush_get_option('account-name', 'admin');
  $account_pass = drush_get_option('account-pass', FALSE);
  $show_password = drush_get_option('show-passwords', !$account_pass);
  if (!$account_pass) {
    $account_pass = drush_generate_password();
  }
  $phpcode = _drush_site_install6_cookies($profile, $cli_cookie);
  $post = array (
    "site_name" => drush_get_option('site-name', 'Site-Install'),
    "site_mail" => drush_get_option('site-mail', 'admin@example.com'),
    "account" => array (
      "name" => $account_name,
      "mail" => drush_get_option('account-mail', 'admin@example.com'),
      "pass" => array (
        "pass1" => $account_pass,
        "pass2" => $account_pass,
      )
    ),
    "date_default_timezone" => "0",
    "clean_url" => drush_get_option('clean-url', TRUE),
    "form_id" => "install_configure_form",
    "update_status_module" => array("1" => "1"),
  );
  // Merge in the additional options.
  foreach ($additional_form_options as $key => $value) {
    $current = &$post;
    foreach (explode('.', $key) as $param) {
      $current = &$current[$param];
    }
    $current = $value;
  }
  $phpcode .= '
  $_POST = ' . var_export($post, true) . ';
  include("'. $drupal_root .'/install.php");';
  drush_shell_exec('php -r %s', $phpcode);

  if ($show_password) {
    drush_log(dt('Installation complete.  User name: @name  User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
  }
  else {
    drush_log(dt('Installation complete.'), LogLevel::OK);
  }
}

/**
 * Submit a given op to install.php; if a meta "Refresh" tag
 * is returned in the result, then submit that op as well.
 */
function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) {
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  // Remember the install task at the start of the stage
  $install_task = _drush_site_install6_install_task();
  $op = $initial_op;
  while (!empty($op)) {
    $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");';
    drush_shell_exec('php -r %s', $phpcode);
    $output = implode("\n", drush_shell_exec_output());
    // Check for a "Refresh" back to the do_nojs op; e.g.:
    //   <meta http-equiv="Refresh" content="0; URL=http://default/install.php?locale=en&profile=wk_profile6&id=1&op=do_nojs">
    // If this pattern is NOT found, then go on to the "finished" step.
    $matches = array();
    $match_result = preg_match('/http-equiv="Refresh".*op=([a-zA-Z0-9_]*)/', $output, $matches);
    if ($match_result) {
      $op = $matches[1];
    }
    else {
      $op = '';
    }
  }
  if (($install_task == _drush_site_install6_install_task()) && ($initial_op != "finished")) {
    return drush_set_error('DRUSH_SITE_INSTALL_FAILED', dt("The site install task '!task' failed.", array('!task' => $install_task)));
  }
  return TRUE;
}

/**
 * Utility function to grab/set current "cli cookie".
 */
function _drush_site_install6_cookies($profile, $cookie = NULL) {
  $drupal_base_url = parse_url(drush_get_context('DRUSH_SELECTED_URI'));
  $output = '$_GET=array("profile"=>"' . $profile . '", "locale"=>"' . drush_get_option('locale', 'en') . '", "id"=>"1"); $_REQUEST=&$_GET;';
  $output .= 'define("DRUSH_SITE_INSTALL6", TRUE);$_SERVER["SERVER_SOFTWARE"] = NULL;';
  $output .= '$_SERVER["SCRIPT_NAME"] = "/install.php";';
  $output .= '$_SERVER["HTTP_HOST"] = "'.$drupal_base_url['host'].'";';
  $output .= '$_SERVER["REMOTE_ADDR"] = "127.0.0.1";';

  if ($cookie) {
    $output .= sprintf('$_COOKIE=unserialize("%s");', str_replace('"', '\"', $cookie));
  }
  else {
    $output .= 'function _cli_cookie_print(){print(serialize(array(session_name()=>session_id())));} register_shutdown_function("_cli_cookie_print");';
  }

  return $output;
}

/**
 * Utility function to check the install_task.  We are
 * not bootstrapped to a high enough level to use variable_get.
 */
function _drush_site_install6_install_task() {
  if ($data = db_result(db_query("SELECT value FROM {variable} WHERE name = 'install_task'",1))) {
    $result = unserialize($data);
  }
  return $result;
}
<?php

use Drush\Log\LogLevel;

/**
 * Install Drupal 7
 */
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
  require_once DRUSH_DRUPAL_CORE . '/includes/install.core.inc';

  if (!isset($profile)) {
    require_once DRUSH_DRUPAL_CORE . '/includes/file.inc';
    require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
    require_once DRUSH_DRUPAL_CORE . '/includes/common.inc';
    require_once DRUSH_DRUPAL_CORE . '/includes/module.inc';

    // If there is an installation profile that is marked as exclusive, use that
    // one.
    try {
      $profile = _install_select_profile(install_find_profiles());
    }
    catch (\Exception $e) {
      // This is only a best effort to provide a better default, no harm done
      // if it fails.
    }
    if (empty($profile)) {
      $profile = 'standard';
    }
  }

  $sql = drush_sql_get_class();
  $db_spec = $sql->db_spec();

  $account_name = drush_get_option('account-name', 'admin');
  $account_pass = drush_get_option('account-pass', FALSE);
  $show_password = drush_get_option('show-passwords', !$account_pass);
  if (!$account_pass) {
    $account_pass = drush_generate_password();
  }
  $settings = array(
    'parameters' => array(
      'profile' => $profile,
      'locale' => drush_get_option('locale', 'en'),
    ),
    'forms' => array(
      'install_settings_form' => array(
        'driver' => $db_spec['driver'],
        $db_spec['driver'] => $db_spec,
        'op' => dt('Save and continue'),
      ),
      'install_configure_form' => array(
        'site_name' => drush_get_option('site-name', 'Site-Install'),
        'site_mail' => drush_get_option('site-mail', 'admin@example.com'),
        'account' => array(
          'name' => $account_name,
          'mail' => drush_get_option('account-mail', 'admin@example.com'),
          'pass' => array(
            'pass1' => $account_pass,
            'pass2' => $account_pass,
          ),
        ),
        'update_status_module' => array(
          1 => TRUE,
          2 => TRUE,
        ),
        'clean_url' => drush_get_option('clean-url', TRUE),
        'op' => dt('Save and continue'),
      ),
    ),
  );

  // Merge in the additional options.
  foreach ($additional_form_options as $key => $value) {
    $current = &$settings['forms'];
    foreach (explode('.', $key) as $param) {
      $current = &$current[$param];
    }
    $current = $value;
  }

  $msg = 'Starting Drupal installation. This takes a while.';
  if (is_null(drush_get_option('notify'))) {
    $msg .= ' Consider using the --notify global option.';
  }
  drush_log(dt($msg), LogLevel::OK);
  drush_op('install_drupal', $settings);
  if ($show_password) {
    drush_log(dt('Installation complete.  User name: @name  User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
  }
  else {
    drush_log(dt('Installation complete.'), LogLevel::OK);
  }
}
<?php
/**
 * @file
 *   Update.php for provisioned sites.
 *   This file is a derivative of the standard drupal update.php,
 *   which has been modified to allow being run from the command
 *   line.
 */

use Drush\Log\LogLevel;

/**
 * Drupal's update.inc has functions that are in previous update_X.inc files
 * for example, update_check_incompatibility() which can prove useful when
 * enabling modules.
 */
require_once DRUSH_DRUPAL_CORE . '/includes/update.inc';

use Drupal\Core\Utility\Error;
use Drupal\Core\Entity\EntityStorageException;
/**
 * Perform one update and store the results which will later be displayed on
 * the finished page.
 *
 * An update function can force the current and all later updates for this
 * module to abort by returning a $ret array with an element like:
 * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
 * The schema version will not be updated in this case, and all the
 * aborted updates will continue to appear on update.php as updates that
 * have not yet been run.
 *
 * @param $module
 *   The module whose update will be run.
 * @param $number
 *   The update number to run.
 * @param $context
 *   The batch context array
 */
function drush_update_do_one($module, $number, $dependency_map,  &$context) {
  $function = $module . '_update_' . $number;

  // If this update was aborted in a previous step, or has a dependency that
  // was aborted in a previous step, go no further.
  if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
    return;
  }

  $context['log'] = FALSE;

  \Drupal::moduleHandler()->loadInclude($module, 'install');

  $ret = array();
  if (function_exists($function)) {
    try {
      if ($context['log']) {
        Database::startLog($function);
      }

      drush_log("Executing " . $function);
      $ret['results']['query'] = $function($context['sandbox']);
      $ret['results']['success'] = TRUE;
    }
    // @TODO We may want to do different error handling for different exception
    // types, but for now we'll just print the message.
    catch (Exception $e) {
      $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
      drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
    }

    if ($context['log']) {
      $ret['queries'] = Database::getLog($function);
    }
  }
  else {
    $ret['#abort'] = array('success' => FALSE);
    drush_set_error('DRUSH_UPDATE_FUNCTION_NOT_FOUND', dt('Update function @function not found', array('@function' => $function)));
  }

  if (isset($context['sandbox']['#finished'])) {
    $context['finished'] = $context['sandbox']['#finished'];
    unset($context['sandbox']['#finished']);
  }

  if (!isset($context['results'][$module])) {
    $context['results'][$module] = array();
  }
  if (!isset($context['results'][$module][$number])) {
    $context['results'][$module][$number] = array();
  }
  $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);

  if (!empty($ret['#abort'])) {
    // Record this function in the list of updates that were aborted.
    $context['results']['#abort'][] = $function;
  }

  // Record the schema update if it was completed successfully.
  if ($context['finished'] == 1 && empty($ret['#abort'])) {
    drupal_set_installed_schema_version($module, $number);
  }

  $context['message'] = 'Performing ' . $function;
}

/**
 * Clears caches and rebuilds the container.
 *
 * This is called in between regular updates and post updates. Do not use
 * drush_drupal_cache_clear_all() as the cache clearing and container rebuild
 * must happen in the same process that the updates are run in.
 *
 * Drupal core's update.php uses drupal_flush_all_caches() directly without
 * explicitly rebuilding the container as the container is rebuilt on the next
 * HTTP request of the batch.
 *
 * @see drush_drupal_cache_clear_all()
 * @see \Drupal\system\Controller\DbUpdateController::triggerBatch()
 */
function drush_update_cache_rebuild() {
  drupal_flush_all_caches();
  \Drupal::service('kernel')->rebuildContainer();
}

function update_main() {
  // In D8, we expect to be in full bootstrap.
  drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);

  require_once DRUPAL_ROOT . '/core/includes/install.inc';
  require_once DRUPAL_ROOT . '/core/includes/update.inc';
  drupal_load_updates();
  update_fix_compatibility();

  // Check requirements before updating.
  if (!drush_update_check_requirements()) {
    if (!drush_confirm(dt('Requirements check reports errors. Do you wish to continue?'))) {
        return drush_user_abort();
    }
  }

  // Pending hook_update_N() implementations.
  $pending = update_get_update_list();

  // Pending hook_post_update_X() implementations.
  $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation();

  $start = array();

  $change_summary = [];
  if (drush_get_option('entity-updates', FALSE)) {
    $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
  }

  // Print a list of pending updates for this module and get confirmation.
  if (count($pending) || count($change_summary) || count($post_updates)) {
    drush_print(dt('The following updates are pending:'));
    drush_print();

    foreach ($change_summary as $entity_type_id => $changes) {
      drush_print($entity_type_id . ' entity type : ');
      foreach ($changes as $change) {
        drush_print(strip_tags($change), 2);
      }
    }

    foreach (array('update', 'post_update') as $update_type) {
      $updates = $update_type == 'update' ? $pending : $post_updates;
      foreach ($updates as $module => $updates) {
        if (isset($updates['start'])) {
          drush_print($module . ' module : ');
          if (!empty($updates['pending'])) {
            $start += [$module => array()];

            $start[$module] = array_merge($start[$module], $updates['pending']);
            foreach ($updates['pending'] as $update) {
              drush_print(strip_tags($update), 2);
            }
          }
          drush_print();
        }
      }
    }

    if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
      return drush_user_abort();
    }

    drush_update_batch($start);
  }
  else {
    drush_log(dt("No database updates required"), LogLevel::SUCCESS);
  }

  return count($pending) + count($change_summary) + count($post_updates);
}

/**
 * Check update requirements and report any errors.
 */
function drush_update_check_requirements() {
  $continue = TRUE;

  \Drupal::moduleHandler()->resetImplementations();
  $requirements = update_check_requirements();
  $severity = drupal_requirements_severity($requirements);

  // If there are issues, report them.
  if ($severity != REQUIREMENT_OK) {
    if ($severity === REQUIREMENT_ERROR) {
      $continue = FALSE;
    }
    foreach ($requirements as $requirement) {
      if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
        $message = isset($requirement['description']) ? $requirement['description'] : '';
        if (isset($requirement['value']) && $requirement['value']) {
          $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
        }
        $log_level = $requirement['severity'] === REQUIREMENT_ERROR ? LogLevel::ERROR : LogLevel::WARNING;
        drush_log($message, $log_level);
      }
    }
  }

  return $continue;
}

function _update_batch_command($id) {
  // In D8, we expect to be in full bootstrap.
  drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);

  drush_batch_command($id);
}

/**
 * Start the database update batch process.
 */
function drush_update_batch() {
  $start = drush_get_update_list();
  // Resolve any update dependencies to determine the actual updates that will
  // be run and the order they will be run in.
  $updates = update_resolve_dependencies($start);

  // Store the dependencies for each update function in an array which the
  // batch API can pass in to the batch operation each time it is called. (We
  // do not store the entire update dependency array here because it is
  // potentially very large.)
  $dependency_map = array();
  foreach ($updates as $function => $update) {
    $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
  }

  $operations = array();

  foreach ($updates as $update) {
    if ($update['allowed']) {
      // Set the installed version of each module so updates will start at the
      // correct place. (The updates are already sorted, so we can simply base
      // this on the first one we come across in the above foreach loop.)
      if (isset($start[$update['module']])) {
        drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
        unset($start[$update['module']]);
      }
      // Add this update function to the batch.
      $function = $update['module'] . '_update_' . $update['number'];
      $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
    }
  }

  // Apply post update hooks.
  $post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions();
  if ($post_updates) {
    $operations[] = ['drush_update_cache_rebuild', []];
    foreach ($post_updates as $function) {
      $operations[] = ['update_invoke_post_update', [$function]];
    }
  }

  // Lastly, perform entity definition updates, which will update storage
  // schema if needed. If module update functions need to work with specific
  // entity schema they should call the entity update service for the specific
  // update themselves.
  // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate()
  // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate()
  if (drush_get_option('entity-updates', FALSE) &&  \Drupal::entityDefinitionUpdateManager()->needsUpdates()) {
    $operations[] = array('drush_update_entity_definitions', array());
  }

  $batch['operations'] = $operations;
  $batch += array(
    'title' => 'Updating',
    'init_message' => 'Starting updates',
    'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
    'finished' => 'drush_update_finished',
    'file' => 'includes/update.inc',
  );
  batch_set($batch);
  \Drupal::service('state')->set('system.maintenance_mode', TRUE);
  drush_backend_batch_process('updatedb-batch-process');
  \Drupal::service('state')->set('system.maintenance_mode', FALSE);
}

/**
 * Apply entity schema updates.
 */
function drush_update_entity_definitions(&$context) {
  try {
    \Drupal::entityDefinitionUpdateManager()->applyUpdates();
  }
  catch (EntityStorageException $e) {
    watchdog_exception('update', $e);
    $variables = Error::decodeException($e);
    unset($variables['backtrace']);
    // The exception message is run through
    // \Drupal\Component\Utility\SafeMarkup::checkPlain() by
    // \Drupal\Core\Utility\Error::decodeException().
    $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
    $context['results']['core']['update_entity_definitions'] = $ret;
    $context['results']['#abort'][] = 'update_entity_definitions';
  }
}

// Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates.
function drush_get_update_list() {
  $return = array();
  $updates = update_get_update_list();
  foreach ($updates as $module => $update) {
    $return[$module] = $update['start'];
  }

  return $return;
}

/**
 * Process and display any returned update output.
 *
 * @see \Drupal\system\Controller\DbUpdateController::batchFinished()
 * @see \Drupal\system\Controller\DbUpdateController::results()
 */
function drush_update_finished($success, $results, $operations) {

  if (!drush_get_option('cache-clear', TRUE)) {
    drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING);
  }
  else {
    drupal_flush_all_caches();
  }

  foreach ($results as $module => $updates) {
    if ($module != '#abort') {
      foreach ($updates as $number => $queries) {
        foreach ($queries as $query) {
          // If there is no message for this update, don't show anything.
          if (empty($query['query'])) {
            continue;
          }

          if ($query['success']) {
            drush_log(strip_tags($query['query']));
          }
          else {
            drush_set_error(dt('Failed: ') . strip_tags($query['query']));
          }
        }
      }
    }
  }
}

/**
 * Return a 2 item array with
 *  - an array where each item is a 3 item associative array describing a pending update.
 *  - an array listing the first update to run, keyed by module.
 */
function updatedb_status() {
  $pending = update_get_update_list();

  $return = array();
  // Ensure system module's updates run first.
  $start['system'] = array();

  foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) {
    foreach ($changes as $change) {
      $return[] = array(
        'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change));
    }
  }

  // Print a list of pending updates for this module and get confirmation.
  foreach ($pending as $module => $updates) {
    if (isset($updates['start']))  {
      foreach ($updates['pending'] as $update_id => $description) {
        // Strip cruft from front.
        $description = str_replace($update_id . ' -   ', '', $description);
        $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
      }
      if (isset($updates['start'])) {
        $start[$module] = $updates['start'];
      }
    }
  }

  return array($return, $start);
}

/**
 * Apply pending entity schema updates.
 */
function entity_updates_main() {
  $change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
  if (!empty($change_summary)) {
    drush_print(dt('The following updates are pending:'));
    drush_print();

    foreach ($change_summary as $entity_type_id => $changes) {
      drush_print($entity_type_id . ' entity type : ');
      foreach ($changes as $change) {
        drush_print(strip_tags($change), 2);
      }
    }

    if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
      return drush_user_abort();
    }

    $operations[] = array('drush_update_entity_definitions', array());


    $batch['operations'] = $operations;
    $batch += array(
      'title' => 'Updating',
      'init_message' => 'Starting updates',
      'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
      'finished' => 'drush_update_finished',
      'file' => 'includes/update.inc',
    );
    batch_set($batch);
    \Drupal::service('state')->set('system.maintenance_mode', TRUE);
    drush_backend_batch_process('updatedb-batch-process');
    \Drupal::service('state')->set('system.maintenance_mode', FALSE);
  }
  else {
    drush_log(dt("No entity schema updates required"), LogLevel::SUCCESS);
  }
}
<?php
/**
 * @file
 *   Update.php for provisioned sites.
 *   This file is a derivative of the standard drupal update.php,
 *   which has been modified to allow being run from the command
 *   line.
 */

use Drush\Log\LogLevel;

define('MAINTENANCE_MODE', 'update');


/**
 * Add a column to a database using syntax appropriate for PostgreSQL.
 * Save result of SQL commands in $ret array.
 *
 * Note: when you add a column with NOT NULL and you are not sure if there are
 * already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL
 * won't work when the table is not empty, and db_add_column() will fail.
 * To have an empty string as the default, you must use: 'default' => "''"
 * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL
 * version will set values of the added column in old rows to the
 * DEFAULT value.
 *
 * @param $ret
 *   Array to which results will be added.
 * @param $table
 *   Name of the table, without {}
 * @param $column
 *   Name of the column
 * @param $type
 *   Type of column
 * @param $attributes
 *   Additional optional attributes. Recognized attributes:
 *     not null => TRUE|FALSE
 *     default  => NULL|FALSE|value (the value must be enclosed in '' marks)
 * @return
 *   nothing, but modifies $ret parameter.
 */
function db_add_column(&$ret, $table, $column, $type, $attributes = array()) {
  if (array_key_exists('not null', $attributes) and $attributes['not null']) {
    $not_null = 'NOT NULL';
  }
  if (array_key_exists('default', $attributes)) {
    if (!isset($attributes['default'])) {
      $default_val = 'NULL';
      $default = 'default NULL';
    }
    elseif ($attributes['default'] === FALSE) {
      $default = '';
    }
    else {
      $default_val = "$attributes[default]";
      $default = "default $attributes[default]";
    }
  }

  $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
  if (!empty($default)) {
    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default");
  }
  if (!empty($not_null)) {
    if (!empty($default)) {
      $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val");
    }
    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
  }
}

/**
 * Change a column definition using syntax appropriate for PostgreSQL.
 * Save result of SQL commands in $ret array.
 *
 * Remember that changing a column definition involves adding a new column
 * and dropping an old one. This means that any indices, primary keys and
 * sequences from serial-type columns are dropped and might need to be
 * recreated.
 *
 * @param $ret
 *   Array to which results will be added.
 * @param $table
 *   Name of the table, without {}
 * @param $column
 *   Name of the column to change
 * @param $column_new
 *   New name for the column (set to the same as $column if you don't want to change the name)
 * @param $type
 *   Type of column
 * @param $attributes
 *   Additional optional attributes. Recognized attributes:
 *     not null => TRUE|FALSE
 *     default  => NULL|FALSE|value (with or without '', it won't be added)
 * @return
 *   nothing, but modifies $ret parameter.
 */
function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) {
  if (array_key_exists('not null', $attributes) and $attributes['not null']) {
    $not_null = 'NOT NULL';
  }
  if (array_key_exists('default', $attributes)) {
    if (!isset($attributes['default'])) {
      $default_val = 'NULL';
      $default = 'default NULL';
    }
    elseif ($attributes['default'] === FALSE) {
      $default = '';
    }
    else {
      $default_val = "$attributes[default]";
      $default = "default $attributes[default]";
    }
  }

  $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old");
  $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type");
  $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old");
  if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); }
  if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); }
  $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
}


/**
 * Disable anything in the {system} table that is not compatible with the
 * current version of Drupal core.
 */
function update_fix_compatibility() {
  $ret = array();
  $incompatible = array();
  $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
  while ($result = db_fetch_object($query)) {
    if (update_check_incompatibility($result->name, $result->type)) {
      $incompatible[] = $result->name;
      drush_log(dt("!type !name is incompatible with this release of Drupal, and will be disabled.",
        array("!type" => $result->type, '!name' => $result->name)), LogLevel::WARNING);
    }
  }
  if (!empty($incompatible)) {

    $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')");
  }
  return $ret;
}

/**
 * Helper function to test compatibility of a module or theme.
 */
function update_check_incompatibility($name, $type = 'module') {
  static $themes, $modules;

  // Store values of expensive functions for future use.
  if (empty($themes) || empty($modules)) {
    drush_include_engine('drupal', 'environment');
    $themes = _system_theme_data();
    $modules = module_rebuild_cache();
  }

  if ($type == 'module' && isset($modules[$name])) {
    $file = $modules[$name];
  }
  else if ($type == 'theme' && isset($themes[$name])) {
    $file = $themes[$name];
  }
  if (!isset($file)
      || !isset($file->info['core'])
      || $file->info['core'] != drush_get_drupal_core_compatibility()
      || version_compare(phpversion(), $file->info['php']) < 0) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Perform Drupal 5.x to 6.x updates that are required for update.php
 * to function properly.
 *
 * This function runs when update.php is run the first time for 6.x,
 * even before updates are selected or performed.  It is important
 * that if updates are not ultimately performed that no changes are
 * made which make it impossible to continue using the prior version.
 * Just adding columns is safe.  However, renaming the
 * system.description column to owner is not.  Therefore, we add the
 * system.owner column and leave it to system_update_6008() to copy
 * the data from description and remove description. The same for
 * renaming locales_target.locale to locales_target.language, which
 * will be finished by locale_update_6002().
 */
function update_fix_d6_requirements() {
  $ret = array();

  if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) {
    $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE);
    db_add_field($ret, 'cache', 'serialized', $spec);
    db_add_field($ret, 'cache_filter', 'serialized', $spec);
    db_add_field($ret, 'cache_page', 'serialized', $spec);
    db_add_field($ret, 'cache_menu', 'serialized', $spec);

    db_add_field($ret, 'system', 'info', array('type' => 'text'));
    db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
    if (db_table_exists('locales_target')) {
      db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => ''));
    }
    if (db_table_exists('locales_source')) {
      db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default'));
      db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none'));
    }
    variable_set('update_d6_requirements', TRUE);

    // Create the cache_block table. See system_update_6027() for more details.
    $schema['cache_block'] = array(
      'fields' => array(
        'cid'        => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
        'data'       => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
        'expire'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
        'created'    => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
        'headers'    => array('type' => 'text', 'not null' => FALSE),
        'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
      ),
      'indexes' => array('expire' => array('expire')),
      'primary key' => array('cid'),
    );
    db_create_table($ret, 'cache_block', $schema['cache_block']);

    // Create the semaphore table now -- the menu system after 6.15 depends on
    // this table, and menu code runs in updates prior to the table being
    // created in its original update function, system_update_6054().
    $schema['semaphore'] = array(
      'fields' => array(
        'name' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => ''),
      'value' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => ''),
      'expire' => array(
        'type' => 'float',
        'size' => 'big',
        'not null' => TRUE),
      ),
     'indexes' => array('expire' => array('expire')),
     'primary key' => array('name'),
    );
    db_create_table($ret, 'semaphore', $schema['semaphore']);
  }

  return $ret;
}

/**
 * Check update requirements and report any errors.
 */
function update_check_requirements() {
  // Check the system module requirements only.
  $requirements = module_invoke('system', 'requirements', 'update');
  $severity = drupal_requirements_severity($requirements);

  // If there are issues, report them.
  if ($severity != REQUIREMENT_OK) {
    foreach ($requirements as $requirement) {
      if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
        $message = isset($requirement['description']) ? $requirement['description'] : '';
        if (isset($requirement['value']) && $requirement['value']) {
          $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
        }
        drush_log($message, LogLevel::WARNING);
      }
    }
  }
}

/**
 * Create the batch table.
 *
 * This is part of the Drupal 5.x to 6.x migration.
 */
function update_create_batch_table() {

  // If batch table exists, update is not necessary
  if (db_table_exists('batch')) {
    return;
  }

  $schema['batch'] = array(
    'fields' => array(
      'bid'       => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
      'token'     => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
      'timestamp' => array('type' => 'int', 'not null' => TRUE),
      'batch'     => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
    ),
    'primary key' => array('bid'),
    'indexes' => array('token' => array('token')),
  );

  $ret = array();
  db_create_table($ret, 'batch', $schema['batch']);
  return $ret;
}

function update_main_prepare() {
  global $profile;
  // Some unavoidable errors happen because the database is not yet up-to-date.
  // Our custom error handler is not yet installed, so we just suppress them.
  drush_errors_off();

  require_once './includes/bootstrap.inc';
  // Minimum load of components.
  // This differs from the Drupal 6 update.php workflow for compatbility with
  // the Drupal 6 backport of module_implements() caching.
  // @see http://drupal.org/node/557542
  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
  require_once './includes/install.inc';
  require_once './includes/file.inc';
  require_once './modules/system/system.install';

  // Load module basics.
  include_once './includes/module.inc';
  $module_list['system']['filename'] = 'modules/system/system.module';
  $module_list['filter']['filename'] = 'modules/filter/filter.module';
  module_list(TRUE, FALSE, FALSE, $module_list);
  module_implements('', FALSE, TRUE);

  drupal_load('module', 'system');
  drupal_load('module', 'filter');

  // Set up $language, since the installer components require it.
  drupal_init_language();

  // Set up theme system for the maintenance page.
  drupal_maintenance_theme();

  // Check the update requirements for Drupal.
  update_check_requirements();

  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
  $profile = variable_get('install_profile', 'default');
  // Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6.
  if (!drush_get_context('DRUSH_USER')) {
    drush_set_option('user', 1);
    drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
  }

  // This must happen *after* drupal_bootstrap(), since it calls
  // variable_(get|set), which only works after a full bootstrap.
  _drush_log_update_sql(update_create_batch_table());

  // Turn error reporting back on. From now on, only fatal errors (which are
  // not passed through the error handler) will cause a message to be printed.
  drush_errors_on();

  // Perform Drupal 5.x to 6.x updates that are required for update.php to function properly.
  _drush_log_update_sql(update_fix_d6_requirements());

  // Must unset $theme->status in order to safely rescan and repopulate
  // the system table to ensure we have a full picture of the platform.
  // This is needed because $theme->status is set to 0 in a call to
  // list_themes() done by drupal_maintenance_theme().
  // It is a issue with _system_theme_data() that returns its own cache
  // variable and can be modififed by others. When this is fixed in
  // drupal core we can remove this unset.
  // For reference see: http://drupal.org/node/762754
  $themes = _system_theme_data();
  foreach ($themes as $theme) {
    unset($theme->status);
  }
  drush_get_extensions();

  include_once './includes/batch.inc';
  drupal_load_updates();

  // Disable anything in the {system} table that is not compatible with the current version of Drupal core.
  _drush_log_update_sql(update_fix_compatibility());
}

function update_main() {
  update_main_prepare();

  list($pending, $start) = updatedb_status();

  // Print a list of pending updates for this module and get confirmation.
  if ($pending) {
    // @todo get table header working
    // array_unshift($pending, array(dt('Module'), dt('ID'), dt('Description')));
    drush_print_table($pending, FALSE);
    if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
      return drush_user_abort();
    }
    // Proceed with running all pending updates.
    $operations = array();
    foreach ($start as $module => $version) {
      drupal_set_installed_schema_version($module, $version - 1);
      $updates = drupal_get_schema_versions($module);
      $max_version = max($updates);
      if ($version <= $max_version) {
        drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version)));
        foreach ($updates as $update) {
          if ($update >= $version) {
            $operations[] = array('_update_do_one', array($module, $update));
          }
        }
      }
      else {
        drush_log(dt('No database updates for module @module', array('@module' => $module)), LogLevel::SUCCESS);
      }
    }
    $batch = array(
      'operations' => $operations,
      'title' => 'Updating',
      'init_message' => 'Starting updates',
      'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
      'finished' => 'update_finished',
    );
    batch_set($batch);
    $batch =& batch_get();
    $batch['progressive'] = FALSE;
    drush_backend_batch_process('updatedb-batch-process');
  }
  else {
    drush_log(dt("No database updates required"), LogLevel::SUCCESS);
  }

  return count($pending);
}

/**
 * A simplified version of the batch_do_one function from update.php
 *
 * This does not mess with sessions and the like, as it will be used
 * from the command line
 */
function _update_do_one($module, $number, &$context) {
  // If updates for this module have been aborted
  // in a previous step, go no further.
  if (!empty($context['results'][$module]['#abort'])) {
    return;
  }

  $function = $module .'_update_'. $number;
  drush_log("Executing $function", LogLevel::SUCCESS);

  if (function_exists($function)) {
    $ret = $function($context['sandbox']);
    $context['results'][$module] = $ret;
    _drush_log_update_sql($ret);
  }

  if (isset($ret['#finished'])) {
    $context['finished'] = $ret['#finished'];
    unset($ret['#finished']);
  }

  if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) {
    drupal_set_installed_schema_version($module, $number);
  }

}

function _update_batch_command($id) {
  update_main_prepare();
  drush_batch_command($id);
}

/**
 * Return a 2 item array with
 *  - an array where each item is a 3 item associative array describing a pending update.
 *  - an array listing the first update to run, keyed by module.
 */
function updatedb_status() {
  $return = array();

  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
  foreach ($modules as $module => $schema_version) {
    $updates = drupal_get_schema_versions($module);
    // Skip incompatible module updates completely, otherwise test schema versions.
    if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
      // module_invoke returns NULL for nonexisting hooks, so if no updates
      // are removed, it will == 0.
      $last_removed = module_invoke($module, 'update_last_removed');
      if ($schema_version < $last_removed) {
        drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.'));
        continue;
      }

      $updates = drupal_map_assoc($updates);

      // Record the starting update number for each module.
      foreach (array_keys($updates) as $update) {
        if ($update > $schema_version) {
          $start[$module] = $update;
          break;
        }
      }
      if (isset($start['system'])) {
        // Ensure system module's updates run first.
        $start = array('system' => $start['system']) + $start;
      }

      // Record any pending updates. Used for confirmation prompt.
      foreach (array_keys($updates) as $update) {
        if ($update > $schema_version) {
          if (class_exists('ReflectionFunction')) {
            // The description for an update comes from its Doxygen.
            $func = new ReflectionFunction($module. '_update_'. $update);
            $description = trim(str_replace(array("\n", '*', '/'), '', $func->getDocComment()));
          }
          if (empty($description)) {
            $description = dt('description not available');
          }

          $return[] = array('module' => ucfirst($module), 'update_id' => $update, 'description' => $description);
        }
      }
    }
  }

  return array($return, $start);
}
<?php
/**
 * @file
 *   Update.php for provisioned sites.
 *   This file is a derivative of the standard drupal update.php,
 *   which has been modified to allow being run from the command
 *   line.
 */

use Drush\Log\LogLevel;

/**
 * Global flag to identify update.php run, and so avoid various unwanted
 * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing
 * and translation, and solve some theming issues. This flag is checked on several
 * places in Drupal code (not just update.php).
 */
define('MAINTENANCE_MODE', 'update');

/**
 * Drupal's update.inc has functions that are in previous update_X.inc files
 * for example, update_check_incompatibility() which can prove useful when
 * enabling modules.
 */
require_once DRUSH_DRUPAL_CORE . '/includes/update.inc';
/**
 * Returns (and optionally stores) extra requirements that only apply during
 * particular parts of the update.php process.
 */
function update_extra_requirements($requirements = NULL) {
  static $extra_requirements = array();
  if (isset($requirements)) {
    $extra_requirements += $requirements;
  }
  return $extra_requirements;
}

/**
 * Perform one update and store the results which will later be displayed on
 * the finished page.
 *
 * An update function can force the current and all later updates for this
 * module to abort by returning a $ret array with an element like:
 * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
 * The schema version will not be updated in this case, and all the
 * aborted updates will continue to appear on update.php as updates that
 * have not yet been run.
 *
 * @param $module
 *   The module whose update will be run.
 * @param $number
 *   The update number to run.
 * @param $context
 *   The batch context array
 */
function drush_update_do_one($module, $number, $dependency_map,  &$context) {
  $function = $module . '_update_' . $number;

  // If this update was aborted in a previous step, or has a dependency that
  // was aborted in a previous step, go no further.
  if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
    return;
  }

  $context['log'] = FALSE;

  $ret = array();
  if (function_exists($function)) {
    try {
      if ($context['log']) {
        Database::startLog($function);
      }

      drush_log("Executing " . $function);
      $ret['results']['query'] = $function($context['sandbox']);

      // If the update hook returned a status message (common in batch updates),
      // show it to the user.
      if ($ret['results']['query']) {
        drush_log($ret['results']['query'], LogLevel::OK);
      }

      $ret['results']['success'] = TRUE;
    }
    // @TODO We may want to do different error handling for different exception
    // types, but for now we'll just print the message.
    catch (Exception $e) {
      $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
      drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
    }

    if ($context['log']) {
      $ret['queries'] = Database::getLog($function);
    }
  }

  if (isset($context['sandbox']['#finished'])) {
    $context['finished'] = $context['sandbox']['#finished'];
    unset($context['sandbox']['#finished']);
  }

  if (!isset($context['results'][$module])) {
    $context['results'][$module] = array();
  }
  if (!isset($context['results'][$module][$number])) {
    $context['results'][$module][$number] = array();
  }
  $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);

  if (!empty($ret['#abort'])) {
    // Record this function in the list of updates that were aborted.
    $context['results']['#abort'][] = $function;
  }

  // Record the schema update if it was completed successfully.
  if ($context['finished'] == 1 && empty($ret['#abort'])) {
    drupal_set_installed_schema_version($module, $number);
  }

  $context['message'] = 'Performed update: ' . $function;
}

/**
 * Check update requirements and report any errors.
 */
function update_check_requirements() {
  $warnings = FALSE;

  // Check the system module and update.php requirements only.
  $requirements = system_requirements('update');
  $requirements += update_extra_requirements();

  // If there are issues, report them.
  foreach ($requirements as $requirement) {
    if (isset($requirement['severity']) && $requirement['severity'] > REQUIREMENT_OK) {
      $message = isset($requirement['description']) ? $requirement['description'] : '';
      if (isset($requirement['value']) && $requirement['value']) {
        $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')';
      }
      $warnings = TRUE;
      drupal_set_message($message, LogLevel::WARNING);
    }
  }
  return $warnings;
}


function update_main_prepare() {
  // Some unavoidable errors happen because the database is not yet up-to-date.
  // Our custom error handler is not yet installed, so we just suppress them.
  drush_errors_off();

  // We prepare a minimal bootstrap for the update requirements check to avoid
  // reaching the PHP memory limit.
  $core = DRUSH_DRUPAL_CORE;
  require_once $core . '/includes/bootstrap.inc';
  require_once $core . '/includes/common.inc';
  require_once $core . '/includes/file.inc';
  require_once $core . '/includes/entity.inc';
  include_once $core . '/includes/unicode.inc';

  update_prepare_d7_bootstrap();
  drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);

  require_once $core . '/includes/install.inc';
  require_once $core . '/modules/system/system.install';

  // Load module basics.
  include_once $core . '/includes/module.inc';
  $module_list['system']['filename'] = 'modules/system/system.module';
  module_list(TRUE, FALSE, FALSE, $module_list);
  drupal_load('module', 'system');

  // Reset the module_implements() cache so that any new hook implementations
  // in updated code are picked up.
  module_implements('', FALSE, TRUE);

  // Set up $language, since the installer components require it.
  drupal_language_initialize();

  // Set up theme system for the maintenance page.
  drupal_maintenance_theme();

  // Check the update requirements for Drupal.
  update_check_requirements();

  // update_fix_d7_requirements() needs to run before bootstrapping beyond path.
  // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc.
  drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);

  update_fix_d7_requirements();

  // Clear the module_implements() cache before the full bootstrap. The calls
  // above to drupal_maintenance_theme() and update_check_requirements() have
  // invoked hooks before all modules have actually been loaded by the full
  // bootstrap. This means that the module_implements() results for any hooks
  // that have been invoked, including hook_module_implements_alter(), is a
  // smaller set of modules than should be returned normally.
  // @see https://github.com/drush-ops/drush/pull/399
  module_implements('', FALSE, TRUE);

  // Now proceed with a full bootstrap.

  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
  drupal_maintenance_theme();

  drush_errors_on();

  include_once DRUPAL_ROOT . '/includes/batch.inc';
  drupal_load_updates();

  update_fix_compatibility();

   // Change query-strings on css/js files to enforce reload for all users.
  _drupal_flush_css_js();
  // Flush the cache of all data for the update status module.
  if (db_table_exists('cache_update')) {
    cache_clear_all('*', 'cache_update', TRUE);
  }

  module_list(TRUE, FALSE, TRUE);
}

function update_main() {
  update_main_prepare();

  list($pending, $start) = updatedb_status();
  if ($pending) {
    // @todo get table header working.
    // $headers = array(dt('Module'), dt('ID'), dt('Description'));
    drush_print_table($pending);
    if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
      return drush_user_abort();
    }
    drush_update_batch($start);
  }
  else {
    drush_log(dt("No database updates required"), LogLevel::SUCCESS);
  }

  return count($pending);
}

function _update_batch_command($id) {
  update_main_prepare();
  drush_batch_command($id);
}

/**
 * Start the database update batch process.
 *
 * @param $start
 *   An array of all the modules and which update to start at.
 * @param $redirect
 *   Path to redirect to when the batch has finished processing.
 * @param $url
 *   URL of the batch processing page (should only be used for separate
 *   scripts like update.php).
 * @param $batch
 *   Optional parameters to pass into the batch API.
 * @param $redirect_callback
 *   (optional) Specify a function to be called to redirect to the progressive
 *   processing page.
 */
function drush_update_batch($start) {
  // Resolve any update dependencies to determine the actual updates that will
  // be run and the order they will be run in.
  $updates = update_resolve_dependencies($start);

  // Store the dependencies for each update function in an array which the
  // batch API can pass in to the batch operation each time it is called. (We
  // do not store the entire update dependency array here because it is
  // potentially very large.)
  $dependency_map = array();
  foreach ($updates as $function => $update) {
    $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
  }

  $operations = array();
  foreach ($updates as $update) {
    if ($update['allowed']) {
      // Set the installed version of each module so updates will start at the
      // correct place. (The updates are already sorted, so we can simply base
      // this on the first one we come across in the above foreach loop.)
      if (isset($start[$update['module']])) {
        drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
        unset($start[$update['module']]);
      }
      // Add this update function to the batch.
      $function = $update['module'] . '_update_' . $update['number'];
      $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
    }
  }

  $batch['operations'] = $operations;
  $batch += array(
    'title' => 'Updating',
    'init_message' => 'Starting updates',
    'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
    'finished' => 'drush_update_finished',
    'file' => 'includes/update.inc',
  );
  batch_set($batch);
  drush_backend_batch_process('updatedb-batch-process');
}



function drush_update_finished($success, $results, $operations) {
  // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback.
}

/**
 * Return a 2 item array with
 *  - an array where each item is a 3 item associative array describing a pending update.
 *  - an array listing the first update to run, keyed by module.
 */
function updatedb_status() {
  $pending = update_get_update_list();

  $return = array();
  // Ensure system module's updates run first.
  $start['system'] = array();

  // Print a list of pending updates for this module and get confirmation.
  foreach ($pending as $module => $updates) {
    if (isset($updates['start']))  {
      foreach ($updates['pending'] as $update_id => $description) {
        // Strip cruft from front.
        $description = str_replace($update_id . ' -   ', '', $description);
        $return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
      }
      if (isset($updates['start'])) {
        $start[$module] = $updates['start'];
      }
    }
  }
  return array($return, $start);
}
<?php

/**
 * @file
 *  Field API's drush integration
 */

/**
 * Implementation of hook_drush_help().
 */
function field_drush_help($section) {
  switch ($section) {
    case 'meta:field:title':
      return dt('Field commands');
    case 'meta:field:summary':
      return dt('Manipulate Drupal 7+ fields.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function field_drush_command() {
  $items['field-create'] = array(
    'description' => 'Create fields and instances. Returns urls for field editing.',
    'core' => array('7+'),
    'arguments' => array(
      'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.',
      'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.'
    ),
    'required-arguments' => 1,
    'options' => array(
      'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.',
    ),
    'examples' => array(
      'drush field-create article' => 'Define new article fields via interactive prompts.',
      'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.',
      'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.'
    ),
  );
  $items['field-update'] = array(
    'description' => 'Return URL for field editing web page.',
    'core' => array('7+'),
    'arguments' => array(
      'field_name' => 'Name of field that needs updating.',
    ),
    'examples' => array(
      'field-update comment_body' => 'Quickly navigate to a field edit web page.',
    ),
  );
  $items['field-delete'] = array(
    'description' => 'Delete a field and its instances.',
    'core' => array('7+'),
    'arguments' => array(
      'field_name' => 'Name of field to delete.',
    ),
    'options' => array(
      'bundle' => 'Only delete the instance attached to this bundle. If omitted, admin can choose to delete one instance or whole field.',
      'entity_type' => 'Disambiguate a particular bundle from identically named bundles. Usually not needed.'
    ),
    'examples' => array(
      'field-delete city' => 'Delete the city field and any instances it might have.',
      'field-delete city --bundle=article' => 'Delete the city instance on the article bundle',
    ),
  );
  $items['field-clone'] = array(
    'description' => 'Clone a field and all its instances.',
    'core' => array('7+'),
    'arguments' => array(
      'source_field_name' => 'Name of field that will be cloned',
      'target_field_name' => 'Name of new, cloned field.',
    ),
    'examples' => array(
      'field-clone tags labels' => 'Copy \'tags\' field into a new field \'labels\' field which has same instances.',
      'open `field-clone tags labels`' => 'Clone field and then open field edit forms for refinement.',
    ),
  );
  $items['field-info'] = array(
    'description' => 'View information about fields, field_types, and widgets.',
    'core' => array('7+'),
    'arguments' => array(
      'type' => 'Recognized values: fields, types. If omitted, a choice list appears.',
    ),
    'examples' => array(
      'field-info types' => 'Show a table which lists all field types and their available widgets',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'csv',
      'field-labels' => array(
        'field-name' => 'Field name',
        'type' => 'Field type',
        'bundle' => 'Field bundle',
        'type-name' => 'Type name',
        'widget' => 'Default widget',
        'widgets' => 'Widgets',
      ),
      'table-metadata' => array(
        'process-cell' => '_drush_field_info_process_cell',
      ),
      'output-data-type' => 'format-table',
    ),
  );
  return $items;
}

/**
 * Command argument complete callback.
 */
function field_field_create_complete() {
  if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    $all = array();
    $info = field_info_bundles();
    foreach ($info as $entity_type => $bundles) {
      $all = array_merge($all, array_keys($bundles));
    }
    return array('values' => array_unique($bundles));
  }
}

/**
 * Command argument complete callback.
 */
function field_field_update_complete() {
  return field_field_complete_field_names();
}

/**
 * Command argument complete callback.
 */
function field_field_delete_complete() {
  return field_field_complete_field_names();
}

/**
 * Command argument complete callback.
 */
function field_field_clone_complete() {
  return field_field_complete_field_names();
}

/**
 * Command argument complete callback.
 */
function field_field_info_complete() {
  return array('values' => array('fields', 'types'));
}

/**
 * List field names for completion.
 *
 * @return
 *  Array of available site aliases.
 */
function field_field_complete_field_names() {
  if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    $info = field_info_fields();
    return array('values' => array_keys($info));
  }
}

function drush_field_create($bundle) {
  $entity_type = drush_get_option('entity_type', 'node');

  $args = func_get_args();
  array_shift($args);
  if (empty($args)) {
    // Just one item in this array for now.
    $args[] = drush_field_create_wizard();
  }

  // Iterate over each field spec.
  foreach ($args as $string) {
    list($name, $type, $widget) = explode(',', $string);
    $info = field_info_field($name);
    if (empty($info)) {
      // Field does not exist already. Create it.
      $field = array(
        'field_name' => $name,
        'type' => $type,
      );
      drush_op('field_create_field', $field);
    }

    // Create the instance.
    $instance = array(
      'field_name' => $name,
      'entity_type' => $entity_type,
      'bundle' => $bundle,
    );
    if ($widget) {
      $instance['widget'] = array('type' => $widget);
    }
    drush_op('field_create_instance', $instance);

    $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE));
  }
  drush_print(implode(' ', $urls));
}

// Copy of function _field_ui_bundle_admin_path() since we don't want to load UI module.
function drush_field_ui_bundle_admin_path($entity_type, $bundle_name) {
  $bundles = field_info_bundles($entity_type);
  $bundle_info = $bundles[$bundle_name];
  if (isset($bundle_info['admin'])) {
    return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path'];
  }
}

function drush_field_update($field_name) {
   $info = field_info_field($field_name);
   foreach ($info['bundles'] as $entity_type => $bundles) {
     foreach ($bundles as $bundle) {
       $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $field_name, array('absolute' => TRUE));
     }
   }
  drush_print(implode(' ', $urls));
}

function drush_field_delete($field_name) {
  $info = field_info_field($field_name);
  $confirm = TRUE;

  if (!$bundle = drush_get_option('bundle')) {
    foreach ($info['bundles'] as $entity_type => $bundles) {
      foreach ($bundles as $bundle) {
        $all_bundles[] = $bundle;
      }
    }
    if (count($all_bundles) > 1) {
      $options = array_merge(array('all' => dt('All bundles')), array_combine($all_bundles, $all_bundles));
      $bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'"));
      if (!$bundle) {
        return drush_user_abort();
      }
      $confirm = FALSE;
    }
    else {
      if (!drush_confirm(dt('Do you want to delete the !field_name field?', array('!field_name' => $field_name)))) {
        return drush_user_abort();
      }
    }
  }

  if ($bundle == 'all') {
    foreach ($info['bundles'] as $entity_type => $bundles) {
       foreach ($bundles as $bundle) {
         $instance = field_info_instance($entity_type, $field_name, $bundle);
         drush_op('field_delete_instance', $instance);
       }
     }
  }
  else {
    $entity_type = drush_field_get_entity_from_bundle($bundle);
    $instance = field_info_instance($entity_type, $field_name, $bundle);
    drush_op('field_delete_instance', $instance);
  }

  // If there are no more bundles, delete the field.
  $info = field_info_field($field_name);
  if (empty($info['bundles'])) {
    drush_op('field_delete_field', $field_name);
  }
}

function drush_field_clone($source_field_name, $target_field_name) {
   if (!$info = field_info_field($source_field_name)) {
     return drush_set_error(dt('!source not found in field list.', array('!source' => $source_field_name)));
   }

   unset($info['id']);
   $info['field_name'] = $target_field_name;
   $target = drush_op('field_create_field', $info);

   foreach ($info['bundles'] as $entity_type => $bundles) {
     foreach ($bundles as $bundle) {
       $instance = field_info_instance($entity_type, $source_field_name, $bundle);
       $instance['field_name'] = $target_field_name;
       unset($instance['id']);
       $instance['field_id'] = $target['id'];
       drush_op('field_create_instance', $instance);
       $urls[] = drush_url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $target_field_name, array('absolute' => TRUE));
     }
   }

  drush_print(implode(' ', $urls));
}

function drush_field_info($type = NULL) {
  if (!isset($type)) {
    // Don't ask in 'pipe' mode -- just default to 'fields'.
    if (drush_get_context('DRUSH_PIPE')) {
      $type = 'fields';
    }
    else {
      $type = drush_choice(array_combine(array('types', 'fields'), array('types', 'fields')), dt('Which information do you wish to see?'));
    }
  }

  $result = array();
  switch ($type) {
    case 'fields':
      drush_hide_output_fields(array('type-name', 'widget', 'widgets'));
      $info = field_info_fields();
      foreach ($info as $field_name => $field) {
        $bundle_strs = array();
        foreach ($field['bundles'] as $entity_type => $bundles) {
          $bundle_strs += $bundles;
        }
        $result[$field_name] = array(
          'field-name' => $field_name,
          'type' => $field['type'],
          'bundle' => $bundle_strs,
        );
      }
      break;
    case 'types':
      drush_hide_output_fields(array('field-name', 'type', 'bundle'));
      $info = field_info_field_types();
      module_load_include('inc', 'field_ui', 'field_ui.admin');
      $widgets = field_info_widget_types();
      foreach ($info as $type_name => $type) {
        $widgets = field_ui_widget_type_options($type_name);
        $result[$type_name] = array(
          'type-name' => $type_name,
          'widget' => $type['default_widget'],
          'widgets' => $widgets,
        );
      }
      break;
    default:
      return drush_set_error('DRUSH_FIELD_INVALID_SELECTION', dt("Argument for drush field-info must be 'fields' or 'types'"));
  }

  return $result;
}

/**
 * We need to handle the formatting of cells in table-format
 * output specially.  In 'types' output, the output data is a simple
 * associative array of machine names => human-readable names.
 * We choose to show the machine names.  In 'fields' output, the
 * output data is a list of entity types, each of which contains a list
 * of bundles.  We comma-separate the bundles, and space-separate
 * the entities.
 */
function _drush_field_info_process_cell($data, $metadata) {
  $first = reset($data);
  if (is_array($first)) {
    foreach($data as $entity => $bundles) {
      $list[] = drush_format($bundles, array(), 'csv');
    }
    return drush_format($list, array(), 'list');
  }
  return drush_format(array_keys($data), array(), 'csv');
}

/**
 * Prompt user enough to create basic field and instance.
 *
 * @return array $field_spec
 *   An array of brief field specifications.
 */
function drush_field_create_wizard() {
  $specs[] = drush_prompt(dt('Field name'));
  module_load_include('inc', 'field_ui', 'field_ui.admin');
  $types = field_ui_field_type_options();
  $field_type = drush_choice($types, dt('Choose a field type'));
  $specs[] = $field_type;
  $widgets = field_ui_widget_type_options($field_type);
  $specs[] = drush_choice($widgets, dt('Choose a widget'));
  return implode(',', $specs);
}

function drush_field_get_entity_from_bundle($bundle) {
  if (drush_get_option('entity_type')) {
    return drush_get_option('entity_type');
  }
  else {
    $info = field_info_bundles();
    foreach ($info as $entity_type => $bundles) {
      if (isset($bundles[$bundle])) {
        return $entity_type;
      }
    }
  }
}
<?php

/**
 * Implementation of hook_drush_help().
 *
 * This function is called whenever a drush user calls
 * 'drush help <name-of-your-command>'
 *
 * @param
 *   A string with the help section (prepend with 'drush:')
 *
 * @return
 *   A string with the help text for your command.
 */
function help_drush_help($section) {
  switch ($section) {
    case 'drush:help':
      return dt("Drush provides an extensive help system that describes both drush commands and topics of general interest.  Use `drush help --filter` to present a list of command categories to view, and `drush topic` for a list of topics that go more in-depth on how to use and extend drush.");
  }
}

/**
 * Implementation of hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * @return
 *   An associative array describing your command(s).
 */
function help_drush_command() {
  $items = array();

  $items['help'] = array(
    'description' => 'Print this help message. See `drush help help` for more options.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'allow-additional-options' => array('helpsingle'),
    'options' => array(
      'sort' => 'Sort commands in alphabetical order. Drush waits for full bootstrap before printing any commands when this option is used.',
      'filter' => array(
        'description' => 'Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.',
        'example-value' => 'category',
        'value' => 'optional',
      ),
    ),
    'arguments' => array(
      'command' => 'A command name, or command alias.',
    ),
    'examples' => array(
      'drush' => 'List all commands.',
      'drush --filter=devel_generate' => 'Show only commands defined in devel_generate.drush.inc',
      'drush help pm-download' => 'Show help for one command.',
      'drush help dl' => 'Show help for one command using an alias.',
      'drush help --format=html' => 'Show an HTML page detailing all available commands.',
      'drush help --format=json' => 'All available comamnds, in a machine parseable format.',
    ),
    // Use output format system for all formats except the default presentation.
    'outputformat' => array(
      'default' => 'table',
      'field-labels' => array('name' => 'Name', 'description' => 'Description'),
      'output-data-type' => 'format-table',
    ),
    'topics' => array('docs-readme'),
  );
  return $items;
}


/**
 * Command argument complete callback.
 *
 * For now, this can't move to helpsingle since help command is the entry point for both.
 *
 * @return
 *   Array of available command names.
 */
function core_help_complete() {
  return array('values' => array_keys(drush_get_commands()));
}

/**
 * Command callback for help command. This is the default command, when none
 * other has been specified.
 */
function drush_core_help($name = '') {
  $format = drush_get_option('format', 'table');
  if ($name) {
    // helpsingle command builds output when a command is specified.
    $options = drush_redispatch_get_options();
    if ($name != 'help') {
      unset($options['help']);
    }
    $return = drush_invoke_process('@self' ,'helpsingle', func_get_args(), $options);
    drush_backend_set_result($return['object']);
    return;
  }

  // For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION.
  drush_bootstrap_max();
  drush_get_commands(true);
  $implemented = drush_get_commands();
  ksort($implemented);
  $command_categories = drush_commands_categorize($implemented);
  if ($format != 'table') {
    return $command_categories;
  }
  else {
    $visible = drush_help_visible($command_categories);

    // If the user specified --filter w/out a value, then
    // present a choice list of help categories.
    if (drush_get_option('filter', FALSE) === TRUE) {
      $help_categories = array();
      foreach ($command_categories as $key => $info) {
        $description = $info['title'];
        if (array_key_exists('summary', $info)) {
          $description .= ": " . $info['summary'];
        }
        $help_categories[$key] = $description;
      }
      $result = drush_choice($help_categories, 'Select a help category:');
      if (!$result) {
        return drush_user_abort();
      }
      drush_set_option('filter', $result);
    }
    // Filter out categories that the user does not want to see
    $filter_category = drush_get_option('filter');
    if (!empty($filter_category) && ($filter_category !== TRUE)) {
      if (!array_key_exists($filter_category, $command_categories)) {
        return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category)));
      }
      $command_categories = array($filter_category => $command_categories[$filter_category]);
    }

    // Make a fake command section to hold the global options, then print it.
    $global_options_help = drush_global_options_command(TRUE);
    if (!drush_get_option('filter')) {
      drush_print_help($global_options_help);
    }
    drush_help_listing_print($command_categories);
    drush_backend_set_result($command_categories);
    return;
  }
}

// Uncategorize the list of commands. Hiddens have been removed and
// filtering performed.
function drush_help_visible($command_categories) {
  $all = array();
  foreach ($command_categories as $category => $info) {
    $all = array_merge($all,  $info['commands']);
  }
  return $all;
}

/**
 * Print CLI table listing all commands.
 */
function drush_help_listing_print($command_categories) {
  $all_commands = array();
  foreach ($command_categories as $key => $info) {
    // Get the commands in this category.
    $commands = $info['commands'];

    // Build rows for drush_print_table().
    $rows = array();
    foreach($commands as $cmd => $command) {
      $name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd;
      $rows[$cmd] = array('name' => $name, 'description' => $command['description']);
    }
    drush_print($info['title'] . ": (" . $key . ")");
    drush_print_table($rows, FALSE, array('name' => 20));
  }
}

/**
 * Build a fake command for the purposes of showing examples and options.
 */
function drush_global_options_command($brief = FALSE) {
  $global_options_help = array(
    'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help.  Run `drush topic` to read even more documentation.',
    'sections' => array(
      'options' => 'Global options (see `drush topic core-global-options` for the full list)',
    ),
    'options' => drush_get_global_options($brief),
    'examples' => array(
      'drush dl cck zen' => 'Download CCK module and Zen theme.',
      'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.',
    ),
    '#brief' => TRUE,
  );
  $global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__);
  drush_command_invoke_all_ref('drush_help_alter', $global_options_help);
  ksort($global_options_help['options']);

  return $global_options_help;
}
<?php

/**
 * Implementation of hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * @return
 *   An associative array describing your command(s).
 */
function helpsingle_drush_command() {
  $items = array();
  $items['helpsingle'] = array(
    'description' => 'Print help for a single command',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'allow-additional-options' => TRUE,
    'hidden' => TRUE,
    'arguments' => array(
      'command' => 'A command name, or command alias.',
    ),
    'examples' => array(
      'drush help pm-download' => 'Show help for one command.',
      'drush help dl' => 'Show help for one command using an alias.',
    ),
    'topics' => array('docs-readme'),
  );
  return $items;
}

/**
 * Command callback. Show help for a single command.
 */
function drush_core_helpsingle($commandstring) {
  // First check and see if the command can already be found.
  $commands = drush_get_commands();
  if (!array_key_exists($commandstring, $commands)) {
    // If the command cannot be found, then bootstrap so that
    // additional commands will be brought in.
    // TODO: We need to do a full bootstrap in order to find module service
    // commands. We only need to do this for Drupal 8, though; 7 and earlier
    // can stop at DRUSH_BOOTSTRAP_DRUPAL_SITE. Perhaps we could use command
    // caching to avoid bootstrapping, if we have collected the commands for
    // this site once already.
    drush_bootstrap_max();
    $commands = drush_get_commands();
  }
  if (array_key_exists($commandstring, $commands)) {
    $command = $commands[$commandstring];

    annotationcommand_adapter_add_hook_options($command);

    drush_print_help($command);
    return TRUE;
  }
  $shell_aliases = drush_get_context('shell-aliases', array());
  if (array_key_exists($commandstring, $shell_aliases)) {
    $msg = dt("'@alias-name' is a shell alias.  Its value is: !name. See `drush topic docs-shell-aliases` and `drush shell-alias` for more information.", array('@alias-name' => $commandstring, '!name' => $shell_aliases[$commandstring]));
    drush_log($msg, 'ok');
    return TRUE;
  }
  return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring)));
}

/**
 * Print the help for a single command to the screen.
 *
 * @param array $command
 *   A fully loaded $command array.
 */
function drush_print_help($command) {
  _drush_help_merge_subcommand_information($command);

  if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) {
    $help = array($command['description']);
  }

  if ($command['strict-option-handling']) {
    $command['topics'][] = 'docs-strict-options';
  }

  // Give commandfiles an opportunity to add examples and options to the command.
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  drush_engine_add_help_topics($command);
  drush_command_invoke_all_ref('drush_help_alter', $command);

  drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80)));
  drush_print();

  $global_options = drush_get_global_options();
  foreach ($command['global-options'] as $global_option) {
    $command['options'][$global_option] = $global_options[$global_option];
  }

  // Sort command options.
  uksort($command['options'], '_drush_help_sort_command_options');

  // Print command sections help.
  foreach ($command['sections'] as $key => $value) {
    if (!empty($command[$key])) {
      $rows = drush_format_help_section($command, $key);
      if ($rows) {
        drush_print(dt($value) . ':');
        drush_print_table($rows, FALSE, array('label' => 40));
        unset($rows);
        drush_print();
      }
    }
  }

  // Append aliases if any.
  if ($command['aliases']) {
    drush_print(dt("Aliases: ") . implode(', ', $command['aliases']));
  }
}

/**
 * Sort command options alphabetically. Engine options at the end.
 */
function _drush_help_sort_command_options($a, $b) {
  $engine_a = strpos($a, '=');
  $engine_b = strpos($b, '=');
  if ($engine_a && !$engine_b) {
    return 1;
  }
  else if (!$engine_a && $engine_b) {
    return -1;
  }
  elseif ($engine_a && $engine_b) {
    if (substr($a, 0, $engine_a) == substr($b, 0, $engine_b)) {
      return 0;
    }
  }
  return ($a < $b) ? -1 : 1;
}

/**
 * Check to see if the specified command contains an 'allow-additional-options'
 * record.  If it does, find the additional options that are allowed, and
 * add in the help text for the options of all of the sub-commands.
 */
function _drush_help_merge_subcommand_information(&$command) {
  // 'allow-additional-options' will either be FALSE (default),
  // TRUE ("allow anything"), or an array that lists subcommands
  // that are or may be called via drush_invoke by this command.
  if (is_array($command['allow-additional-options'])) {
    $implemented = drush_get_commands();
    foreach ($command['allow-additional-options'] as $subcommand_name) {
      if (array_key_exists($subcommand_name, $implemented)) {
        $command['options'] += $implemented[$subcommand_name]['options'];
        $command['sub-options'] = array_merge_recursive($command['sub-options'], $implemented[$subcommand_name]['sub-options']);
        if (empty($command['arguments'])) {
          $command['arguments'] = $implemented[$subcommand_name]['arguments'];
        }
        $command['topics'] = array_merge($command['topics'], $implemented[$subcommand_name]['topics']);
      }
    }
  }
}

/**
 * Format one named help section from a command record
 *
 * @param $command
 *   A command record with help information
 * @param $section
 *   The name of the section to format ('options', 'topic', etc.)
 * @returns array
 *   Formatted rows, suitable for printing via drush_print_table. The returned
 *   array can be empty.
 */
function drush_format_help_section($command, $section) {
  $rows = array();
  $formatter = (function_exists('drush_help_section_formatter_' . $section)) ? 'drush_help_section_formatter_' . $section : 'drush_help_section_default_formatter';
  foreach ($command[$section] as $name => $help_attributes) {
    if (!is_array($help_attributes)) {
      $help_attributes = array('description' => $help_attributes);
    }
    $help_attributes['label'] = $name;
    call_user_func_array($formatter, array($command, &$help_attributes));
    if (empty($help_attributes['hidden'])) {
      $rows[] = array('label' => $help_attributes['label'], 'description' => $help_attributes['description']);
      // Process the subsections too, if any
      if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) {
        $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter));
      }
    }
  }
  return $rows;
}

/**
 * Format one named portion of a subsection from a command record.
 * Subsections allow related parts of a help record to be grouped
 * together.  For example, in the 'options' section, sub-options that
 * are related to a particular primary option are stored in a 'sub-options'
 * section whose name == the name of the primary option.
 *
 * @param $command
 *   A command record with help information
 * @param $section
 *   The name of the section to format ('options', 'topic', etc.)
 * @param $subsection
 *   The name of the subsection (e.g. the name of the primary option)
 * @param $formatter
 *   The name of a function to use to format the rows of the subsection
 * @param $prefix
 *   Characters to prefix to the front of the label (for indentation)
 * @returns array
 *   Formatted rows, suitable for printing via drush_print_table.
 */
function _drush_format_help_subsection($command, $section, $subsection, $formatter, $prefix = '  ') {
  $rows = array();
  foreach ($command['sub-' . $section][$subsection] as $name => $help_attributes) {
    if (!is_array($help_attributes)) {
      $help_attributes = array('description' => $help_attributes);
    }
    $help_attributes['label'] = $name;
    call_user_func_array($formatter, array($command, &$help_attributes));
    if (!array_key_exists('hidden', $help_attributes)) {
      $rows[] = array('label' => $prefix . $help_attributes['label'], 'description' => $help_attributes['description']);
      // Process the subsections too, if any
      if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) {
        $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter, $prefix . '  '));
      }
    }
  }
  return $rows;
}

/**
 * The options section formatter.  Adds a "--" in front of each
 * item label.  Also handles short-form and example-value
 * components in the help attributes.
 */
function drush_help_section_formatter_options($command, &$help_attributes) {
  if ($help_attributes['label'][0] == '-') {
    drush_log(dt("Option '!option' of command !command should instead be declared as '!fixed'", array('!option' => $help_attributes['label'], '!command' => $command['command'], '!fixed' => preg_replace('/^--*/', '', $help_attributes['label']))), 'debug');
  }
  else {
    $help_attributes['label'] = '--' . $help_attributes['label'];
  }
  if (!empty($help_attributes['required'])) {
    $help_attributes['description'] .= " " . dt("Required.");
  }

  $prefix = '<';
  $suffix = '>';
  if (array_key_exists('example-value', $help_attributes)) {
    if (isset($help_attributes['value']) && $help_attributes['value'] == 'optional') {
      $prefix = '[';
      $suffix = ']';
    }
    $help_attributes['label'] .= '=' . $prefix . $help_attributes['example-value'] . $suffix;

    if (array_key_exists('short-form', $help_attributes)) {
      $help_attributes['short-form'] .= " $prefix" . $help_attributes['example-value'] . $suffix;
    }
  }
  if (array_key_exists('short-form', $help_attributes)) {
    $help_attributes['label'] = '-' . $help_attributes['short-form'] . ', ' . $help_attributes['label'];
  }
  drush_help_section_default_formatter($command, $help_attributes);
}

/**
 * The default section formatter.  Replaces '[command]' with the
 * command name.
 */
function drush_help_section_default_formatter($command, &$help_attributes) {
  // '[command]' is a token representing the current command. @see pm_drush_engine_version_control().
  $help_attributes['label'] = str_replace('[command]', $command['command'], $help_attributes['label']);
}
<?php

/**
 * @file
 *  Image module's drush integration.
 *
 *  @todo image-build($field_name, $bundle, $style_name)
 */

use Drush\Log\LogLevel;

/**
 * Implementation of hook_drush_command().
 */
function image_drush_command() {
  $items['image-flush'] = array(
    'description' => 'Flush all derived images for a given style.',
    'core' => array('7+'),
    'arguments' => array(
      'style' => 'An image style machine name. If not provided, user may choose from a list of names.',
    ),
    'options' => array(
      'all' => 'Flush all derived images',
    ),
    'examples' => array(
      'drush image-flush' => 'Pick an image style and then delete its images.',
      'drush image-flush thumbnail' => 'Delete all thumbnail images.',
      'drush image-flush --all' => 'Flush all derived images. They will be regenerated on the fly.',
    ),
    'aliases' => array('if'),
  );
  $items['image-derive'] = array(
    'description' => 'Create an image derivative.',
    'core' => array('7+'),
    'drupal dependencies' => array('image'),
    'arguments' => array(
      'style' => 'An image style machine name.',
      'source' => 'Path to a source image. Optionally prepend stream wrapper scheme.',
    ),
    'required arguments' => TRUE,
    'options' => array(),
    'examples' => array(
      'drush image-derive thumbnail themes/bartik/logo.png' => 'Save thumbnail sized derivative of logo image.',
    ),
    'aliases' => array('id'),
  );
  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function image_drush_help_alter(&$command) {
  // Drupal 8+ customizations.
  if ($command['command'] == 'image-derive' && drush_drupal_major_version() >= 8) {
    unset($command['examples']);
    $command['examples']['drush image-derive thumbnail core/themes/bartik/logo.png'] = 'Save thumbnail sized derivative of logo image.';
  }
}

/**
 * Command argument complete callback.
 *
 * @return
 *   Array of available configuration files for editing.
 */
function image_image_flush_complete() {
  drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
  drush_include_engine('drupal', 'image');
  return array('values' => array_keys(drush_image_styles()));
}

function drush_image_flush_pre_validate($style_name = NULL) {
  drush_include_engine('drupal', 'image');
  if (!empty($style_name) && !$style = drush_image_style_load($style_name)) {
    return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
  }
}

function drush_image_flush($style_name = NULL) {
  drush_include_engine('drupal', 'image');
  if (drush_get_option('all')) {
    $style_name = 'all';
  }

  if (empty($style_name)) {
    $styles = array_keys(drush_image_styles());
    $choices = array_combine($styles, $styles);
    $choices = array_merge(array('all' => 'all'), $choices);
    $style_name = drush_choice($choices, dt("Choose a style to flush."));
    if ($style_name === FALSE) {
      return drush_user_abort();
    }
  }

  if ($style_name == 'all') {
    foreach (drush_image_styles() as $style_name => $style) {
      drush_image_flush_single($style_name);
    }
    drush_log(dt('All image styles flushed'), LogLevel::SUCCESS);
  }
  else {
    drush_image_flush_single($style_name);
  }
}

function drush_image_derive_validate($style_name, $source) {
  drush_include_engine('drupal', 'image');
  if (!$style = drush_image_style_load($style_name)) {
    return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
  }

  if (!file_exists($source)) {
    return drush_set_error(dt('Source file not found - !file.', array('!file' => $source)));
  }
}

/*
 * Command callback. Create an image derivative.
 *
 * @param string $style_name
 *   The name of an image style.
 *
 * @param string $source
 *   The path to a source image, relative to Drupal root.
 */
function drush_image_derive($style_name, $source) {
  drush_include_engine('drupal', 'image');
  return _drush_image_derive($style_name, $source);
}
<?php

/**
 * @file
 *   Set up local Drush configuration.
 */

use Drush\Log\LogLevel;

/**
 * Implementation of hook_drush_command().
 *
 * @return
 *   An associative array describing your command(s).
 */
function init_drush_command() {
  $items['core-init'] = array(
    'description' => 'Enrich the bash startup file with completion and aliases. Copy .drushrc file to ~/.drush',
    'aliases' => array('init'),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'package' => 'core',
    'global-options' => array('editor', 'bg'),
    'options' => array(
      'edit' => 'Open the new config file in an editor.',
      'add-path' => "Always add Drush to the \$PATH in the user's .bashrc file, even if it is already in the \$PATH. Use --no-add-path to skip updating .bashrc with the Drush \$PATH. Default is to update .bashrc only if Drush is not already in the \$PATH.",
    ),
    'examples' => array(
      'drush core-init --edit' => 'Enrich Bash and open drush config file in editor.',
      'drush core-init --edit --bg' => 'Return to shell prompt as soon as the editor window opens.',
    ),
  );
  return $items;
}

/**
 * Initialize local Drush configuration
 */
function drush_init_core_init() {
  $home = drush_server_home();
  $drush_config_dir = $home . "/.drush";
  $drush_config_file = $drush_config_dir . "/drushrc.php";
  $drush_bashrc = $drush_config_dir . "/drush.bashrc";
  $drush_prompt = $drush_config_dir . "/drush.prompt.sh";
  $drush_complete = $drush_config_dir . "/drush.complete.sh";
  $examples_dir = DRUSH_BASE_PATH . "/examples";
  $example_configuration = $examples_dir . "/example.drushrc.php";
  $example_bashrc = $examples_dir . "/example.bashrc";
  $example_prompt = $examples_dir . "/example.prompt.sh";
  $example_complete = DRUSH_BASE_PATH . "/drush.complete.sh";
  $bashrc_additions = array();

  // Create a ~/.drush directory if it does not yet exist
  if (!is_dir($drush_config_dir)) {
    drush_mkdir($drush_config_dir);
  }

  // If there is no ~/.drush/drushrc.php, then copy the
  // example Drush configuration file here
  if (!is_file($drush_config_file)) {
    copy($example_configuration, $drush_config_file);
    drush_log(dt("Copied example Drush configuration file to !path", array('!path' => $drush_config_file)), LogLevel::OK);
  }

  // If Drush is not in the $PATH, then figure out which
  // path to add so that Drush can be found globally.
  $add_path = drush_get_option('add-path', NULL);
  if ((!drush_which("drush") || $add_path) && ($add_path !== FALSE)) {
    $drush_path = drush_find_path_to_drush($home);
    $drush_path = preg_replace("%^" . preg_quote($home) . "/%", '$HOME/', $drush_path);

    $bashrc_additions["%$drush_path%"] = "\n# Path to Drush, added by 'drush init'.\nexport PATH=\"\$PATH:$drush_path\"\n\n";
  }

  // If there is no ~/.drush/drush.bashrc file, then copy
  // the example bashrc file there
  if (!is_file($drush_bashrc)) {
    copy($example_bashrc, $drush_bashrc);
    $pattern = basename($drush_bashrc);
    $bashrc_additions["%$pattern%"] = "\n# Include Drush bash customizations.". drush_bash_addition($drush_bashrc);
    drush_log(dt("Copied example Drush bash configuration file to !path", array('!path' => $drush_bashrc)), LogLevel::OK);
  }

  // If there is no ~/.drush/drush.complete.sh file, then copy it there
  if (!is_file($drush_complete)) {
    copy($example_complete, $drush_complete);
    $pattern = basename($drush_complete);
    $bashrc_additions["%$pattern%"] = "\n# Include Drush completion.\n". drush_bash_addition($drush_complete);
    drush_log(dt("Copied Drush completion file to !path", array('!path' => $drush_complete)), LogLevel::OK);
  }

  // If there is no ~/.drush/drush.prompt.sh file, then copy
  // the example prompt.sh file here
  if (!is_file($drush_prompt)) {
    copy($example_prompt, $drush_prompt);
    $pattern = basename($drush_prompt);
    $bashrc_additions["%$pattern%"] = "\n# Include Drush prompt customizations.\n". drush_bash_addition($drush_prompt);
    drush_log(dt("Copied example Drush prompt file to !path", array('!path' => $drush_prompt)), LogLevel::OK);
  }

  // Decide whether we want to add our Bash commands to
  // ~/.bashrc or ~/.bash_profile
  $bashrc = drush_init_find_bashrc($home);

  // Modify the user's bashrc file, adding our customizations.
  $bashrc_contents = "";
  if (file_exists($bashrc)) {
    $bashrc_contents = file_get_contents($bashrc);
  }
  $new_bashrc_contents = $bashrc_contents;
  foreach ($bashrc_additions as $pattern => $addition) {
    // Only put in the addition if the pattern does not already
    // exist in the bashrc file.
    if (!preg_match($pattern, $new_bashrc_contents)) {
      $new_bashrc_contents = $new_bashrc_contents . $addition;
    }
  }
  if ($new_bashrc_contents != $bashrc_contents) {
    if (drush_confirm(dt(implode('', $bashrc_additions) . "Append the above code to !file?", array('!file' => $bashrc)))) {
      file_put_contents($bashrc, "\n\n". $new_bashrc_contents);
      drush_log(dt("Updated bash configuration file !path", array('!path' => $bashrc)), LogLevel::OK);
      drush_log(dt("Start a new shell in order to experience the improvements (e.g. `bash`)."), LogLevel::OK);
      if (drush_get_option('edit')) {
        $exec = drush_get_editor();
        drush_shell_exec_interactive($exec, $drush_config_file, $drush_config_file);
      }
    }
    else {
      return drush_user_abort();
    }
  }
  else {
    drush_log(dt('No code added to !path', array('!path' => $bashrc)), LogLevel::OK);
  }
}

/**
 * Determine which .bashrc file is best to use on this platform.
 */
function drush_init_find_bashrc($home) {
  return $home . "/.bashrc";
}

/**
 * Determine where Drush is located, so that we can add
 * that location to the $PATH
 */
function drush_find_path_to_drush($home) {
  // First test: is Drush inside a vendor directory?
  // Does vendor/bin exist?  If so, use that.  We do
  // not have a good way to locate the 'bin' directory
  // if it has been relocated in the composer.json config
  // section.
  if ($vendor_pos = strpos(DRUSH_BASE_PATH, "/vendor/")) {
    $vendor_dir = substr(DRUSH_BASE_PATH, 0, $vendor_pos + 7);
    $vendor_bin = $vendor_dir . '/bin';
    if (is_dir($vendor_bin)) {
      return $vendor_bin;
    }
  }

  // Fallback is to use the directory that Drush is in.
  return DRUSH_BASE_PATH;
}

function drush_bash_addition($file) {
  return <<<EOD

if [ -f "$file" ] ; then
  source $file
fi


EOD;
}
<?php

/**
 * @file
 * Provides Drush commands related to Interface Translation.
 */

/**
 * Implementation of hook_drush_help().
 */
function locale_drush_help($section) {
  switch ($section) {
    case 'meta:locale:title':
      return dt('Interface translation');
    case 'meta:locale:summary':
      return dt('Interact with the interface translation system.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function locale_drush_command() {
  $items['locale-check'] = [
    'description' => 'Checks for available translation updates.',
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  ];
  $items['locale-update'] = [
    'description' => 'Updates the available translations.',
    'options' => [
      'langcodes' => 'A comma-separated list of language codes to update. If omitted, all translations will be updated.'
    ],
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  ];
  // @todo Implement proper export and import commands.
  return $items;
}

/**
 * Checks for available translation updates.
 *
 * @see \Drupal\locale\Controller\LocaleController::checkTranslation()
 *
 * @todo This can be simplified once https://www.drupal.org/node/2631584 lands
 *   in Drupal core.
 */
function drush_locale_check() {
  \Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.compare');

  // Check translation status of all translatable project in all languages.
  // First we clear the cached list of projects. Although not strictly
  // necessary, this is helpful in case the project list is out of sync.
  locale_translation_flush_projects();
  locale_translation_check_projects();

  // Execute a batch if required. A batch is only used when remote files
  // are checked.
  if (batch_get()) {
    drush_backend_batch_process();
  }
}

/**
 * Imports the available translation updates.
 *
 * @see TranslationStatusForm::buildForm()
 * @see TranslationStatusForm::prepareUpdateData()
 * @see TranslationStatusForm::submitForm()
 *
 * @todo This can be simplified once https://www.drupal.org/node/2631584 lands
 *   in Drupal core.
 */
function drush_locale_update() {
  $module_handler = \Drupal::moduleHandler();
  $module_handler->loadInclude('locale', 'fetch.inc');
  $module_handler->loadInclude('locale', 'bulk.inc');

  $langcodes = [];
  foreach (locale_translation_get_status() as $project_id => $project) {
    foreach ($project as $langcode => $project_info) {
      if (!empty($project_info->type)) {
        $langcodes[] = $langcode;
      }
    }
  }

  if ($passed_langcodes = drush_get_option('langcodes')) {
    $langcodes = array_intersect($langcodes, explode(',', $passed_langcodes));
    // @todo Not selecting any language code in the user interface results in
    //   all translations being updated, so we mimick that behavior here.
  }
  // Deduplicate the list of langcodes since each project may have added the
  // same language several times.
  $langcodes = array_unique($langcodes);

  // @todo Restricting by projects is not possible in the user interface and is
  //   broken when attempting to do it in a hook_form_alter() implementation so
  //   we do not allow for it here either.
  $projects = [];

  // Set the translation import options. This determines if existing
  // translations will be overwritten by imported strings.
  $options = _locale_translation_default_update_options();

  // If the status was updated recently we can immediately start fetching the
  // translation updates. If the status is expired we clear it an run a batch to
  // update the status and then fetch the translation updates.
  $last_checked = \Drupal::state()->get('locale.translation_last_checked');
  if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
    locale_translation_clear_status();
    $batch = locale_translation_batch_update_build(array(), $langcodes, $options);
    batch_set($batch);
  }
  else {
    // Set a batch to download and import translations.
    $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options);
    batch_set($batch);
    // Set a batch to update configuration as well.
    if ($batch = locale_config_batch_update_components($options, $langcodes)) {
      batch_set($batch);
    }
  }

  drush_backend_batch_process();
}
<?php
/**
 * @file
 * Add system notifications as a new drush option.
 */

/**
 * @todo there are no hooks fired after a command errors out.
 */
register_shutdown_function('drush_notify_shutdown');

/**
 * Implements hook_drush_help_alter().
 */
function notify_drush_help_alter(&$command) {
  if ($command['command'] == 'global-options') {
    // Do not include these in options in standard help.
    if ($command['#brief'] === FALSE) {
      $command['options']['notify'] = array(
        'description' => 'Use system notifications to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.',
        'example-value' => 60,
        'never-propagate' => TRUE,
      );
      $command['options']['notify-audio'] = array(
        'description' => 'Trigger an audio alert to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.',
        'example-value' => 60,
        'never-propagate' => TRUE,
      );
      $command['sub-options']['notify']['notify-cmd'] = array(
        'description' => 'Specify the shell command to trigger the notification.',
        'never-propagate' => TRUE,
      );
      $command['sub-options']['notify']['notify-cmd-audio'] = array(
        'description' => 'Specify the shell command to trigger the audio notification.',
        'never-propagate' => TRUE,
      );
    }
  }
}

/**
 * Implements hook_drush_help().
 */
function notify_drush_help($section) {
  switch ($section) {
    case 'notify:cache-clear':
      return dt('Caches have been cleared.');
    case 'notify:site-install:error':
      return dt('Failed on site installation');
  }
}

/**
 * Shutdown function to signal on errors.
 */
function drush_notify_shutdown() {
  $cmd = drush_get_command();

  if (empty($cmd['command'])) {
    return;
  }

  // pm-download handles its own notification.
  if ($cmd['command'] != 'pm-download' && drush_notify_allowed($cmd['command'])) {
    $msg = dt("Command '!command' completed.", array('!command' => $cmd['command']));
    drush_notify_send(drush_notify_command_message($cmd['command'], $msg));
  }

  if (drush_get_option('notify', FALSE) && drush_get_error()) {
    // If the only error is that notify failed, do not try to notify again.
    $log = drush_get_error_log();
    if (count($log) == 1 && array_key_exists('NOTIFY_COMMAND_NOT_FOUND', $log)) {
      return;
    }

    // Send an alert that the command failed.
    if (drush_notify_allowed($cmd['command'])) {
      $msg = dt("Command '!command' failed.", array('!command' => $cmd['command']));
      drush_notify_send(drush_notify_command_message($cmd['command'] . ':error', $msg));
    }
  }
}

/**
 * Determine the message to send on command completion.
 *
 * @param string $command
 *   Name of the Drush command for which we check message overrides.
 * @param string $default
 *   (Default: NULL) Default message to use if there are not notification message overrides.
 *
 * @return string
 *   Message to use for notification.
 */
function drush_notify_command_message($command, $default = NULL) {
  if ($msg = drush_command_invoke_all('drush_help', 'notify:' . $command)) {
    $msg = implode("\n", $msg);
  }
  else {
    $msg = $default ? $default : $msg = $command . ': No news is good news.';
  }

  return $msg;
}

/**
 * Prepares and dispatches notifications to delivery mechanisms.
 *
 * You may avoid routing a message to secondary messaging mechanisms (e.g. audio),
 * by direct use of the delivery functions.
 *
 * @param string $msg
 *   Message to send via notification.
 */
function drush_notify_send($msg) {
  drush_notify_send_text($msg);
  if (drush_get_option('notify-audio', FALSE)) {
    drush_notify_send_audio($msg);
  }
}

/**
 * Send text-based system notification.
 *
 * This is the automatic, default behavior. It is intended for use with tools
 * such as libnotify in Linux and Notification Center on OSX.
 *
 * @param string $msg
 *   Message text for delivery.
 *
 * @return bool
 *   TRUE on success, FALSE on failure
 */
function drush_notify_send_text($msg) {
  $override = drush_get_option('notify-cmd', FALSE);

  if (!empty($override)) {
    $cmd = $override;
  }
  else {
    switch (PHP_OS) {
      case 'Darwin':
        $cmd = 'terminal-notifier -message %s -title Drush';
        $error_message = dt('terminal-notifier command failed. Please install it from https://github.com/alloy/terminal-notifier.');
        break;
      case 'Linux':
      default:
        $icon = drush_normalize_path(DRUSH_BASE_PATH . '/drush_logo-black.png');
        $cmd = "notify-send %s -i $icon";
        $error_message = dt('notify-send command failed. Please install it as per http://coderstalk.blogspot.com/2010/02/how-to-install-notify-send-in-ubuntu.html.');
        break;
    }
  }

  if (!drush_shell_exec($cmd, $msg)) {
    return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', $error_message . ' ' . dt('Or you may specify an alternate command to run by specifying --notify-cmd=<my_command>'));
  }

  return TRUE;
}

/**
 * Send an audio-based system notification.
 *
 * This function is only automatically invoked with the additional use of the
 * --notify-audio flag or configuration state.
 *
 * @param $msg
 *   Message for audio recital.
 *
 * @return bool
 *   TRUE on success, FALSE on failure
 */
function drush_notify_send_audio($msg) {
  $override = drush_get_option('notify-cmd-audio', FALSE);

  if (!empty($override)) {
    $cmd = $override;
  }
  else {
    switch (PHP_OS) {
      case 'Darwin':
        $cmd = 'say %s';
        break;
      case 'Linux':
      default:
        $cmd = drush_get_option('notify-cmd-audio', 'spd-say') . ' %s';
    }
  }

  if (!drush_shell_exec($cmd, $msg)) {
    return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', dt('The third party notification utility failed.'));
  }
}

/**
 * Identify if the given Drush request should trigger a notification.
 *
 * @param $command
 *   Name of the command.
 *
 * @return
 *   Boolean
 */
function drush_notify_allowed($command) {
  $notify = drush_get_option(array('notify', 'notify-audio'), FALSE);
  $execution = time() - $_SERVER['REQUEST_TIME'];

  return ($notify === TRUE ||
    (is_numeric($notify) && $notify > 0 && $execution > $notify));
}

<?php

/**
 * @file
 *   Core drush output formats.
 */

/**
 * @return drush_outputformat The selected output format engine
 */
function drush_get_outputformat() {
  return drush_get_engine('outputformat');
}

/**
 * Dynamically switch to a new output format.  Does NOT override
 * user-selected output format.
 */
function drush_set_default_outputformat($format, $metadata = array()) {
  $command = drush_get_command();
  $command['engines']['outputformat']['default'] = $format;
  $outputformat = drush_load_command_engine($command, 'outputformat', $metadata);
}

/**
 * Given a command name or a command record, return the
 * command formatter that is used to process that command's output.
 */
function drush_get_command_format_metadata($command, $metadata = array()) {
  $commands = drush_get_commands();
  if (!is_array($command) && array_key_exists($command, $commands)) {
    $command = $commands[$command];
  }
  return drush_get_command_engine_config($command, 'outputformat', $metadata);
}

/**
 * Implementation of hook_drush_engine_type_info().
 */
function outputformat_drush_engine_type_info() {
  $info = array();
  $info['outputformat'] = array(
    'description' => 'Output formatting options selection and use.',
    'topic' => 'docs-output-formats',
    'topic-file' => 'docs/output-formats.md',
    'combine-help' => TRUE,
    'option' => 'format',
    'options' => array(
      'format' => array(
        'description' => 'Select output format.',
        'example-value' => 'json',
      ),
      'fields' => array(
        'description' => 'Fields to output.',
        'example-value' => 'field1,field2',
        'value' => 'required',
        'list' => TRUE,
      ),
      'list-separator' => array(
        'description' => 'Specify how elements in a list should be separated. In lists of lists, this applies to the elements in the inner lists.',
        'hidden' => TRUE,
      ),
      'line-separator' => array(
        'description' => 'In nested lists of lists, specify how the outer lists ("lines") should be separated.',
        'hidden' => TRUE,
      ),
      'field-labels' => array(
        'description' => 'Add field labels before first line of data. Default is on; use --no-field-labels to disable.',
        'default' => '1',
        'key' => 'include-field-labels',
      ),
    ),
    // Allow output formats to declare their
    // "output data type" instead of their
    // "required engine capability" for readability.
    'config-aliases' => array(
      'output-data-type' => 'require-engine-capability',
    ),
  );
  return $info;
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 *
 * The output format types supported are represented by
 * the 'engine-capabilities' of the output format engine.
 * The different capabilities include:
 *
 * format-single:       A simple string.
 *
 * format-list:         An associative array where the key
 *                      is usually the row label, and the value
 *                      is a simple string.  Some list formatters
 *                      render the label, and others (like
 *                      "list" and "csv") throw it away.
 *
 * format-table:        An associative array, where the key
 *                      is the row id, and the value is the
 *                      column data.  The column data is also
 *                      an associative array where the key
 *                      is the column id and the value is the
 *                      cell data.  The cell data should usually
 *                      be a simple string; however, some
 *                      formatters can recursively format their
 *                      cell contents before rendering (e.g. if
 *                      a cell contains a list of items in an array).
 *
 * These definitions align with the declared 'output-data-type'
 * declared in command records.  @see drush_parse_command().
 *
 * Any output format that does not declare any engine capabilities
 * is expected to be able to render any php data structure that is
 * passed to it.
 */
function outputformat_drush_engine_outputformat() {
  $common_topic_example = array(
    "a" => array("b" => 2, "c" => 3),
    "d" => array("e" => 5, "f" => 6)
  );

  $engines = array();
  $engines['table'] = array(
    'description' => 'A formatted, word-wrapped table.',
    'engine-capabilities' => array('format-table'),
  );
  $engines['key-value'] = array(
    'description' => 'A formatted list of key-value pairs.',
    'engine-capabilities' => array('format-single', 'format-list', 'format-table'),
    'hidden' => TRUE,
  );
  $engines['key-value-list'] = array(
    'implemented-by' => 'list',
    'list-item-type' => 'key-value',
    'description' => 'A list of formatted lists of key-value pairs.',
    'list-field-selection-control' => 1,
    'engine-capabilities' => array('format-table'),
    'hidden' => TRUE,
  );
  $engines['json'] = array(
    'machine-parsable' => TRUE,
    'description' => 'Javascript Object Notation.',
    'topic-example' => $common_topic_example,
  );
  $engines['string'] = array(
    'machine-parsable' => TRUE,
    'description' => 'A simple string.',
    'engine-capabilities' => array('format-single'),
  );
  $engines['message'] = array(
    'machine-parsable' => FALSE, // depends on the label....
    'hidden' => TRUE,
  );
  $engines['print-r'] = array(
    'machine-parsable' => TRUE,
    'description' => 'Output via php print_r function.',
    'verbose-only' => TRUE,
    'topic-example' => $common_topic_example,
  );
  $engines['var_export'] = array(
    'machine-parsable' => TRUE,
    'description' => 'An array in executable php format.',
    'topic-example' => $common_topic_example,
  );
  $engines['yaml'] = array(
    'machine-parsable' => TRUE,
    'description' => 'Yaml output format.',
    'topic-example' => $common_topic_example,
  );
  $engines['php'] = array(
    'machine-parsable' => TRUE,
    'description' => 'A serialized php string.',
    'verbose-only' => TRUE,
    'topic-example' => $common_topic_example,
  );
  $engines['config'] = array(
    'machine-parsable' => TRUE,
    'implemented-by' => 'list',
    'list-item-type' => 'var_export',
    'description' => "A configuration file in executable php format. The variable name is \"config\", and the variable keys are taken from the output data array's keys.",
    'metadata' => array(
      'variable-name' => 'config',
    ),
    'list-field-selection-control' => -1,
    'engine-capabilities' => array('format-list','format-table'),
    'verbose-only' => TRUE,
  );
  $engines['list'] = array(
    'machine-parsable' => TRUE,
    'list-item-type' => 'string',
    'description' => 'A simple list of values.',
    // When a table is printed as a list, only the array keys of the rows will print.
    'engine-capabilities' => array('format-list', 'format-table'),
    'topic-example' => array('a', 'b', 'c'),
  );
  $engines['nested-csv'] = array(
    'machine-parsable' => TRUE,
    'implemented-by' => 'list',
    'list-separator' => ',',
    'list-item-type' => 'csv-or-string',
    'hidden' => TRUE,
  );
  $engines['csv-or-string'] = array(
    'machine-parsable' => TRUE,
    'hidden' => TRUE,
  );
  $engines['csv'] = array(
    'machine-parsable' => TRUE,
    'implemented-by' => 'list',
    'list-item-type' => 'nested-csv',
    'labeled-list' => TRUE,
    'description' => 'A list of values, one per row, each of which is a comma-separated list of values.',
    'engine-capabilities' => array('format-table'),
    'topic-example' => array(array('a', 12, 'a@one.com'),array('b', 17, 'b@two.com')),
  );
  $engines['variables'] = array(
    'machine-parsable' => TRUE,
    'description' => 'A list of php variable assignments.',
    'engine-capabilities' => array('format-table'),
    'verbose-only' => TRUE,
    'list-field-selection-control' => -1,
    'topic-example' => $common_topic_example,
  );
  $engines['labeled-export'] = array(
    'machine-parsable' => TRUE,
    'description' => 'A list of php exports, labeled with a name.',
    'engine-capabilities' => array('format-table'),
    'verbose-only' => TRUE,
    'implemented-by' => 'list',
    'list-item-type' => 'var_export',
    'metadata' => array(
      'label-template' => '!label: !value',
    ),
    'list-field-selection-control' => -1,
    'topic-example' => $common_topic_example,
  );
  $engines['html'] = array(
    'machine-parsable' => FALSE,
    'description' => 'An HTML representation',
    'engine-capabilities' => array('format-table'),
  );
  return $engines;
}

/**
 * Implements hook_drush_command_alter
 */
function outputformat_drush_command_alter(&$command) {
  // In --pipe mode, change the default format to the default pipe format, or
  // to json, if no default pipe format is given.
  if (drush_get_context('DRUSH_PIPE') && (isset($command['engines']['outputformat']))) {
    $default_format = isset($command['engines']['outputformat']['pipe-format']) ? $command['engines']['outputformat']['pipe-format'] : 'json';
    $command['engines']['outputformat']['default'] = $default_format;
  }
}

/**
 * Implements hook_drush_help_alter().
 */
function outputformat_drush_help_alter(&$command) {
  if (isset($command['engines']['outputformat'])) {
    $outputformat = $command['engines']['outputformat'];
    // If the command defines specific field labels,
    // then modify the help for --fields to include
    // specific information about the available fields.
    if (isset($outputformat['field-labels'])) {
      $all_fields = array();
      $all_fields_description = array();
      foreach ($outputformat['field-labels'] as $field => $human_readable) {
        $all_fields[] = $field;
        if ((strtolower($field) != strtolower($human_readable)) && !array_key_exists(strtolower($human_readable), $outputformat['field-labels'])) {
          $all_fields_description[] = $field . dt(" (or '!other')", array('!other' => strtolower($human_readable)));
        }
        else {
          $all_fields_description[] = $field;
        }
      }
      $field_defaults = isset($outputformat['fields-default']) ? $outputformat['fields-default'] : $all_fields;
      $command['options']['fields']['example-value'] = implode(', ', $field_defaults);
      $command['options']['fields']['description'] .= ' '. dt('All available fields are: !fields.', array('!fields' => implode(', ', $all_fields_description)));
      if (isset($outputformat['fields-default'])) {
        $command['options']['full']['description'] = dt("Show the full output, with all fields included.");
      }
    }
    else {
      // If the command does not define specific field labels,
      // then hide the help for --fields unless the command
      // uses output format engines that format tables.
      if (isset($outputformat['require-engine-capability']) && is_array($outputformat['require-engine-capability'])) {
        if (!in_array('format-table', $outputformat['require-engine-capability'])) {
          unset($command['options']['fields']);
          unset($command['options']['field-labels']);
        }
      }
      // If the command does define output formats, but does not
      // define fields, then just hide the help for the --fields option.
      else {
        $command['options']['fields']['hidden'] = TRUE;
        $command['options']['field-labels']['hidden'] = TRUE;
      }
    }

    // If the command defines a default pipe format, then
    // add '--pipe   Equivalent to --format=<pipe-default>'.
    if (isset($outputformat['pipe-format'])) {
      if (isset($command['options']['pipe'])) {
        $command['options']['pipe'] .= ' ';
      }
      else {
        $command['options']['pipe'] = '';
      }
      if (isset($outputformat['pipe-metadata']['message-template'])) {
        $command['options']['pipe'] .= dt('Displays output in the form "!message"', array('!message' => $outputformat['pipe-metadata']['message-template']));
      }
      else {
        $command['options']['pipe'] .= dt("Equivalent to --format=!default.", array('!default' => $outputformat['pipe-format']));
      }
    }
  }
}

/**
 * Implements hook_drush_engine_topic_additional_text().
 */
function outputformat_drush_engine_topic_additional_text($engine, $instance, $config) {
  $result = array();

  // If the output format engine has a 'topic-example' in
  // its configuration, then format the provided array using
  // the output formatter, and insert the result of the
  // transform into the topic text.
  if ($engine == 'outputformat') {
    if (array_key_exists('topic-example', $config)) {
      $code = $config['topic-example'];
      $formatted = drush_format($code, array(), $instance);
      $result[] = dt("Code:\n\nreturn !code;\n\nOutput with --format=!instance:\n\n!formatted", array('!code' => var_export($code, TRUE), '!instance' => $instance, '!formatted' => $formatted));
    }
  }

  return $result;
}

/**
 * Interface for output format engines.
 */
class drush_outputformat {
  function __construct($config) {
    $config += array(
      'column-widths' => array(),
      'field-mappings' => array(),
      'engine-info' => array(),
    );
    $config['engine-info'] += array(
      'machine-parsable' => FALSE,
      'metadata' => array(),
    );
    $config += $config['engine-info']['metadata'];
    $this->engine_config = $config;
  }
  function format_error($message) {
    return drush_set_error('DRUSH_FORMAT_ERROR', dt("The output data could not be processed by the selected format '!type'.  !message", array('!type' => $this->engine, '!message' => $message)));
  }
  function formatter_type() {
    return $this->engine;
  }
  function is_list() {
    return FALSE;
  }
  function formatter_is_simple_list() {
    if (!isset($this->sub_engine)) {
      return false;
    }
    return ($this->formatter_type() == 'list') && ($this->sub_engine->supports_single_only());
  }
  function data_type($metadata) {
    if (isset($metadata['metameta']['require-engine-capability']) && is_array($metadata['metameta']['require-engine-capability'])) {
      return  $metadata['metameta']['require-engine-capability'][0];
    }
    if (isset($metadata['require-engine-capability']) && is_array($metadata['require-engine-capability'])) {
      return  $metadata['require-engine-capability'][0];
    }
    return 'unspecified';
  }
  function supported_data_types($metadata = NULL) {
    if ($metadata == NULL) {
      $metadata = $this->engine_config;
    }
    if (isset($metadata['metameta']['engine-info']['engine-capabilities'])) {
      return  $metadata['metameta']['engine-info']['engine-capabilities'];
    }
    if (isset($metadata['engine-info']['engine-capabilities'])) {
      return  $metadata['engine-info']['engine-capabilities'];
    }
    return array();
  }
  function supports_single_only($metadata = NULL) {
    $supported = $this->supported_data_types($metadata);
    return (count($supported) == 1) && ($supported[0] == 'format-single');
  }
  function get_info($key) {
    if (array_key_exists($key, $this->engine_config)) {
      return $this->engine_config[$key];
    }
    elseif (isset($this->sub_engine)) {
      return $this->sub_engine->get_info($key);
    }
    return FALSE;
  }
  /**
   * Perform pre-processing and then format() the $input.
   */
  function process($input, $metadata = array()) {
    $metadata = array_merge_recursive($metadata, $this->engine_config);
    if (isset($metadata['private-fields']) && is_array($input)) {
      if (!drush_get_option('show-passwords', FALSE)) {
        if (!is_array($metadata['private-fields'])) {
          $metadata['private-fields'] = array($metadata['private-fields']);
        }
        foreach ($metadata['private-fields'] as $private) {
          drush_unset_recursive($input, $private);
        }
      }
    }
    if (isset($metadata[$this->engine . '-metadata'])) {
      $engine_specific_metadata = $metadata[$this->engine . '-metadata'];
      unset($metadata[$this->engine . '-metadata']);
      $metadata = array_merge($metadata, $engine_specific_metadata);
    }
    if ((drush_get_context('DRUSH_PIPE')) && (isset($metadata['pipe-metadata']))) {
      $pipe_specific_metadata = $metadata['pipe-metadata'];
      unset($metadata['pipe-metadata']);
      $metadata = array_merge($metadata, $pipe_specific_metadata);
    }
    $machine_parsable = $this->engine_config['engine-info']['machine-parsable'];
    $formatter_type = $machine_parsable ? 'parsable' : 'formatted';
    if ((!$machine_parsable) && is_bool($input)) {
      $input = $input ? 'TRUE' : 'FALSE';
    }

    // Run $input through any filters that are specified for this formatter.
    if (isset($metadata[$formatter_type . '-filter'])) {
      $filters = $metadata[$formatter_type . '-filter'];
      if (!is_array($filters)) {
        $filters = array($filters);
      }
      foreach ($filters as $filter) {
        if (function_exists($filter)) {
          $input = $filter($input, $metadata);
        }
      }
    }
    if (isset($metadata['field-labels'])) {
      foreach (drush_hide_output_fields() as $hidden_field) {
        unset($metadata['field-labels'][$hidden_field]);
      }
    }
    return $this->format($input, $metadata);
  }
  function format($input, $metadata) {
    return $input;
  }
}

/**
 * Specify that certain fields should not appear in the resulting output.
 */
function drush_hide_output_fields($fields_to_hide = array()) {
  $already_hidden = drush_get_context('DRUSH_HIDDEN_OUTPUT_FIELDS');
  if (!is_array($fields_to_hide)) {
    $fields_to_hide = array($fields_to_hide);
  }
  $result = array_merge($already_hidden, $fields_to_hide);
  drush_set_context('DRUSH_HIDDEN_OUTPUT_FIELDS', $result);
  return $result;
}
<?php

/**
 * Output formatter 'csv-or-string'
 *
 * @param $data
 *   The render data may be either a string or an array
 *   - string: printed as-is, without quotes.
 *   - array: the value is printed as a csv list.
 *
 * This is a helper format for handling nested csv lists.
 */
class drush_outputformat_csv_or_string extends drush_outputformat {
  function format($data, $metadata) {
    // If the data is an array, print it as a comma-separated list
    if (is_array($data)) {
      return drush_format($data, $metadata, 'csv');
    }
    return (string)$data;
  }
}
<?php

/**
* Output formatter 'html'
*
* @param $data
*
* @param $metadata
*   '' -
*   '' -
*/
class drush_outputformat_html extends drush_outputformat {
  function format($input, $metadata) {
    $input = drush_help_visible($input);
    $global_options_command = drush_global_options_command(TRUE);
    $global_options_rows = drush_format_help_section($global_options_command, 'options');
    ob_start();
    require_once __DIR__ . '/html.tpl.php';
    $return = ob_get_clean();
    return $return;
  }
}

<html>
<head><title>Drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head>
<body>
  <h3>Global Options (see `drush topic core-global-options` for the full list)</h3>
  <table><?php
    foreach ($global_options_rows as $key => $row) {
      print '<tr>';
      foreach ($row as $value) {
        print  "<td>" . htmlspecialchars($value) . "</td>\n";
      }
      print "</tr>\n";
    } ?>
  </table>
  <h3>Command list</h3>
  <table><?php
    foreach ($input as $key => $command) {
      print "  <tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n";
    } ?>
  </table>
<h3>Command detail</h3>
<dl><?php
      foreach ($input as $key => $command) {
        print "\n<a name=\"$key\"></a><dt>$key</dt><dd><pre>\n";
        drush_core_helpsingle($key);
        print "</pre></dd>\n";
      }
    ?>
</body>
</html>
<?php

/**
 * Output formatter 'json'
 *
 * @param $data
 *   The $data parameter is converted to Javascript Object Notation
 * @param $metadata
 *   Unused
 *
 * Code:
 *
 *   return array(
 *     "a" => array("b" => 2, "c" => 3),
 *     "d" => array("e" => 5, "f" => 6)
 *   );
 *
 * Output with --format=json:
 *
 *   {"a":{"b":2,"c":3},"d":{"e":5,"f":6}}
 */
class drush_outputformat_json extends drush_outputformat {
  function format($input, $metadata) {
    return drush_json_encode($input);
  }
}
<?php

/**
 * Output formatter 'key_value'
 *
 * @param $data
 *   The $data parameter contains an array of key / value pairs which
 *   are rendered as "key   :   value" in a formatted word-wrapped table
 *   with aligned columns.  'value' is expected to always be a simple string;
 *   if it is not, it is rendered with var_export.
 * @param $metadata
 *   'label' - If present, creates a section header "[label]" prior to the data
 *   'separator' - If present, used instead of ', ' when impoding data values
 *   'ini-item' - If present, selects a single item from any data value that is
 *     an array and uses it instead of imploding all values together.
 *
 * Code:
 *
 *   return array(
 *     "b" => "Two B or ! Two B, that is the comparison",
 *     "c" => "I see that C has gone to Sea"
 *   );
 *
 * Output with --format=key-value:
 *
 *     b   :  Two B or ! Two B,
 *            that is the
 *            comparison
 *     c   :  I see that C has gone
 *            to Sea
 *
 * Code:
 *
 *   return array(
 *     "a" => array(
 *       "b" => "Two B or ! Two B, that is the comparison",
 *       "c" => "I see that C has gone to Sea"
 *     ),
 *     "d" => array(
 *       "e" => "Elephants and electron microscopes",
 *       "f" => "My margin is too small"
 *     )
 *   );
 *
 * Output with --format=key-value-list:
 *
 *     b   :  Two B or ! Two B,
 *            that is the
 *            comparison
 *     c   :  I see that C has gone
 *            to Sea
 *
 *     e   :  Elephants and
 *            electron microscopes
 *     f   :  My margin is too
 *            small
 */
class drush_outputformat_key_value extends drush_outputformat {
  function format($input, $metadata) {
    if (!is_array($input)) {
      if (isset($metadata['label'])) {
        $input = array(dt($metadata['label']) => $input);
      }
      else {
        return $this->format_error(dt('No label provided.'));
      }
    }
    $kv_metadata = isset($metadata['table-metadata']) ? $metadata['table-metadata'] : array();
    if ((!isset($kv_metadata['key-value-item'])) && (isset($metadata['field-labels']))) {
      $input = drush_select_output_fields($input, $metadata['field-labels'], $metadata['field-mappings']);
    }
    if (isset($metadata['include-field-labels'])) {
      $kv_metadata['include-field-labels'] = $metadata['include-field-labels'];
    }
    $formatted_table = drush_key_value_to_array_table($input, $kv_metadata);
    if ($formatted_table === FALSE) {
      return FALSE;
    }
    return drush_format_table($formatted_table, FALSE, array());
  }
}
<?php

/**
 * Output formatter 'list'
 *
 * @param $data
 *   The $data parameter is expected to be an array of key / value pairs.
 *   Each key / value pair is passed to some other output formatter for
 *   rendering; the key becomes the label, $metadata['label'], and the
 *   value becomes the $data for the sub-formatter.
 * @param $metadata
 *   'matches' - Specifies the exact kind of list to be rendered in an
 *     array of two elements.  $matches[0] is the full name of the
 *     list format (e.g. 'string-list'), and $matches[1] is the type
 *     of the sub-formatter (e.g. 'string').  If not specified, 'string'
 *     is assumed.
 *
 * Code:
 *
 *   return array('a', 'b', 'c');
 *
 * Output with --format=list: (list of string):
 *
 *   a
 *   b
 *   c
 */
class drush_outputformat_list extends drush_outputformat {
  function is_list() {
    return TRUE;
  }
  function validate() {
    // Separate the 'list' and 'filter' metadata from everything
    // else in $engine_config['engine-info']
    $list_metadata = array();
    foreach ($this->engine_config as $key => $value) {
      if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter') || (substr($key, 0, 5) == 'field') || ($key == 'options')) {
        unset($this->engine_config[$key]);
        $list_metadata[$key] = $value;
      }
    }
    foreach ($this->engine_config['engine-info'] as $key => $value) {
      if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter')) {
        unset($this->engine_config['engine-info'][$key]);
        $list_metadata[$key] = $value;
      }
    }
    $sub_formatter = isset($list_metadata['list-item-type']) ? $list_metadata['list-item-type'] : 'string';
    $this->sub_engine = drush_load_engine('outputformat', $sub_formatter, $this->engine_config);
    if (!is_object($this->sub_engine)) {
      return drush_set_error('DRUSH_INVALID_SUBFORMATTER', dt("The list output formatter could not load its subformatter: !sub", array('!sub' => $sub_formatter)));
    }
    $engine_info = $this->engine_config['engine-info'];
    $this->engine_config = array(
      'engine-info' => array(
        'machine-parsable' => $this->sub_engine->engine_config['engine-info']['machine-parsable'],
      ),
      'metameta' => $this->sub_engine->engine_config,
    ) + $list_metadata;
    return TRUE;
  }

  function format($input, $metadata) {
    $output = '';
    if (is_array($input)) {
      // If this list is processing output from a command that produces table
      // @todo - need different example below?
      // output, but our subformatter only supports 'single' items (e.g. csv),
      // then we will convert our data such that the output will be the keys
      // of the table rows.
      if (($this->data_type($metadata) == 'format-table') && ($this->supports_single_only($metadata)) && !isset($metadata['list-item'])) {
        // If the user specified exactly one field with --fields, then
        // use it to select the data column to use instead of the array key.
        if (isset($metadata['field-labels']) && (count($metadata['field-labels']) == 1)) {
          $first_label = key($metadata['field-labels']);
          $input = drush_output_get_selected_field($input, $first_label);
        }
        else {
          $input = array_keys($input);
        }
      }
      $first = TRUE;
      $field_selection_control = isset($metadata['list-field-selection-control']) ? $metadata['list-field-selection-control'] : 0;
      $selected_output_fields = false;
      if (empty($metadata['metameta'])) {
        $metameta = $metadata;
        unset($metameta['list-item']);
        unset($metameta['list-item-default-value']);
      }
      else {
        $metameta = $metadata['metameta'];
      }
      $list_separator_key = 'list-separator';
      if ($this->sub_engine->is_list()) {
        $list_separator_key = 'line-separator';
        if (isset($metadata['list-separator'])) {
          $metameta['list-separator'] = $metadata['list-separator'];
        }
      }
      $separator = isset($metadata[$list_separator_key]) && !empty($metadata[$list_separator_key]) ? $metadata[$list_separator_key] : "\n";
      // @todo - bug? we iterate over a hard coded, single item array?
      foreach (array('field-labels') as $key) {
        if (isset($metadata[$key])) {
          $metameta[$key] = $metadata[$key];
        }
      }

      // Include field labels, if specified
      if (!isset($metadata['list-item']) && isset($metadata['labeled-list']) && is_array($input) && isset($metadata['field-labels'])) {
        if (isset($metadata['include-field-labels']) && $metadata['include-field-labels']) {
          array_unshift($input, $metadata['field-labels']);
        }
      }
      foreach ($input as $label => $data) {
        // If this output formatter is set to print a single item from each
        // element, select that item here.
        if (isset($metadata['list-item'])) {
          $data = isset($data[$metadata['list-item']]) ? $data[$metadata['list-item']] : $metadata['list-item-default-value'];
        }
        // If this formatter supports the --fields option, then filter and
        // order the fields the user wants here.  Note that we need to be
        // careful about when we call drush_select_output_fields(); sometimes,
        // there will be nested formatters of type 'list', and it would not
        // do to select the output fields more than once.
        // 'list-field-selection-control can be set to a positive number to
        // cause output fields to be selected at a later point in the call chain.
        elseif (is_array($data) && isset($metadata['field-labels'])) {
          if (!$field_selection_control) {
            $data = drush_select_output_fields($data, $metadata['field-labels'], $metadata['field-mappings']);
            $selected_output_fields = true;
          }
        }
        $metameta['label'] = $label;
        if ($selected_output_fields) {
          $metameta['list-field-selection-control'] = -1;
        }
        elseif ($field_selection_control) {
          $metameta['list-field-selection-control'] = $field_selection_control - 1;
        }
        $formatted_item = $this->sub_engine->format($data, $metameta);
        if ($formatted_item === FALSE) {
          return FALSE;
        }
        if (!$first) {
          $output .= $separator;
        }
        if (($separator != "\n") && !empty($separator) && (strpos($formatted_item, $separator) !== FALSE)) {
          $formatted_item = drush_wrap_with_quotes($formatted_item);
        }
        $output .= $formatted_item;
        $first = FALSE;
      }
    }
    return $output;
  }
}
<?php

/**
 * Output formatter 'message'
 *
 * @param $data
 *   The parameters for the render data
 * @param $metadata
 *   'message-template' - Provides the template string to use with dt().
 *
 * Code:
 *
 *   return array('a' => 1, 'b' => 2);
 *
 * Given 'message-template' == 'The first is !a and the second is !b',
 * output with --format=message:
 *
 *   The first is 1 and the second is 2
 */
class drush_outputformat_message extends drush_outputformat {
  function format($data, $metadata) {
    $result = '';
    if (isset($metadata['message-template'])) {
      foreach ($data as $key => $value) {
        $data_for_dt['!' . $key] = $value;
      }
      $result = dt($metadata['message-template'], $data_for_dt);
    }
    return $result;
  }
}
<?php

/**
 * Output formatter 'php'
 *
 * @param $data
 *   The $data parameter is rendered as a serialized php string
 * @param $metadata
 *
 * Code:
 *
 */
class drush_outputformat_php extends drush_outputformat {
  function format($input, $metadata) {
    return serialize($input);
  }
}
<?php

/**
 * Output formatter 'print-r'
 *
 * @param $data
 *   The $data parameter is rendered with the php print_r function
 * @param $metadata
 *   'label' - If present, prints "label: " prior to the data
 *
 * Code:
 *
 *   return array(
 *     "a" => array("b" => 2, "c" => 3),
 *     "d" => array("e" => 5, "f" => 6)
 *   );
 *
 * Output with --format=print-r:
 *
 *   Array
 *   (
 *       [a] => Array
 *           (
 *               [b] => 2
 *               [c] => 3
 *           )
 *
 *       [d] => Array
 *           (
 *               [e] => 5
 *               [f] => 6
 *           )
 *   )
 */
class drush_outputformat_print_r extends drush_outputformat {
  function format($input, $metadata) {
    if (is_string($input)) {
      $output = '"' . $input . '"';
    }
    elseif (is_array($input) || is_object($input)) {
      $output = print_r($input, TRUE);
    }
    else {
      $output = $input;
    }
    if (isset($metadata['label'])) {
      $output = $metadata['label'] . ': ' . $output;
    }
    return $output;
  }
}
<?php

/**
 * Output formatter 'string'
 *
 * @param $data
 *   The render data may be either a string or an array
 *   string - printed as-is, without quotes
 *   array - the value of the first item in the array is printed as-is
 * @param $metadata
 *   'label' - If present, prints "label: " prior to the data
 *
 * Code:
 *
 *   return DRUSH_VERSION;
 *
 * Output with --format=string:
 *
 *   6.0-dev
 */
class drush_outputformat_string extends drush_outputformat {
  function format($data, $metadata) {
    // If the data is an array, print the value of the first item.
    if (is_array($data)) {
      if (count($data) > 1) {
        return $this->format_error("Multiple rows provided where only one is allowed.");
      }
      if (!empty($data)) {
        $data = reset($data);
      }
      if (is_array($data)) {
        return $this->format_error("Array provided where a string is required.");
      }
    }
    return (string)$data;
  }
}
<?php

/**
 * Output formatter 'table'
 *
 * @param $data
 *   The $data parameter is expected to be an array (keys ignored) of
 *   rows; each row, in turn, is an array of key / value pairs.  Every
 *   row is expected to have the same set of keys.  The data is rendered
 *   as a formatted word-wrapped table with rows of data cells aligned in
 *   columns.
 * @param $metadata
 *   'field-labels' - If present, contains an array of key / value pairs
 *     that map from the keys in the row columns to the label for the
 *     column header.
 *   'column-widths' - If present, contains an array of key / value pairs,
 *     where the key is the integer column number, and the value is the
 *     width that column should be formatted to.
 *
 * Code:
 *
 *   return array(
 *     "a" => array("b" => 2, "c" => 3),
 *     "d" => array("b" => 5, "c" => 6)
 *   );
 *
 * Output with --format=table:
 *
 *    b  c
 *    2  3
 *    5  6
 */
class drush_outputformat_table extends drush_outputformat {
  function format($input, $metadata) {
    $field_list = isset($metadata['field-labels']) ? $metadata['field-labels'] : array();
    $widths = array();
    $col = 0;
    foreach($field_list as $key => $label) {
      if (isset($metadata['column-widths'][$key])) {
        $widths[$col] = $metadata['column-widths'][$key];
      }
      ++$col;
    }
    $rows = drush_rows_of_key_value_to_array_table($input, $field_list, $metadata);
    $field_labels = array_key_exists('include-field-labels', $metadata) && $metadata['include-field-labels'];
    if (!$field_labels) {
      array_shift($rows);
    }
    return drush_format_table($rows, $field_labels, $widths);
  }
}
The 'table' formatter will convert an associative array into a formatted,
word-wrapped table.  Each item in the associative array represents one row
in the table.  Each row is similarly composed of associative arrays, with
the key of each item indicating the column, and the value indicating the
contents of the cell.  See below for an example source array.
<p>
The command core-requirements is an example of a command that produces output
in a tabular format.
<p>
<i>$ drush core-requirements</i>
<pre>
 Title                 Severity  Description
 Cron maintenance      Error     Last run 2 weeks ago
 tasks                           Cron has not run recently. For more
                                 information, see the online handbook entry for
                                 configuring cron jobs. You can run cron
                                 manually.
 Drupal                Info      7.19
</pre>
(Note: the output above has been shortened for clarity; the actual output
of core-requirements contains additional rows not shown here.)
<p>
It is possible to determine the available fields by consulting <i>drush
help core requirements</i>:
<pre>
 --fields=&lt;title, severity, description&gt;   Fields to output. All
                                           available fields are:
                                           title, severity, sid,
                                           description, value,
                                           reason, weight.
</pre>
It is possible to control the fields that appear in the table, and their
order, by naming the desired fields in the --fields option.  The space
between items is optional, so `--fields=title,sid` is valid.
<p>
Code:
<pre>
return array (
  'cron' =>
  array (
    'title' => 'Cron maintenance tasks',
    'severity' => 2,
    'value' => 'Last run 2 weeks ago',
    'description' => 'Cron has not run recently. For more information, see the online handbook entry for <a href="http://drupal.org/cron">configuring cron jobs</a>. You can <a href="/admin/reports/status/run-cron">run cron manually</a>.',
    'severity-label' => 'Error',
  ),
  'drupal' =>
  array (
    'title' => 'Drupal',
    'value' => '7.19',
    'severity' => -1,
    'weight' => -10,
    'severity-label' => 'Info',
  ),
)
</pre>
<?php

/**
 * Output formatter 'var_export'
 *
 * Note: this class is also used by format 'config'
 *
 * @param $data
 *   The $data parameter is rendered with the php var_export() function
 * @param $metadata
 *   'label' - If present, prints "$variable['label'] = " prior to the data
 *   'variable-name' - If present, provides an alternate name for $variable
 *     when labels are in use.
 *
 * Code:
 *
 *   return array(
 *     "a" => array("b" => 2, "c" => 3),
 *     "d" => array("e" => 5, "f" => 6)
 *   );
 *
 * Output with --format=var_export:
 *
 *   array (
 *     'a' =>
 *     array (
 *       'b' => 2,
 *       'c' => 3,
 *     ),
 *     'd' =>
 *     array (
 *       'e' => 5,
 *       'f' => 6,
 *     ),
 *   )
 *
 * Output with --format=config: (list of export)
 *
 *   $config['a'] = array (
 *     'b' => 2,
 *     'c' => 3,
 *   );
 *   $config['d'] = array (
 *     'e' => 5,
 *     'f' => 6,
 *   );
 */
class drush_outputformat_var_export extends drush_outputformat {
  function format($input, $metadata) {
    if (isset($metadata['label'])) {
      $variable_name = isset($metadata['variable-name']) ? $metadata['variable-name'] : 'variables';
      $variable_name = preg_replace("/[^a-zA-Z0-9_-]/", "", str_replace(' ', '_', $variable_name));
      $label = $metadata['label'];
      $label_template = (isset($metadata['label-template'])) ? $metadata['label-template'] : '$!variable["!label"] = !value;';
      $output = dt($label_template, array('!variable' => $variable_name, '!label' => $label, '!value' => var_export($input, TRUE)));
    }
    else {
      $output = drush_var_export($input);
    }
    return $output;
  }
}
<?php

/**
 * Output formatter 'variables'
 *
 * @param $data
 *   The $data parameter is expected to be a nested array of key / value pairs.
 *   The top-level key becomes the variable name, $metadata['variable-name'],
 *   and the key on the inner-level items becomes the array label,
 *   $metadata['label'].  These items are then rendered by the 'var_export' formatter.
 * @param $metadata
 *   Unused.
 *
 * Code:
 *
 *   return array(
 *     "a" => array("b" => 2, "c" => 3),
 *     "d" => array("e" => 5, "f" => 6)
 *   );
 *
 * Output with --format=variables:
 *
 *   $a['b'] = 2;
 *   $a['c'] = 3;
 *   $d['e'] = 5;
 *   $d['f'] = 6;
 */
class drush_outputformat_variables extends drush_outputformat {
  function validate() {
    $metadata = $this->engine_config;
    $this->sub_engine = drush_load_engine('outputformat', 'var_export', $metadata);
    if (!is_object($this->sub_engine)) {
      return FALSE;
    }
    return TRUE;
  }

  function format($data, $metadata) {
    $output = '';
    if (is_array($data)) {
      foreach ($data as $variable_name => $section) {
        foreach ($section as $label => $value) {
          $metameta = array(
            'variable-name' => $variable_name,
            'label' => $label,
          );
          $formatted_item = $this->sub_engine->process($value, $metameta);
          if ($formatted_item === FALSE) {
            return FALSE;
          }
          $output .= $formatted_item;
          $output .= "\n";
          }
      }
    }
    return $output;
  }
}
<?php

use Symfony\Component\Yaml\Dumper;

/**
 * Output formatter 'yaml'
 *
 * @param $data
 *   The $data parameter is rendered in yaml
 * @param $metadata
 *
 * Code:
 *
 */
class drush_outputformat_yaml extends drush_outputformat {
  function format($input, $metadata) {
    $dumper = new Dumper();
    // Set Yaml\Dumper's default indentation for nested nodes/collections to
    // 2 spaces for consistency with Drupal coding standards.
    $dumper->setIndentation(2);
    // The level where you switch to inline YAML is set to PHP_INT_MAX to
    // ensure this does not occur.
    $output = $dumper->dump($input, PHP_INT_MAX, NULL, NULL, TRUE);
    return $output;
  }
}
<?php

use Drush\Log\LogLevel;

/**
 * Implements hook_drush_help().
 */
function queue_drush_help($section) {
  switch ($section) {
    case 'drush:queue-run':
      return dt('Run Drupal queue workers. As opposed to "drush cron" that can only be run one at a time on a single site, "drush queue-run" can be invoked as many times as the server load allows.');
  }
}

/**
 * Implements hook_drush_command().
 */
function queue_drush_command() {
  $items['queue-run'] = array(
    'description' => 'Run a specific queue by name',
    'arguments' => array(
      'queue_name' => 'The name of the queue to run, as defined in either hook_queue_info or hook_cron_queue_info.',
    ),
    'required-arguments' => TRUE,
    'options' => array(
      'time-limit' => 'The maximum number of seconds allowed to run the queue',
    ),
  );
  $items['queue-list'] = array(
    'description' => 'Returns a list of all defined queues',
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'csv',
      'field-labels' => array(
        'queue' => 'Queue',
        'items' => 'Items',
        'class' => 'Class',
      ),
      'ini-item' => 'items',
      'table-metadata' => array(
        'key-value-item' => 'items',
      ),
      'output-data-type' => 'format-table',
    ),
  );

  return $items;
}

/**
 * Validation callback for drush queue-run.
 */
function drush_queue_run_validate($queue_name) {
  try {
    $queue = drush_queue_get_class();
    $queue->getInfo($queue_name);
  }
  catch (\Drush\Queue\QueueException $exception) {
    return drush_set_error('DRUSH_QUEUE_RUN_VALIDATION_ERROR', $exception->getMessage());
  }
}

/**
 * Return the appropriate queue class.
 */
function drush_queue_get_class() {
  return drush_get_class('Drush\Queue\Queue');
}

/**
 * Command callback for drush queue-run.
 *
 * Queue runner that is compatible with queues declared using both
 * hook_queue_info() and hook_cron_queue_info().
 *
 * @param $queue_name
 *   Arbitrary string. The name of the queue to work with.
 */
function drush_queue_run($queue_name) {
  $queue = drush_queue_get_class();
  $time_limit = (int) drush_get_option('time-limit');
  $start = microtime(TRUE);
  $count = $queue->run($queue_name, $time_limit);
  $elapsed = microtime(TRUE) - $start;
  drush_log(dt('Processed @count items from the @name queue in @elapsed sec.', array('@count' => $count, '@name' => $queue_name, '@elapsed' => round($elapsed, 2))), drush_get_error() ? LogLevel::WARNING : LogLevel::OK);
}

/**
 * Command callback for drush queue-list.
 */
function drush_queue_list() {
  $queue = drush_queue_get_class();
  return $queue->listQueues();
}

<?php

use Drush\Log\LogLevel;
use Drush\Role\RoleBase;

/**
 * Implementation of hook_drush_help().
 */
function role_drush_help($section) {
  switch ($section) {
    case 'meta:role:title':
      return dt('Role commands');
    case 'meta:role:summary':
      return dt('Interact with the role system.');
  }
}

/**
 * Implements hook_drush_help_alter().
 */
function role_drush_help_alter(&$command) {
  // Drupal 8+ has changed role names.
  if (in_array($command['command'] , array('role-add-perm', 'role-add-perm', 'role-list')) && drush_drupal_major_version() >= 8) {
    foreach ($command['examples'] as $key => $val) {
      $newkey = str_replace(array('anonymous user', 'authenticated user'), array('anonymous', 'authenticated'), $key);
      $command['examples'][$newkey] = $val;
      unset($command['examples'][$key]);
    }
  }
}

/**
 * Implementation of hook_drush_command().
 */
function role_drush_command() {
  $items['role-create'] = array(
    'description' => 'Create a new role.',
    'examples' => array(
      "drush role-create 'test role'" => "Create a new role 'test role' on D6 or D7; auto-assign the rid. On D8, 'test role' is the rid, and the human-readable name is set to 'Test role'.",
      "drush role-create 'test role' 'Test role'" => "Create a new role with a machine name of 'test role', and a human-readable name of 'Test role'. On D6 and D7, behaves as the previous example."
    ),
    'arguments' => array(
      'machine name' => 'The symbolic machine name for the role. Required.',
      'human-readable name' => 'A descriptive name for the role. Optional; Drupal 8 only.  Ignored in D6 and D7.',
    ),
    'aliases' => array('rcrt'),
  );
  $items['role-delete'] = array(
    'description' => 'Delete a role.',
    'examples' => array(
      "drush role-delete 'test role'" => "Delete the role 'test role'.",
    ),
    'arguments' => array(
      'machine name' => 'The symbolic machine name for the role. Required.  In D6 and D7, this may also be a numeric role ID.',
    ),
    'aliases' => array('rdel'),
  );
  $items['role-add-perm'] = array(
    'description' => 'Grant specified permission(s) to a role.',
    'examples' => array(
      "drush role-add-perm 'anonymous user' 'post comments'" => 'Allow anon users to post comments.',
      "drush role-add-perm 'anonymous user' \"'post comments','access content'\"" => 'Allow anon users to post comments and access content.',
      "drush role-add-perm 'authenticated user' --module=node" => 'Select a permission from "node" permissions to add to logged in users.'
    ),
    'arguments' => array(
      'role' => 'The role to modify.  Required.',
      'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.',
    ),
    'required-arguments' => 1,
    'options' => array(
      'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.',
    ),
    'global-options' => array(
      'cache-clear',
    ),
    'aliases' => array('rap'),
  );

  $items['role-remove-perm'] = array(
    'description' => 'Remove specified permission(s) from a role.',
    'examples' => array(
      "drush role-remove-perm 'anonymous user' 'access content'" => 'Hide content from anon users.',
    ),
    'arguments' => array(
      'role' => 'The role to modify.',
      'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.',
    ),
    'required-arguments' => 1,
    'options' => array(
      'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.',
    ),
    'global-options' => array(
      'cache-clear',
    ),
    'aliases' => array('rmp'),
  );

  $items['role-list'] = array(
    'description' => 'Display a list of all roles defined on the system.  If a role name is provided as an argument, then all of the permissions of that role will be listed.  If a permission name is provided as an option, then all of the roles that have been granted that permission will be listed.',
    'examples' => array(
      "drush role-list --filter='administer nodes'" => 'Display a list of roles that have the administer nodes permission assigned.',
      "drush role-list 'anonymous user'" => 'Display all of the permissions assigned to the anon user role.'
    ),
    'arguments' => array(
      'role' => 'The role to list.  Optional; if specified, lists all permissions assigned to that role.  If no role is specified, lists all of the roles available on the system.',
    ),
    'options' => array(
      'filter' => 'Limits the list of roles to only those that have been assigned the specified permission. Optional; may not be specified if a role argument is provided.',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'list',
      'field-labels' => array('rid' => 'ID', 'label' => 'Role Label', 'perm' => "Permission"),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('rls'),
  );

  return $items;
}

/**
 * Create the specified role
 */
function drush_role_create($rid, $role_name = '') {
  $role = drush_role_get_class();
  $result = $role->role_create($rid, $role_name);
  if ($result !== FALSE) {
    drush_log(dt('Created "!role"', array('!role' => $rid)), LogLevel::SUCCESS);
  }
  return $result;
}

/**
 * Create the specified role
 */
function drush_role_delete($rid) {
  $role = drush_role_get_class($rid);
  if ($role === FALSE) {
    return FALSE;
  }
  $result = $role->delete();
  if ($result !== FALSE) {
    drush_log(dt('Deleted "!role"', array('!role' => $rid)), LogLevel::SUCCESS);
  }
  return $result;
}

/**
 * Add one or more permission(s) to the specified role.
 *
 * @param string $rid machine name for a role
 * @param null|string $permissions machine names, delimited by commas.
 *
 * @return bool
 */
function drush_role_add_perm($rid, $permissions = NULL) {
  if (is_null($permissions)) {
    // Assume --module is used thus inject a FALSE
    $perms = array(FALSE);
  }
  else {
    $perms = _convert_csv_to_array($permissions);
  }

  $added_perm = FALSE;

  foreach($perms as $perm) {
    $result = drush_role_perm('add', $rid, $perm);
    if ($result !== FALSE) {
      $added_perm = TRUE;
      drush_log(dt('Added "!perm" to "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::SUCCESS);
    }
  }

  if ($added_perm) {
    drush_drupal_cache_clear_all();
  }

  return $result;
}

/**
 * Remove permission(s) from the specified role.
 *
 * @param string $rid machine name for a role
 * @param null|string $permissions machine names, delimited by commas.
 *
 * @return bool
 */
function drush_role_remove_perm($rid, $permissions = NULL) {
  if (is_null($permissions)) {
    // Assume --module is used thus inject a FALSE
    $perms = array(FALSE);
  }
  else {
    $perms = _convert_csv_to_array($permissions);
  }

  $removed_perm = FALSE;

  foreach($perms as $perm) {
    $result = drush_role_perm('remove', $rid, $perm);
    if ($result !== FALSE) {
      $removed_perm = TRUE;
      drush_log(dt('Removed "!perm" from "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::OK);
    }
  }

  if ($removed_perm) {
    drush_drupal_cache_clear_all();
  }

  return $result;
}

/**
 * Implement permission add / remove operations.
 *
 * @param string $action 'add' | 'remove'
 * @param string $rid
 * @param string|null $permission
 *
 * @return bool|RoleBase
 *
 * @see drush_set_error()
 */
function drush_role_perm($action, $rid, $permission = NULL) {
  $role = drush_role_get_class($rid);
  if (!$role) {
    return FALSE;
  }

  // If a permission wasn't provided, but the module option is specified,
  // provide a list of permissions provided by that module.
  if (!$permission && $module = drush_get_option('module', FALSE)) {
    drush_include_engine('drupal', 'environment');
    if (!drush_module_exists($module)) {
      return drush_set_error('DRUSH_ROLE_ERROR', dt('!module not enabled!', array('!module' => $module)));
    }
    $module_perms = $role->getModulePerms($module);
    if (empty($module_perms)) {
      return drush_set_error('DRUSH_ROLE_NO_PERMISSIONS', dt('No permissions found for module !module', array('!module' => $module)));
    }
    $choice = drush_choice($module_perms, "Enter a number to choose which permission to $action.");
    if ($choice === FALSE) {
      return drush_user_abort();
    }
    $permission = $module_perms[$choice];
  }
  else {
    $permissions = $role->getAllModulePerms();
    if (!in_array($permission, $permissions)) {
      return drush_set_error(dt('Could not find the permission: !perm', array('!perm' => $permission)));
    }
  }

  $result = $role->{$action}($permission);
  if ($result === FALSE) {
    return FALSE;
  }
  return $role;
}

/**
 * Get core version specific Role handler class.
 *
 * @param string $role_name
 * @return RoleBase
 *
 * @see drush_get_class().
 */
function drush_role_get_class($role_name = DRUPAL_ANONYMOUS_RID) {
  return drush_get_class('Drush\Role\Role', func_get_args());
}

/**
 * Displays a list of roles
 */
function drush_role_list($rid = '') {
  $result = array();
  if (empty($rid)) {
    drush_hide_output_fields(array('perm'));
    // Get options passed.
    $perm = drush_get_option('filter');
    $roles = array();

    // Get all roles - if $perm is empty user_roles retrieves all roles.
    $roles = user_roles(FALSE, $perm);
    if (empty($roles)) {
      return drush_set_error('DRUSH_NO_ROLES', dt("No roles found."));
    }
    foreach ($roles as $rid => $value) {
      $role = drush_role_get_class($rid);
      $result[$role->name] = array(
        'rid' => $rid,
        'label' => $role->name,
      );
    }
  }
  else {
    drush_hide_output_fields(array('rid', 'label'));
    $role = drush_role_get_class($rid);
    if (!$role) {
      return FALSE;
    }
    $perms = $role->getPerms();
    foreach ($perms as $permission) {
      $result[$permission] = array(
        'perm' => $permission);
    }
  }
  return $result;
}
<?php

use Drush\Log\LogLevel;

/*
 * Implements COMMAND hook init.
 */
function drush_core_rsync_init() {
  // Try to get @self defined when --uri was not provided.
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
}

/**
 * A command callback.
 *
 * @param source
 *   A site alias ("@dev") or site specification ("/path/to/drupal#mysite.com")
 *   followed by an optional path (":path/to/sync"), or any path
 *   that could be passed to rsync ("user@server.com:/path/to/dir/").
 * @param destination
 *   Same format as source.
 * @param additional_options
 *   An array of options that overrides whatever was passed in on
 *   the command line (like the 'process' context, but only for
 *   the scope of this one call).
 */
function drush_core_rsync($source, $destination, $additional_options = array()) {
  // Preflight source in case it defines aliases used by the destination
  _drush_sitealias_preflight_path($source);
  // After preflight, evaluate file paths.  We evaluate destination paths first, because
  // there is a first-one-wins policy with --exclude-paths, and we want --target-command-specific
  // to take precedence over --source-command-specific.
  $destination_settings = drush_sitealias_evaluate_path($destination, $additional_options, FALSE, "rsync", 'target-');
  $source_settings = drush_sitealias_evaluate_path($source, $additional_options, FALSE, "rsync", 'source-');
  $source_path = $source_settings['evaluated-path'];
  $destination_path = $destination_settings['evaluated-path'];

  if (!isset($source_settings)) {
    return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate source path !path.', array('!path' => $source)));
  }
  if (!isset($destination_settings)) {
    return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate destination path !path.', array('!path' => $destination)));
  }

  // If the user path is the same for the source and the destination, then
  // always add a slash to the end of the source.  If the user path is not
  // the same in the source and the destination, then you need to know how
  // rsync paths work, and put on the trailing '/' if you want it.
  if ($source_settings['user-path'] == $destination_settings['user-path']) {
    $source_path .= '/';
  }
  // Prompt for confirmation. This is destructive.
  if (!drush_get_context('DRUSH_SIMULATE')) {
    drush_print(dt("You will delete files in !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path)));
    if (!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  // Next, check to see if both the source and the destination are remote.
  // If so, then we'll process this as an rsync from source to local,
  // followed by an rsync from local to the destination.
  if (drush_sitealias_is_remote_site($source_settings) && drush_sitealias_is_remote_site($destination_settings)) {
    return _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path);
  }

  // Exclude settings is the default only when both the source and
  // the destination are aliases or site names.  Therefore, include
  // settings will be the default whenever either the source or the
  // destination contains a : or a /.
  $include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE);

  $options = _drush_build_rsync_options($additional_options, $include_settings_is_default);

  // Get all of the args and options that appear after the command name.
  $original_args = drush_get_original_cli_args_and_options();
  foreach ($original_args as $original_option) {
    if ($original_option{0} == '-') {
      $options .= ' ' . $original_option;
    }
  }

  drush_backend_set_result($destination_path);
  // Go ahead and call rsync with the paths we determined
  return drush_core_exec_rsync($source_path, $destination_path, $options);
}

/**
 * Make a direct call to rsync after the source and destination paths
 * have been evaluated.
 *
 * @param $source
 *   Any path that can be passed to rsync.
 * @param $destination
 *   Any path that can be passed to rsync.
 * @param $additional_options
 *   An array of options that overrides whatever was passed in on the command
 *   line (like the 'process' context, but only for the scope of this one
 *   call).
 * @param $include_settings_is_default
 *   If TRUE, then settings.php will be transferred as part of the rsync unless
 *   --exclude-conf is specified.  If FALSE, then settings.php will be excluded
 *   from the transfer unless --include-conf is specified.
 * @param $live_output
 *   If TRUE, output goes directly to the terminal using system(). If FALSE,
 *   rsync is executed with drush_shell_exec() with output in
 *   drush_shell_exec_output().
 *
 * @return
 *   TRUE on success, FALSE on failure.
 */
function drush_core_call_rsync($source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) {
  $options = _drush_build_rsync_options($additional_options, $include_settings_is_default);
  return drush_core_exec_rsync($source, $destination, $options, $additional_options, $live_output);
}

function drush_core_exec_rsync($source, $destination, $options, $additional_options = array(), $live_output = TRUE) {
  $ssh_options = drush_get_option_override($additional_options, 'ssh-options', '');
  $exec = "rsync -e 'ssh $ssh_options' $options $source $destination";

  if ($live_output) {
    $exec_result = drush_op_system($exec);
    $result = ($exec_result == 0);
  }
  else {
    $result = drush_shell_exec($exec);
  }

  if (!$result) {
    drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination)));
  }

  return $result;
}

function _drush_build_rsync_options($additional_options, $include_settings_is_default = TRUE) {
  $options = '';
  // Exclude vcs reserved files.
  if (!_drush_rsync_option_exists('include-vcs', $additional_options)) {
    $vcs_files = drush_version_control_reserved_files();
    foreach ($vcs_files as $file) {
      $options .= ' --exclude="'.$file.'"';
    }
  }
  else {
    unset($additional_options['include-vcs']);
  }

  $mode = '-akz';
  // Process --include-paths and --exclude-paths options the same way
  foreach (array('include', 'exclude') as $include_exclude) {
    // Get the option --include-paths or --exclude-paths and explode to an array of paths
    // that we will translate into an --include or --exclude option to pass to rsync
    $inc_ex_path = explode(PATH_SEPARATOR, drush_get_option($include_exclude . '-paths', ''));
    foreach ($inc_ex_path as $one_path_to_inc_ex) {
      if (!empty($one_path_to_inc_ex)) {
        $options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"';
      }
    }
    // Remove stuff inserted by evaluate path
    unset($additional_options[$include_exclude . '-paths']);
    unset($additional_options[$include_exclude . '-files-processed']);
  }
  // drush_core_rsync passes in $include_settings_is_default such that
  // 'exclude-conf' is the default when syncing from one alias to
  // another, and 'include-conf' is the default when a path component
  // is included.
  if ($include_settings_is_default ? _drush_rsync_option_exists('exclude-conf', $additional_options) : !_drush_rsync_option_exists('include-conf', $additional_options)) {
    $options .= ' --exclude="settings.php"';
    unset($additional_options['exclude-conf']);
  }
  if (_drush_rsync_option_exists('exclude-sites', $additional_options)) {
    $options .= ' --include="sites/all" --exclude="sites/*"';
    unset($additional_options['exclude-sites']);
  }
  if (_drush_rsync_option_exists('mode', $additional_options)) {
    $mode = "-" . drush_get_option_override($additional_options, 'mode');
    unset($additional_options['mode']);
  }
  if (drush_get_context('DRUSH_VERBOSE')) {
    // the drush_op() will be verbose about the command that gets executed.
    $mode .= 'v';
    $options .= ' --stats --progress';
  }

  // Check if the user has set $options['rsync-version'] to enable rsync legacy version support.
  // Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set.
  $rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9');
  $options_to_exclude = array('ssh-options');
  foreach ($additional_options as $test_option => $value) {
    // Downgrade some options for older versions of rsync
    if ($test_option == 'remove-source-files') {
      if (version_compare($rsync_version, '2.6.4', '<')) {
        $test_option = NULL;
        drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', LogLevel::WARNING);
      }
      elseif (version_compare($rsync_version, '2.6.9', '<')) {
        $test_option = 'remove-sent-files';
      }
    }
    if ((isset($test_option)) && !in_array($test_option, $options_to_exclude) && (isset($value)  && !is_array($value))) {
      if (($value === TRUE) || (!isset($value))) {
        $options .= " --$test_option";
      }
      else {
        $options .= " --$test_option=" . escapeshellarg($value);
      }
    }
  }

  return $mode . $options;
}

function _drush_rsync_option_exists($option, $additional_options) {
  if (array_key_exists($option, $additional_options)) {
    return TRUE;
  }
  else {
    return drush_get_option($option, FALSE);
  }
}

/**
 * Handle an rsync operation from a remote site to a remote
 * site by first rsync'ing to a local location, and then
 * copying that location to its final destination.
 */
function _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path) {
  $options = $additional_options + drush_redispatch_get_options();

  // Make a temporary directory to copy to.  There are three
  // cases to consider:
  //
  // 1. rsync @src:file.txt @dest:location
  // 2. rsync @src:dir @dest:location
  // 3. rsync @src:dir/ @dest:location
  //
  // We will explain each of these in turn.
  //
  // 1. Copy a single file.  We'll split this up like so:
  //
  //    rsync @src:file.txt /tmp/tmpdir
  //    rsync /tmp/tmpdir/file.txt @dest:location
  //
  // Since /tmp/tmpdir is empty, we could also rsync from
  // '/tmp/tmpdir/' if we wished.
  //
  // 2. Copy a directory. A directory with the same name
  // is copied to the destination.  We'll split this up like so:
  //
  //    rsync @src:dir /tmp/tmpdir
  //    rsync /tmp/tmpdir/dir @dest:location
  //
  // The result is that everything in 'dir' is copied to @dest,
  // and ends up in 'location/dir'.
  //
  // 3. Copy the contents of a directory.  We will split this
  // up as follows:
  //
  //    rsync @src:dir/ /tmp/tmpdir
  //    rsync /tmp/tmpdir/ @dest:location
  //
  // After the first rsync, everything in 'dir' will end up in
  // tmpdir.  The second rsync copies everything in tmpdir to
  // @dest:location without creating an encapsulating folder
  // in the destination (i.e. there is no 'tmpdir' in the destination).
  //
  // All three of these cases need to be handled correctly in order
  // to ensure the correct results.  In all cases the first
  // rsync always copies to $tmpDir, however the second rsync has
  // two cases that depend on the source path.  If the source path ends
  // in /, the contents of a directory have been copied to $tmpDir, and
  // the contents of $tmpDir must be copied to the destination.  Otherwise,
  // a specific file or directory has been copied to $tmpDir and that
  // specific item, identified by basename($source_path) must be copied to
  // the destination.

  $putInTmpPath = drush_tempdir();
  $getFromTmpPath = "$putInTmpPath/";
  if (substr($source_path, -1) !== '/') {
    $getFromTmpPath .= basename($source_path);
  }

  // Copy from the source to the temporary location. Exit on failure.
  $values = drush_invoke_process('@self', 'core-rsync', array($source, $putInTmpPath), $options);
  if ($values['error'] != 0) {
    return FALSE;
  }

  // Copy from the temporary location to the final destination.
  $values = drush_invoke_process('@self', 'core-rsync', array($getFromTmpPath, $destination), $options);

  return $values['error'] == 0;
}
<?php

/**
 * @file
 * Use this file as a php scratchpad for your Drupal site. You might want to
 * load a node, change it, and call node_save($node), for example. If you have
 * used the Execute PHP feature of devel.module, this is the drush equivalent.
 *
 * You may edit this file with whatever php you choose. Then execute the file
 * using `drush script scratch.php`. That command will bootstrap your drupal
 * site and then run the php below.
 *
 * The script command enables you to store your script files wherever you wish and
 * will help you list all of them should you collection grow. See its help.
 *
 */

// Just some ideas to get the juices flowing.
drush_print_r(user_roles());
drush_print_r($GLOBALS['user']);
<?php

use Drush\Log\LogLevel;

/**
 * Implementation of hook_drush_help().
 */
function search_drush_help($section) {
  switch ($section) {
    case 'meta:search:title':
      return dt('Search commands');
    case 'meta:search:summary':
      return dt('Interact with Drupal\'s core search system.');
  }
}

function search_drush_command() {
  $items['search-status'] = array(
    'description' => 'Show how many items remain to be indexed out of the total.',
    'drupal dependencies' => array('search'),
    'outputformat' => array(
      'default' => 'message',
      'pipe-format' => 'message',
      'field-labels' => array('remaining' => 'Items not yet indexed', 'total' => 'Total items'),
      'message-template' => 'There are !remaining items out of !total still to be indexed.',
      'pipe-metadata' => array(
        'message-template' => '!remaining/!total',
      ),
      'output-data-type' => 'format-list',
    ),
  );
  $items['search-index'] = array(
    'description' => 'Index the remaining search items without wiping the index.',
    'drupal dependencies' => array('search'),
  );
  $items['search-reindex'] = array(
    'description' => 'Force the search index to be rebuilt.',
    'drupal dependencies' => array('search'),
    'options' => array(
      'immediate' => 'Rebuild the index immediately, instead of waiting for cron.',
    ),
  );
  return $items;
}

function drush_search_status() {
  list($remaining, $total) = _drush_search_status();
  return array(
    'remaining' => $remaining,
    'total' => $total,
  );
}

function _drush_search_status() {
  $remaining = 0;
  $total = 0;
  if (drush_drupal_major_version() >= 8) {
    $search_page_repository = \Drupal::service('search.search_page_repository');
    foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
      $status = $entity->getPlugin()->indexStatus();
      $remaining += $status['remaining'];
      $total += $status['total'];
    }
  }
  elseif (drush_drupal_major_version() == 7) {
    foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
      drush_include_engine('drupal', 'environment');
      $status = drush_module_invoke($module, 'search_status');
      $remaining += $status['remaining'];
      $total += $status['total'];
    }
  }
  else {
    drush_include_engine('drupal', 'environment');
    foreach (drush_module_implements('search') as $module) {
      // Special case. Apachesolr recommends disabling core indexing with
      // search_cron_limit = 0. Need to avoid infinite status loop.
      if ($module == 'node' && variable_get('search_cron_limit', 10) == 0) {
        continue;
      }
      $status = drush_module_invoke($module, 'search', 'status');
      if (isset($status['remaining']) && isset($status['total'])) {
        $remaining += $status['remaining'];
        $total += $status['total'];
      }
    }
  }
  return array($remaining, $total);
}

function drush_search_index() {
  drush_op('_drush_search_index');
  drush_log(dt('The search index has been built.'), LogLevel::OK);
}

function _drush_search_index() {
  list($remaining, $total) = _drush_search_status();
  register_shutdown_function('search_update_totals');
  $failures = 0;
  while ($remaining > 0) {
    $done = $total - $remaining;
    $percent = $done / $total * 100;
    drush_log(dt('!percent complete. Remaining items to be indexed: !count', array('!percent' => number_format($percent, 2), '!count' => $remaining)), LogLevel::OK);
    $eval = "register_shutdown_function('search_update_totals');";

    // Use drush_invoke_process() to start subshell. Avoids out of memory issue.
    if (drush_drupal_major_version() >= 8) {
      $eval = "drush_module_invoke('search', 'cron');";
    }
    elseif (drush_drupal_major_version() == 7) {
      // If needed, prod drush_module_implements() to recognize our
      // hook_node_update_index() implementations.
      drush_include_engine('drupal', 'environment');
      $implementations = drush_module_implements('node_update_index');
      if (!in_array('system', $implementations)) {
        // Note that this resets module_implements cache.
        drush_module_implements('node_update_index', FALSE, TRUE);
      }

      foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
        // TODO: Make sure that drush_module_invoke is really available when doing this eval().
        $eval .= " drush_module_invoke('$module', 'update_index');";
      }
    }
    else {
      // If needed, prod module_implements() to recognize our hook_nodeapi()
      // implementations.
      $implementations = module_implements('nodeapi');
      if (!in_array('system', $implementations)) {
        // Note that this resets module_implements cache.
        module_implements('nodeapi', FALSE, TRUE);
      }

      $eval .= " module_invoke_all('update_index');";
    }
    drush_invoke_process('@self', 'php-eval', array($eval));
    $previous_remaining = $remaining;
    list($remaining, ) = _drush_search_status();
    // Make sure we're actually making progress.
    if ($remaining == $previous_remaining) {
      $failures++;
      if ($failures == 3) {
        drush_log(dt('Indexing stalled with @number items remaining.', array(
          '@number' => $remaining,
        )), LogLevel::ERROR);
        return;
      }
    }
    // Only count consecutive failures.
    else {
      $failures = 0;
    }
  }
}

function drush_search_reindex() {
  drush_print(dt("The search index must be fully rebuilt before any new items can be indexed."));
  if (drush_get_option('immediate')) {
    drush_print(dt("Rebuilding the index may take a long time."));
  }
  if (!drush_confirm(dt('Do you really want to continue?'))) {
    return drush_user_abort();
  }

  if (drush_drupal_major_version() == 8) {
    // D8 CR: https://www.drupal.org/node/2326575
    $search_page_repository = \Drupal::service('search.search_page_repository');
    foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
      $entity->getPlugin()->markForReindex();
    }
  }
  elseif (drush_drupal_major_version() == 7) {
    drush_op('search_reindex');
  }
  else {
    drush_op('search_wipe');
  }

  if (drush_get_option('immediate')) {
    drush_op('_drush_search_index');
    drush_log(dt('The search index has been rebuilt.'), LogLevel::OK);
  }
  else {
    drush_log(dt('The search index will be rebuilt.'), LogLevel::OK);
  }
}

/**
 * Fake an implementation of hook_node_update_index() for Drupal 7.
 */
function system_node_update_index($node) {
  // Verbose output.
  if (drush_get_context('DRUSH_VERBOSE')) {
    $nid = $node->nid;
    if (is_object($nid)) {
      // In D8, this is a FieldItemList.
      $nid = $nid->value;
    }

    drush_log(dt('Indexing node !nid.', array('!nid' => $nid)), LogLevel::OK);
  }
}

/**
 * Fake an implementation of hook_nodeapi() for Drupal 6.
 */
function system_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'update index') {
    // Verbose output.
    if (drush_get_context('DRUSH_VERBOSE')) {
      drush_log(dt('Indexing node !nid.', array('!nid' => $node->nid)), LogLevel::OK);
    }
  }
}
<?php

/**
 * @file
 *   Shell alias commands. @see example.drushrc.php for details.
 */

function shellalias_drush_help($section) {
  switch ($section) {
    case 'drush:shell-alias':
      return dt('Print a shell alias record.');
  }
}

/**
 * Command argument complete callback.
 *
 * @return
 *  Array of available site aliases.
 */
function shellalias_shell_alias_complete() {
  if ($all = drush_get_context('shell-aliases', array())) {
    return array('values' => array_keys($all));
  }
}

function shellalias_drush_command() {
  $items = array();

  $items['shell-alias'] = array(
    'description' => 'Print all known shell alias records.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'arguments' => array(
      'alias' => 'Shell alias to print',
    ),
    'outputformat' => array(
      'default' => 'key-value',
      'pipe-format' => 'json',
      'simplify-single' => TRUE,
      'output-data-type' => 'format-list',
    ),
    'aliases' => array('sha'),
    'examples' => array(
      'drush shell-alias' => 'List all alias records known to drush.',
      'drush shell-alias pull' => 'Print the value of the shell alias \'pull\'.',
    ),
  );
  return $items;
}

/**
 * Print out the specified shell aliases.
 */
function drush_core_shell_alias($alias = FALSE) {
  $shell_aliases = drush_get_context('shell-aliases', array());
  if (!$alias) {
    return $shell_aliases;
  }
  elseif (isset($shell_aliases[$alias])) {
    return array($alias => $shell_aliases[$alias]);
  }
}
<?php

use Drush\Log\LogLevel;
use Drupal\Core\Config\FileStorage;

function site_install_drush_command() {
  $items['site-install'] = array(
    'description' => 'Install Drupal along with modules/themes/configuration using the specified install profile.',
    'arguments' => array(
      // In Drupal 7 installation profiles can be marked as exclusive by placing
      // a
      // @code
      //   exclusive: true
      // @endcode
      // line in the profile's info file.
      // See https://www.drupal.org/node/1022020 for more information.
      //
      // In Drupal 8 you can turn your installation profile into a distribution
      // by providing a
      // @code
      //   distribution:
      //     name: 'Distribution name'
      // @endcode
      // block in the profile's info YAML file.
      // See https://www.drupal.org/node/2210443 for more information.
      'profile' => 'The install profile you wish to run. Defaults to \'default\' in D6, \'standard\' in D7+, unless an install profile is marked as exclusive (or as a distribution in D8+ terminology) in which case that is used.',
      'key=value...' => 'Any additional settings you wish to pass to the profile. Fully supported on D7+, partially supported on D6 (single step configure forms only). The key is in the form [form name].[parameter name] on D7 or just [parameter name] on D6.',
    ),
    'options' => array(
      'db-url' => array(
        'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.',
        'example-value' => 'mysql://root:pass@host/db',
      ),
      'db-prefix' => 'An optional table prefix to use for initial install.  Can be a key-value array of tables/prefixes in a drushrc file (not the command line).',
      'db-su' => array(
        'description' => 'Account to use when creating a new database. Must have Grant permission (mysql only). Optional.',
        'example-value' => 'root',
      ),
      'db-su-pw' => array(
        'description' => 'Password for the "db-su" account. Optional.',
        'example-value' => 'pass',
      ),
      'account-name' => 'uid1 name. Defaults to admin',
      'account-pass' => 'uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drushrc.php.',
      'account-mail' => 'uid1 email. Defaults to admin@example.com',
      'locale' => array(
        'description' => 'A short language code. Sets the default site language. Language files must already be present. You may use download command to get them.',
        'example-value' => 'en-GB',
      ),
      'clean-url'=> 'Defaults to clean; use --no-clean-url to disable. Note that Drupal 8 and later requires clean.',
      'site-name' => 'Defaults to Site-Install',
      'site-mail' => 'From: for system mailings. Defaults to admin@example.com',
      'sites-subdir' => array(
        'description' => "Name of directory under 'sites' which should be created. Only needed when the subdirectory does not already exist. Defaults to 'default'",
        'value' => 'required',
        'example-value' => 'directory_name',
      ),
      'config-dir' => 'A path pointing to a full set of configuration which should be imported after installation.',
    ),
    'examples' => array(
      'drush site-install expert --locale=uk' => '(Re)install using the expert install profile. Set default language to Ukrainian.',
      'drush site-install --db-url=mysql://root:pass@localhost:port/dbname' => 'Install using the specified DB params.',
      'drush site-install --db-url=sqlite://sites/example.com/files/.ht.sqlite' => 'Install using SQLite (D7+ only).',
      'drush site-install --account-name=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.',
      'drush site-install standard install_configure_form.site_default_country=FR my_profile_form.my_settings.key=value' => 'Pass additional arguments to the profile (D7 example shown here - for D6, omit the form id).',
      "drush site-install standard install_configure_form.update_status_module='array(FALSE,FALSE)'" => 'Disable email notification during install and later (D7). If your server has no mail transfer agent, this gets rid of an error during install.',
      'drush site-install standard install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL' => 'Disable email notification during install and later (D8). If your server has no mail transfer agent, this gets rid of an error during install.',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
    'aliases' => array('si'),
  );
  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function site_install_drush_help_alter(&$command) {
  // Drupal version-specific customizations.
  if ($command['command'] == 'site-install') {
    if (drush_drupal_major_version() >= 8) {
      unset($command['options']['clean-url']);
    }
    else {
      unset($command['options']['config-dir']);
    }
  }
}

/**
 * Command validate.
 */
function drush_core_site_install_validate() {
  if ($sites_subdir = drush_get_option('sites-subdir')) {
    $lower = strtolower($sites_subdir);
    if ($sites_subdir != $lower) {
      drush_log(dt('Only lowercase sites-subdir are valid. Switching to !lower.', array('!lower' => $lower)), LogLevel::WARNING);
      drush_set_option('sites-subdir', $lower);
    }
    // Make sure that we will bootstrap to the 'sites-subdir' site.
    drush_set_context('DRUSH_SELECTED_URI', 'http://' . $sites_subdir);
  }

  if ($config = drush_get_option('config-dir')) {
    if (!file_exists($config)) {
      return drush_set_error('config_import_target', 'The config source directory does not exist.');
    }
    if (!is_dir($config)) {
      return drush_set_error('config_import_target', 'The config source is not a directory.');
    }
    $configFiles = glob("$config/*.yml");
    if (empty($configFiles)) {
      drush_log(dt('Configuration import directory !config does not contain any configuration; will skip import.', array('!config' => $config)), LogLevel::WARNING);
      drush_set_option('config-dir', '');
    }
   }
}

/**
 * Perform setup tasks for installation.
 */
function drush_core_pre_site_install($profile = NULL) {
  $sql = drush_sql_get_class();
  if (!$db_spec = $sql->db_spec()) {
    drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.'));
    return;
  }

  // Make sure URI is set so we get back a proper $alias_record. Needed for quick-drupal.
  _drush_bootstrap_selected_uri();

  $alias_record = drush_sitealias_get_record('@self');
  $sites_subdir = drush_sitealias_local_site_path($alias_record);
  // Override with sites-subdir if specified.
  if ($dir = drush_get_option('sites-subdir')) {
    $sites_subdir = "sites/$dir";
  }
  $conf_path = $sites_subdir;
  // Handle the case where someuse uses --variables to set the file public path. Won't work on D8+.
  $files = !empty($GLOBALS['conf']['files_public_path']) ? $GLOBALS['conf']['files_public_path'] : "$conf_path/files";
  $settingsfile = "$conf_path/settings.php";
  $sitesfile = "sites/sites.php";
  $default = realpath($alias_record['root'] . '/sites/default');
  $sitesfile_write = drush_drupal_major_version() >= 8 && $conf_path != $default && !file_exists($sitesfile);

  if (!file_exists($settingsfile)) {
    $msg[] = dt('create a @settingsfile file', array('@settingsfile' => $settingsfile));
  }
  if ($sitesfile_write) {
    $msg[] = dt('create a @sitesfile file', array('@sitesfile' => $sitesfile));
  }
  if ($sql->db_exists()) {
    $msg[] = dt("DROP all tables in your '@db' database.", array('@db' => $db_spec['database']));
  }
  else {
    $msg[] = dt("CREATE the '@db' database.", array('@db' => $db_spec['database']));
  }

  if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) {
    return drush_user_abort();
  }

  // Can't install without sites subdirectory and settings.php.
  if (!file_exists($conf_path)) {
    if (!drush_mkdir($conf_path) && !drush_get_context('DRUSH_SIMULATE')) {
      drush_set_error(dt('Failed to create directory @conf_path', array('@conf_path' => $conf_path)));
      return;
    }
  }
  else {
    drush_log(dt('Sites directory @subdir already exists - proceeding.', array('@subdir' => $conf_path)));
  }

  if (!drush_file_not_empty($settingsfile)) {
    if (!drush_op('copy', 'sites/default/default.settings.php', $settingsfile) && !drush_get_context('DRUSH_SIMULATE')) {
      return drush_set_error(dt('Failed to copy sites/default/default.settings.php to @settingsfile', array('@settingsfile' => $settingsfile)));
    }

    if (drush_drupal_major_version() == 6) {
      // On D6, we have to write $db_url ourselves. In D7+, the installer does it.
      file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND);
      // Instead of parsing and performing string replacement on the configuration file,
      // the options are appended and override the defaults.
      // Database table prefix
      if (!empty($db_spec['db_prefix'])) {
        if (is_array($db_spec['db_prefix'])) {
          // Write db_prefix configuration as an array
          $db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';';
        }
        else {
          // Write db_prefix configuration as a string
          $db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';';
        }
        file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND);
      }
    }
  }

  // Write an empty sites.php if we are on D8 and using multi-site.
  if ($sitesfile_write) {
    if (!drush_op('copy', 'sites/example.sites.php', $sitesfile) && !drush_get_context('DRUSH_SIMULATE')) {
      return drush_set_error(dt('Failed to copy sites/example.sites.php to @sitesfile', array('@sitesfile' => $sitesfile)));
    }
  }

  // We need to be at least at DRUSH_BOOTSTRAP_DRUPAL_SITE to select the site uri to install to
  define('MAINTENANCE_MODE', 'install');
  if (drush_drupal_major_version() == 6) {
    // The Drupal 6 installer needs to bootstrap up to the specified site.
    drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  }
  else {
    drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  }

  if (!$sql->drop_or_create($db_spec)) {
    return drush_set_error(dt('Failed to create database: @error', array('@error' => implode(drush_shell_exec_output()))));
  }

  return TRUE;
}

/**
 * Command callback.
 */
function drush_core_site_install($profile = NULL) {
  $args = func_get_args();
  $form_options = array();

  if ($args) {
    // The first argument is the profile.
    $profile = array_shift($args);
    // Subsequent arguments are additional form values.
    foreach ($args as $arg) {
      list($key, $value) = explode('=', $arg, 2);

      // Allow for numeric and NULL values to be passed in.
      if (is_numeric($value)) {
        $value = intval($value);
      }
      elseif ($value == 'NULL') {
        $value = NULL;
      }

      $form_options[$key] = $value;
    }
  }

  // If the profile is not explicitly set, default to the 'minimal' for an issue-free config import.
  if (empty($profile) && drush_get_option('config-dir')) {
    $profile = 'minimal';
  }

  drush_include_engine('drupal', 'site_install');
  drush_core_site_install_version($profile, $form_options);

  // Post installation, run the configuration import.
  if ($config = drush_get_option('config-dir')) {
    // Set the destination site UUID to match the source UUID, to bypass a core fail-safe.
    $source_storage = new FileStorage($config);
    $options = ['yes' => TRUE];
    drush_invoke_process('@self', 'config-set', array('system.site', 'uuid', $source_storage->read('system.site')['uuid']), $options);
    // Run a full configuration import.
    drush_invoke_process('@self', 'config-import', array(), array('source' => $config) + $options);
  }
}
<?php

/**
 * @file
 *   Site alias commands. @see example.drushrc.php for details.
 */

function sitealias_drush_help($section) {
  switch ($section) {
    case 'drush:site-alias':
      return dt('Print an alias record.');
  }
}

function sitealias_drush_command() {
  $items = array();

  $items['site-alias'] = array(
    'callback' => 'drush_sitealias_print',
    'description' => 'Print site alias records for all known site aliases and local sites.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'arguments' => array(
      'site' => 'Site specification to print',
    ),
    'options' => array(
      'with-db' => 'Include the databases structure in the full alias record.',
      'with-db-url' => 'Include the short-form db-url in the full alias record.',
      'no-db' => 'Do not include the database record in the full alias record (default).',
      'with-optional' => 'Include optional default items.',
      'alias-name' => 'For a single alias, set the name to use in the output.',
      'local-only' => 'Only display sites that are available on the local system (remote-site not set, and Drupal root exists).',
      'show-hidden' => 'Include hidden internal elements in site alias output',
    ),
    'outputformat' => array(
      'default' => 'config',
      'pipe-format' => 'var_export',
      'variable-name' => 'aliases',
      'hide-empty-fields' => TRUE,
      'private-fields' => 'password',
      'field-labels' => array('#name' => 'Name', 'root' => 'Root', 'uri' => 'URI', 'remote-host' => 'Host', 'remote-user' => 'User', 'remote-port' => 'Port', 'os' => 'OS', 'ssh-options' => 'SSH options', 'php' => 'PHP'),
      'fields-default' => array('#name', 'root', 'uri', 'remote-host', 'remote-user'),
      'field-mappings' => array('name' => '#name'),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('sa'),
    'examples' => array(
      'drush site-alias' => 'List all alias records known to drush.',
      'drush site-alias @dev' => 'Print an alias record for the alias \'dev\'.',
      'drush @none site-alias' => 'Print only actual aliases; omit multisites from the local Drupal installation.',
    ),
    'topics' => array('docs-aliases'),
  );
  $items['site-set'] = array(
    'description' => 'Set a site alias to work on that will persist for the current session.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'handle-remote-commands' => TRUE,
    'arguments' => array(
      'site' => 'Site specification to use, or "-" for previous site. Omit this argument to "unset"',
    ),
    'aliases' => array('use'),
    'examples' => array(
      'drush site-set @dev' => 'Set the current session to use the @dev alias.',
      'drush site-set user@server/path/to/drupal#sitename' => 'Set the current session to use a remote site via site specification.',
      'drush site-set /path/to/drupal#sitename' => 'Set the current session to use a local site via site specification.',
      'drush site-set -' => 'Go back to the previously-set site (like `cd -`).',
      'drush site-set' => 'Without an argument, any existing site becomes unset.',
    ),
  );
  return $items;
}

/**
 * Command argument complete callback.
 *
 * @return
 *  Array of available site aliases.
 */
function sitealias_site_alias_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/**
 * Command argument complete callback.
 *
 * @return
 *  Array of available site aliases.
 */
function sitealias_site_set_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/**
 * Return a list of all site aliases known to drush.
 *
 * The array key is the site alias name, and the array value
 * is the site specification for the given alias.
 */
function _drush_sitealias_alias_list() {
  return drush_get_context('site-aliases');
}

/**
 * Return a list of all of the local sites at the current drupal root.
 *
 * The array key is the site folder name, and the array value
 * is the site specification for that site.
 */
function _drush_sitealias_site_list() {
  $site_list = array();
  $base_path = drush_get_context('DRUSH_DRUPAL_ROOT');
  if ($base_path) {
    $base_path .= '/sites';
    $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1);
    foreach ($files as $filename => $info) {
      if ($info->basename == 'settings.php') {
        $alias_record = drush_sitealias_build_record_from_settings_file($filename);
        if (!empty($alias_record)) {
          $site_list[drush_sitealias_uri_to_site_dir($alias_record['uri'])] = $alias_record;
        }
      }
    }
  }
  return $site_list;
}

/**
 * Return the list of all site aliases and all local sites.
 */
function _drush_sitealias_all_list() {
  drush_sitealias_load_all();
  return array_merge(_drush_sitealias_alias_list(), _drush_sitealias_site_list());
}

/**
 * Return the list of site aliases (remote or local) that the
 * user specified on the command line.  If none were specified,
 * then all are returned.
 */
function _drush_sitealias_user_specified_list() {
  $command = drush_get_command();
  $specifications = $command['arguments'];
  $site_list = array();

  // Iterate over the arguments and convert them to alias records
  if (!empty($specifications)) {
    list($site_list, $not_found) = drush_sitealias_resolve_sitespecs($specifications);
    if (!empty($not_found)) {
      return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt("Not found: @list", array("@list" => implode(', ', $not_found))));
    }
  }
  // If the user provided no args, then we will return everything.
  else {
    drush_set_default_outputformat('list');
    $site_list = _drush_sitealias_all_list();

    // Filter out the hidden items
    foreach ($site_list as $site_name => $one_site) {
      if (array_key_exists('#hidden', $one_site)) {
        unset($site_list[$site_name]);
      }
    }
  }

  // Filter for only local sites if specified.
  if (drush_get_option('local-only', FALSE)) {
    foreach ($site_list as $site_name => $one_site) {
      if ( (array_key_exists('remote-site', $one_site)) ||
           (!array_key_exists('root', $one_site)) ||
           (!is_dir($one_site['root']))
         ) {
        unset($site_list[$site_name]);
      }
    }
  }
  return $site_list;
}

/**
 * Print out the specified site aliases (or else all) using the format
 * specified.
 */
function drush_sitealias_print() {
  // Try to get the @self alias to be defined.
  $phase = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  $site_list = _drush_sitealias_user_specified_list();
  if ($site_list === FALSE) {
    return FALSE;
  }
  ksort($site_list);
  $with_db = (drush_get_option('with-db') != NULL) || (drush_get_option('with-db-url') != NULL);

  $site_specs = array();
  foreach ($site_list as $site => $alias_record) {
    $result_record = _drush_sitealias_prepare_record($alias_record);
    $site_specs[$site] = $result_record;
  }
  ksort($site_specs);
  return $site_specs;
}

/**
 * Given a site alias name, print out a php-syntax
 * representation of it.
 *
 * @param alias_record
 *   The name of the site alias to print
 */
function _drush_sitealias_prepare_record($alias_record) {
  $output_db = drush_get_option('with-db');
  $output_db_url = drush_get_option('with-db-url');
  $output_optional_items = drush_get_option('with-optional');

  // Make sure that the default items have been added for all aliases
  _drush_sitealias_add_static_defaults($alias_record);

  // Include the optional items, if requested
  if ($output_optional_items) {
    _drush_sitealias_add_transient_defaults($alias_record);
  }

  drush_sitealias_resolve_path_references($alias_record);

  if (isset($output_db_url) || isset($output_db)) {
    drush_sitealias_add_db_settings($alias_record);
  }
  // If the user specified --with-db-url, then leave the
  // 'db-url' entry in the alias record (unless it is not
  // set, in which case we will leave the 'databases' record instead).
  if (isset($output_db_url)) {
    if (!isset($alias_record['db-url'])) {
      $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']);
    }
    unset($alias_record['databases']);
  }
  // If the user specified --with-db, then leave the
  // 'databases' entry in the alias record.
  else if (isset($output_db)) {
    unset($alias_record['db-url']);
  }
  // If neither --with-db nor --with-db-url were specified,
  // then remove both the 'db-url' and the 'databases' entries.
  else {
    unset($alias_record['db-url']);
    unset($alias_record['databases']);
  }

  // We don't want certain fields to go into the output
  if (!drush_get_option('show-hidden')) {
    foreach ($alias_record as $key => $value) {
      if ($key[0] == '#') {
        unset($alias_record[$key]);
      }
    }
  }

  // We only want to output the 'root' item; don't output the '%root' path alias
  if (array_key_exists('path-aliases', $alias_record) && array_key_exists('%root', $alias_record['path-aliases'])) {
    unset($alias_record['path-aliases']['%root']);
    // If there is nothing left in path-aliases, then clear it out
    if (count($alias_record['path-aliases']) == 0) {
      unset($alias_record['path-aliases']);
    }
  }

  return $alias_record;
}

function _drush_sitealias_print_record($alias_record, $site_alias = '') {
  $result_record = _drush_sitealias_prepare_record($alias_record);

  // The alias name will be the same as the site alias name,
  // unless the user specified some other name on the command line.
  $alias_name = drush_get_option('alias-name');
  if (!isset($alias_name)) {
    $alias_name = $site_alias;
    if (empty($alias_name) || is_numeric($alias_name)) {
      $alias_name = drush_sitealias_uri_to_site_dir($result_record['uri']);
    }
  }

  // Alias names contain an '@' when referenced, but do
  // not contain an '@' when defined.
  if (substr($alias_name,0,1) == '@') {
    $alias_name = substr($alias_name,1);
  }

  $exported_alias = var_export($result_record, TRUE);
  drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';');
}

/**
 * Use heuristics to attempt to convert from a site directory to a URI.
 * This function should only be used when the URI really is unknown, as
 * the mapping is not perfect.
 *
 * @param site_dir
 *   A directory, such as domain.com.8080.drupal
 *
 * @return string
 *   A uri, such as http://domain.com:8080/drupal
 */
function _drush_sitealias_site_dir_to_uri($site_dir) {
  // Protect IP addresses NN.NN.NN.NN by converting them
  // temporarily to NN_NN_NN_NN for now.
  $uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir);
  // Convert .[0-9]+. into :[0-9]+/
  $uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri);
  // Convert .[0-9]$ into :[0-9]
  $uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri);
  // Convert .(com|net|org|info). into .(com|net|org|info)/
  $uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri);

  // If there is a / then convert every . after the / to /
  // Then again, if we did this we would break if the path contained a "."
  // I hope that the path would never contain a "."...
  $pos = strpos($uri, '/');
  if ($pos !== false) {
    $uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1));
  }

  // n.b. this heuristic works all the time if there is a port,
  // it also works all the time if there is a port and no path,
  // but it does not work for domains such as .co.jp with no path,
  // and it can fail horribly if someone makes a domain like "info.org".
  // Still, I think this is the best we can do short of consulting DNS.

  // Convert from NN_NN_NN_NN back to NN.NN.NN.NN
  $uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir);

  return 'http://' . $uri;
}

/**
 * Validation callback for drush site-set.
 */
function drush_sitealias_site_set_validate() {
  if (!function_exists('posix_getppid')) {
    $args = array('!command' => 'site-set', '!dependencies' => 'POSIX');
    return drush_set_error('DRUSH_COMMAND_PHP_DEPENDENCY_ERROR', dt('Command !command needs the following PHP extensions installed/enabled to run: !dependencies.', $args));
  }
}

/**
 * Set the DRUPAL_SITE variable by writing it out to a temporary file that we
 * then source for persistent site switching.
 *
 * @param site
 *  A valid site specification.
 */
function drush_sitealias_site_set($site = '@none') {
  if ($filename = drush_sitealias_get_envar_filename()) {
    $last_site_filename = drush_sitealias_get_envar_filename('drush-drupal-prev-site-');
    if ($site == '-') {
      if (file_exists($last_site_filename)) {
        $site = file_get_contents($last_site_filename);
      }
      else {
        $site = '@none';
      }
    }
    if ($site == '@self') {
      $path = drush_cwd();
      $site_record = drush_sitealias_lookup_alias_by_path($path, TRUE);
      if (isset($site_record['#name'])) {
        $site = '@' . $site_record['#name'];
      }
      else {
        $site = '@none';
      }
      // Using 'site-set @self' is quiet if there is no change.
      $current = is_file($filename) ? trim(file_get_contents($filename)) : "@none";
      if ($current == $site) {
        return;
      }
    }
    if (_drush_sitealias_set_context_by_name($site)) {
      if (file_exists($filename)) {
        @unlink($last_site_filename);
        @rename($filename, $last_site_filename);
      }
      $success_message = dt("Site set to !site", array('!site' => $site));
      if ($site == '@none') {
        if (drush_delete_dir($filename)) {
          drush_print($success_message);
        }
      }
      elseif (drush_mkdir(dirname($filename), TRUE)) {
        if (file_put_contents($filename, $site)) {
          drush_print($success_message);
          drush_log(dt("Site information stored in !file", array('!file' => $filename)));
        }
      }
    }
    else {
      return drush_set_error('DRUPAL_SITE_NOT_FOUND', dt("Could not find a site definition for !site.", array('!site' => $site)));
    }
  }
}
<?php

/**
* @file
*  The drush site-ssh command for connecting to a remote alias' server via
*  SSH, either for an interactive session or to run a shell command.
*/

function ssh_drush_command() {
  $items['site-ssh'] = array(
    'description' => 'Connect to a Drupal site\'s server via SSH for an interactive session or to run a shell command',
    'arguments' => array(
      'bash' => 'Bash to execute on target. Optional, except when site-alias is a list.',
    ),
    'options' => array(
      'cd' => "Directory to change to. Use a full path, TRUE for the site's Drupal root directory, or --no-cd for the ssh default (usually the remote user's home directory). Defaults to the Drupal root.",
    ) + drush_shell_exec_proc_build_options(),
    'handle-remote-commands' => TRUE,
    'strict-option-handling' => TRUE,
    'examples' => array(
      'drush @mysite ssh' => 'Open an interactive shell on @mysite\'s server.',
      'drush @prod ssh ls /tmp' => 'Run "ls /tmp" on @prod site. If @prod is a site list, then ls will be executed on each site.',
      'drush @prod ssh git pull' => 'Run "git pull" on the Drupal root directory on the @prod site.',
    ),
    'aliases' => array('ssh'),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'topics' => array('docs-aliases'),
  );
  return $items;
}

/**
 * Command callback.
 */
function drush_ssh_site_ssh($command = NULL) {
  // Get all of the args and options that appear after the command name.
  $args = drush_get_original_cli_args_and_options();
  // n.b. we do not escape the first (0th) arg to allow `drush ssh 'ls /path'`
  // to work in addition to the preferred form of `drush ssh ls /path`.
  // Supporting the legacy form means that we cannot give the full path to an
  // executable if it contains spaces.
  for ($x = 1; $x < count($args); $x++) {
    $args[$x] = drush_escapeshellarg($args[$x]);
  }
  $command = implode(' ', $args);
  if (!$alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
    return drush_set_error('DRUSH_MISSING_TARGET_ALIAS', 'A site alias is required. The way you call ssh command has changed to `drush @alias ssh`.');
  }
  $site = drush_sitealias_get_record($alias);
  // If we have multiple sites, run ourselves on each one. Set context back when done.
  if (isset($site['site-list'])) {
    if (empty($command)) {
      drush_set_error('DRUSH_SITE_SSH_COMMAND_REQUIRED', dt('A command is required when multiple site aliases are specified.'));
      return;
    }
    foreach ($site['site-list'] as $alias_single) {
      drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias_single);
      drush_ssh_site_ssh($command);
    }
    drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias);
    return;
  }

  if (!drush_sitealias_is_remote_site($alias)) {
    // Local sites run their bash without SSH.
    $return = drush_invoke_process('@self', 'core-execute', array($command), array('escape' => FALSE));
    return $return['object'];
  }

  // We have a remote site - build ssh command and run.
  $interactive = FALSE;
  $cd = drush_get_option('cd', TRUE);
  if (empty($command)) {
    $command = 'bash -l';
    $interactive = TRUE;
  }
  $cmd = drush_shell_proc_build($site, $command, $cd, $interactive);
  $status = drush_shell_proc_open($cmd);
  if ($status != 0) {
    return drush_set_error('DRUSH_SITE_SSH_ERROR', dt('An error @code occurred while running the command `@command`', array('@command' => $cmd, '@code' => $status)));
  }
}
<?php

/**
 * @file
 *   Provides State commands.
 */

/**
 * Implementation of hook_drush_help().
 */
function state_drush_help($section) {
  switch ($section) {
    case 'meta:state:title':
      return dt('State commands');
    case 'meta:state:summary':
      return dt('Interact with the State system.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function state_drush_command() {
  $items['state-get'] = array(
    'description' => 'Display a state value.',
    'arguments' => array(
      'key' => 'The key name.',
    ),
    'required-arguments' => 1,
    'examples' => array(
      'drush state-get system.cron_last' => 'Displays last cron run timestamp',
    ),
    'outputformat' => array(
      'default' => 'json',
      'pipe-format' => 'json',
    ),
    'aliases' => array('sget'),
    'core' => array('8+'),
  );

  $items['state-set'] = array(
    'description' => 'Set a state value.',
    'arguments' => array(
      'key' => 'The state key, for example "system.cron_last".',
      'value' => 'The value to assign to the state key. Use \'-\' to read from STDIN.',
    ),
    'required arguments' => 2,
    'options' => array(
      'format' => array(
        'description' => 'Deprecated. See input-format option.',
        'example-value' => 'boolean',
        'value' => 'required',
      ),
      'input-format' => array(
        'description' => 'Type for  the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.',
        'example-value' => 'boolean',
        'value' => 'required',
      ),
      // A convenient way to pass a multiline value within a backend request.
      'value' => array(
        'description' => 'The value to assign to the state key (if any).',
        'hidden' => TRUE,
      ),
    ),
    'examples' => array(
      'drush state-set system.cron_last 1406682882 --format=integer' => 'Sets a timestamp for last cron run.',
      'php -r "print json_encode(array(\'drupal\', \'simpletest\'));"  | drush state-set --format=json foo.name -'=> 'Set a key to a complex value (e.g. array)',
    ),
    'aliases' => array('sset'),
    'core' => array('8+'),
  );

  $items['state-delete'] = array(
    'description' => 'Delete a state value.',
    'arguments' => array(
      'key' => 'The state key, for example "system.cron_last".',
    ),
    'required arguments' => 1,
    'examples' => array(
      'drush state-del system.cron_last' => 'Delete state entry for system.cron_last.',
    ),
    'aliases' => array('sdel'),
    'core' => array('8+'),
  );

  return $items;
}

/**
 * State get command callback.
 *
 * @state $key
 *   The state key.
 */
function drush_state_get($key = NULL) {
  return \Drupal::state()->get($key);
}

/**
 * State set command callback.
 *
 * @param $key
 *   The config key.
 * @param $value
 *    The data to save to state.
 */
function drush_state_set($key = NULL, $value = NULL) {
  // This hidden option is a convenient way to pass a value without passing a key.
  $value = drush_get_option('value', $value);

  if (!isset($value)) {
    return drush_set_error('DRUSH_STATE_ERROR', dt('No state value specified.'));
  }

  // Special flag indicating that the value has been passed via STDIN.
  if ($value === '-') {
    $value = stream_get_contents(STDIN);
  }

  // If the value is a string (usual case, unless we are called from code),
  // then format the input.
  if (is_string($value)) {
    $value = drush_value_format($value, drush_get_option('format', 'auto'));
  }

  \Drupal::state()->set($key, $value);
}

/**
 * State delete command callback.
 *
 * @state $key
 *   The state key.
 */
function drush_state_delete($key = NULL) {
  \Drupal::state()->delete($key);
}
<?php

/**
 * @file
 *   Topic command and associated hooks.
 */

/**
 * Implementation of hook_drush_command().
 *
 * @return
 *   An associative array describing your command(s).
 */
function topic_drush_command() {
  $items['core-topic'] = array(
    'description' => 'Read detailed documentation on a given topic.',
    'arguments' => array(
      'topic name' => 'The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).',
    ),
    'examples' => array(
      'drush topic' => 'Show all available topics.',
      'drush topic docs-context' => 'Show documentation for the drush context API',
      'drush docs-context' => 'Show documentation for the drush context API',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'remote-tty' => TRUE,
    'aliases' => array('topic'),
    'topics' => array('docs-readme'),
  );

  return $items;
}

/**
 * Implement hook_drush_help_alter(). Show 'Topics' section on help detail.
 */
function topic_drush_help_alter(&$command) {
  $implemented = drush_get_commands();
  foreach ($command['topics'] as $topic_name) {
    // We have a related topic. Inject into the $command so the topic displays.
    $command['sections']['topic_section'] = 'Topics';
    $command['topic_section'][$topic_name] = $implemented[$topic_name]['description'];
  }
}

/**
 * A command callback.
 *
 * Show a choice list of available topics and then dispatch to the respective command.
 *
 * @param string $topic_name
 *   A command name.
 */
function drush_topic_core_topic($topic_name = NULL) {
  $commands = drush_get_commands();
  $topics = drush_get_topics();
  if (isset($topic_name)) {
    foreach (drush_get_topics() as $key => $topic) {
      if (strstr($key, $topic_name) === FALSE) {
        unset($topics[$key]);
      }
    }
  }
  if (empty($topics)) {
    return drush_set_error('DRUSH_NO_SUCH_TOPIC', dt("No topics on !topic found.", array('!topic' => $topic_name)));
  }
  if (count($topics) > 1) {
    // Show choice list.
    foreach ($topics as $key => $topic) {
      $choices[$key] = $topic['description'];
    }
    natcasesort($choices);
    if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)', array(5))) {
      return drush_user_abort();
    }
  }
  else {
    $keys = array_keys($topics);
    $topic_name = array_pop($keys);
  }
  return drush_dispatch($commands[$topic_name]);
}

/**
 * A command argument complete callback.
 *
 * @return
 *   Available topic keys.
 */
function topic_core_topic_complete() {
  return array('values' => array_keys(drush_get_topics()));
}

/**
 * Retrieve all defined topics
 */
function drush_get_topics() {
  $commands = drush_get_commands();
  foreach ($commands as $key => $command) {
    if (!empty($command['topic']) && empty($command['is_alias'])) {
      $topics[$key] = $command;
    }
  }
  return $topics;
}
<?php

/**
 * @file
 *   Send scrubbed usage data to drush. Omits arguments and option values in order
 *   to assure that no sensitive data is shared. See http://drupal.org/node/1246738.
 */

use Drush\Log\LogLevel;

/**
 * To send usage data, add the following to a .drushrc.php file:
 * $options['drush_usage_log'] = TRUE;
 * $options['drush_usage_send'] = TRUE;
 * $options['drush_usage_size'] = 51200;
*/

function usage_drush_command() {
  $disclaimer = 'Usage statistics contain the Drush command name and the Drush option names, but no arguments or option values.';
  $items['usage-show'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Show Drush usage information that has been logged but not sent.  ' . $disclaimer,
    'hidden' => TRUE,
    'examples' => array(
      'drush usage-show' => 'Show cached usage statistics.',
      '$options[\'drush_usage_log\']  = TRUE;' => 'Specify in a .drushrc.php file that usage information should be logged locally in a usage statistics file.',
    ),
    'aliases' => array('ushow'),
  );
  $items['usage-send'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'hidden' => TRUE,
    'description' => 'Send anonymous Drush usage information to statistics logging site.  ' . $disclaimer,
    'examples' => array(
      'drush usage-send' => 'Immediately send cached usage statistics.',
      '$options[\'drush_usage_send\']  = TRUE;' => 'Specify in a .drushrc.php file that usage information should be sent.',
      '$options[\'drush_usage_size\']  = 10240;' => 'Specify the frequency (file size) that usage information should be sent.',
    ),
    'aliases' => array('usend'),
  );
  return $items;
}

/**
 * Log and/or send usage data to Mongolab.
 *
 * An organization can implement own hook_drush_exit() to send data to a
 * different endpoint.
 */
function usage_drush_exit() {
  // Ignore statistics for simulated commands. (n.b. in simulated mode, _drush_usage_mongolab will print rather than send statistics)
  if (!drush_get_context('DRUSH_SIMULATE')) {
    $file = _drush_usage_get_file();
    if (drush_get_option('drush_usage_log', FALSE)) {
      _drush_usage_log(drush_get_command(), $file);
    }
    if (drush_get_option('drush_usage_send', FALSE)) {
      _drush_usage_mongolab($file, drush_get_option('drush_usage_size', 51200));
    }
  }
}

/**
 * Set option to send usage to Mongolab.
 *
 * See usage_drush_exit() for more information.
 */
function drush_usage_send() {
  $file = _drush_usage_get_file(TRUE);
  if ($file) {
    drush_set_option('drush_usage_send', TRUE);
    drush_set_option('drush_usage_size', 0);
    drush_print(dt('To automatically send anonymous usage data, add the following to a .drushrc.php file: $options[\'drush_usage_send\'] = TRUE;'));
  }
  else {
    return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.'));
  }
}

/**
 * Displays usage file.
 */
function drush_usage_show() {
  $file = _drush_usage_get_file(TRUE);
  if ($file) {
    $json = '[' . file_get_contents($file) . ']';
    $usage_data = json_decode($json);
    foreach ($usage_data as $item) {
      $cmd = $item->cmd;
      $options = (array) $item->opt;
      array_unshift($options, '');
      drush_print($cmd . implode(' --', $options));
    }
  }
  else {
    return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.'));
  }
}

/**
 * Returns path to usage file.
 */
function _drush_usage_get_file($required = FALSE) {
  $file = drush_directory_cache('usage') . '/usage.txt';
  if (!file_exists($file) && $required) {
    return FALSE;

  }
  return $file;
}

function _drush_usage_log($command, $file) {
  $options = drush_get_command_options_extended($command);

  $used = drush_get_merged_options();
  $command_specific = array_intersect(array_keys($used), array_keys($options));
  $record = array(
    'date' => $_SERVER['REQUEST_TIME'],
    'cmd' => $command['command'],
    'opt' => $command_specific,
    'major' => DRUSH_MAJOR_VERSION,
    'minor' => DRUSH_MINOR_VERSION,
    'os' => php_uname('s'),
    'host' => md5(php_uname('n') . get_current_user()),
  );
  $prequel = (file_exists($file)) ? ",\n" : "";
  if (file_put_contents($file, $prequel . json_encode($record), FILE_APPEND)) {
    drush_log(dt('Logged command and option names to local cache.'), LogLevel::DEBUG);
  }
  else {
    drush_log(dt('Failed to log command and option names to local cache.'), LogLevel::DEBUG);
  }
}

// We only send data periodically to save network traffic and delay. Files
// are sent once they grow over 50KB (configurable).
function _drush_usage_mongolab($file, $min_size_to_send) {
  $json = '[' . file_get_contents($file) . ']';
  if (filesize($file) > $min_size_to_send) {
    $base = 'https://api.mongolab.com/api/1';
    $apikey = '4eb95456e4b0bcd285d8135d'; // submitter account.
    $database = 'usage';
    $collection = 'usage';
    $action = "/databases/$database/collections/$collection";
    $url = $base . $action . "?apiKey=$apikey";
    $header = 'Content-Type: application/json';
    if (!drush_shell_exec("wget -q -O - --no-check-certificate --timeout=20 --header=\"$header\" --post-data %s %s", $json, $url)) {
      if (!drush_shell_exec("curl -s --connect-timeout 20 --header \"$header\" --data %s %s", $json, $url)) {
        drush_log(dt('Drush usage statistics failed to post.'), LogLevel::DEBUG);
        return FALSE;
      }
    }
    drush_log(dt('Drush usage statistics successfully posted.'), LogLevel::DEBUG);
    // Empty the usage.txt file.
    unlink($file);
    return TRUE;
  }
}
<?php

use Drush\Log\LogLevel;

/**
 * Implementation of hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * @return
 *   An associative array describing your command(s).
 */
function variable_drush_command() {
  $items['variable-get'] = array(
    'description' => 'Get a list of some or all site variables and values.',
    'core' => array(6,7),
    'arguments' => array(
      'name' => 'A string to filter the variables by. Variables whose name contains the string will be listed.',
    ),
    'examples' => array(
      'drush vget' => 'List all variables and values.',
      'drush vget user' => 'List all variables containing the string "user".',
      'drush vget site_mail --exact' => 'Show only the value of the variable with the exact key "site_mail".',
      'drush vget site_mail --exact --pipe' => 'Show only the variable with the exact key "site_mail" without changing the structure of the output.',
    ),
    'options' => array(
      'exact' => "Only get the one variable that exactly matches the specified name.  Output will contain only the variable's value.",
    ),
    'outputformat' => array(
      'default' => 'yaml',
      'pipe-format' => 'config',
      'variable-name' => 'variables',
      'table-metadata' => array(
        'format' => 'var_export',
      ),
    ),
    'aliases' => array('vget'),
  );
  $items['variable-set'] = array(
    'description' => "Set a variable.",
    'core' => array(6,7),
    'arguments' => array(
      'name' => 'The name of a variable or the first few letters of its name.',
      'value' => 'The value to assign to the variable. Use \'-\' to read the object from STDIN.',
    ),
    'required-arguments' => TRUE,
    'options' => array(
      'yes' => 'Skip confirmation if only one variable name matches.',
      'always-set' => array('description' => 'Older synonym for --exact; deprecated.', 'hidden' => TRUE),
      'exact' => 'The exact name of the variable to set has been provided; do not prompt for similarly-named variables.',
      'format' => array(
        'description' => 'Type for  the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.',
        'example-value' => 'boolean',
      ),
    ),
    'examples' => array(
      'drush vset --yes preprocess_css TRUE' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.',
      'drush vset --exact maintenance_mode 1' => 'Take the site offline; skips confirmation even if maintenance_mode variable does not exist. Variable is rewritten to site_offline for Drupal 6.',
      'drush vset pr TRUE' => 'Choose from a list of variables beginning with "pr" to set to (bool)true.',
      'php -r "print json_encode(array(\'drupal\', \'simpletest\'));"  | drush vset --format=json project_dependency_excluded_dependencies -'=> 'Set a variable to a complex value (e.g. array)',
    ),
    'aliases' => array('vset'),
  );
  $items['variable-delete'] = array(
    'core' => array(6,7),
    'description' => "Delete a variable.",
    'arguments' => array(
      'name' => 'The name of a variable or the first few letters of its name.',
    ),
    'required-arguments' => TRUE,
    'options' => array(
      'yes' => 'Skip confirmation if only one variable name matches.',
      'exact' => 'Only delete the one variable that exactly matches the specified name.',
    ),
    'examples' => array(
      'drush vdel user_pictures' => 'Delete the user_pictures variable.',
      'drush vdel u' => 'Choose from a list of variables beginning with "u" to delete.',
      'drush vdel -y --exact maintenance_mode' => 'Bring the site back online, skipping confirmation. Variable is rewritten to site_offline for Drupal 6.',
    ),
    'aliases' => array('vdel'),
  );

  return $items;
}

/**
 * Command argument complete callback.
 */
function variable_variable_get_complete() {
  return variable_complete_variables();
}

/**
 * Command argument complete callback.
 */
function variable_variable_set_complete() {
  return variable_complete_variables();
}

/**
 * Command argument complete callback.
 */
function variable_variable_delete_complete() {
  return variable_complete_variables();
}

/**
 * List variables for completion.
 *
 * @return
 *  Array of available variables.
 */
function variable_complete_variables() {
  if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    global $conf;
    return array('values' => array_keys($conf));
  }
}

/**
 * Command callback.
 * List your site's variables.
 */
function drush_variable_get() {
  global $conf;
  $exact = drush_get_option('exact', FALSE);

  $keys = array_keys($conf);
  if ($args = func_get_args()) {
    $args[0] = drush_variable_name_adjust($args[0]);
    if ($exact) {
      $keys = in_array($args[0], $keys) ? array($args[0]) : array();
    }
    $keys = preg_grep("/{$args[0]}/", $keys);
  }

  // In --exact mode, if --pipe is not set, then simplify the return type.
  if ($exact && !drush_get_context('DRUSH_PIPE')) {
    $key = reset($keys);
    $returns = isset($conf[$key]) ? $conf[$key] : FALSE;
  }
  else {
    foreach ($keys as $name) {
      $value = $conf[$name];
      $returns[$name] = $value;
    }
  }
  if (empty($keys)) {
    return drush_set_error('No matching variable found.');
  }
  else {
    return $returns;
  }
}

/**
 * Command callback.
 * Set a variable.
 */
function drush_variable_set() {
  $args = func_get_args();
  $value = $args[1];
  if (!isset($value)) {
    return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.'));
  }

  $args[0] = drush_variable_name_adjust($args[0]);
  $result = drush_variable_like($args[0]);

  $options[] = "$args[0] ". dt('(new variable)');
  $match = FALSE;
  while (!$match && $name = drush_db_result($result)) {
    if ($name == $args[0]) {
      $options[0] = $name;
      $match = TRUE;
    }
    else {
      $options[] = $name;
    }
  }

  if ($value == '-') {
    $value = stream_get_contents(STDIN);
  }

  // If the value is a string (usual case, unless we are called from code),
  // then format the input
  if (is_string($value)) {
    $value = drush_value_format($value, drush_get_option('format', 'auto'));
  }

  // Format the output for display
  if (is_array($value)) {
    $display = "\n" . var_export($value, TRUE);
  }
  elseif (is_integer($value)) {
    $display = $value;
  }
  elseif (is_bool($value)) {
    $display = $value ? "TRUE" : "FALSE";
  }
  else {
    $display = '"' . $value . '"';
  }

  // Check 'always-set' for compatibility with older scripts; --exact is preferred.
  $always_set = drush_get_option('always-set', FALSE) || drush_get_option('exact', FALSE);

  if ($always_set || count($options) == 1 || $match) {
    variable_set($args[0], $value);
    drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $display)), LogLevel::SUCCESS);
    return '';
  }
  else {
    $choice = drush_choice($options, 'Enter a number to choose which variable to set.');
    if ($choice === FALSE) {
      return drush_user_abort();
    }
    $choice = $options[$choice];
    $choice = str_replace(' ' . dt('(new variable)'), '', $choice);
    drush_op('variable_set', $choice, $value);
    drush_log(dt('!name was set to !value', array('!name' => $choice, '!value' => $display)), LogLevel::SUCCESS);
  }
}

/**
 * Command callback.
 * Delete a variable.
 */
function drush_variable_delete() {
  $args = func_get_args();
  $args[0] = drush_variable_name_adjust($args[0]);
  // Look for similar variable names.
  $result = drush_variable_like($args[0]);

  $options = array();
  while ($name = drush_db_result($result)) {
    $options[] = $name;
  }
  if (drush_get_option('exact', FALSE)) {
    $options = in_array($args[0], $options) ? array($args[0]) : array();
  }

  if (count($options) == 0) {
    drush_print(dt('!name not found.', array('!name' => $args[0])));
    return '';
  }

  if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) {
    drush_op('variable_del', $args[0]);
    drush_log(dt('!name was deleted.', array('!name' => $args[0])), LogLevel::SUCCESS);
    return '';
  }
  else {
    $choice = drush_choice($options, 'Enter a number to choose which variable to delete.');
    if ($choice !== FALSE) {
      $choice = $options[$choice];
      drush_op('variable_del', $choice);
      drush_log(dt('!choice was deleted.', array('!choice' => $choice)), LogLevel::SUCCESS);
    }
  }
}

// Query for similar variable names.
function drush_variable_like($arg) {
  return drush_db_select('variable', 'name', 'name LIKE :keyword', array(':keyword' => $arg . '%'), NULL, NULL, 'name');
}

// Unify similar variable names across different versions of Drupal
function drush_variable_name_adjust($arg) {
  if (($arg == 'maintenance_mode') && (drush_drupal_major_version() < 7)) {
    $arg = 'site_offline';
  }
  if (($arg == 'site_offline') && (drush_drupal_major_version() >= 7)) {
    $arg = 'maintenance_mode';
  }
  return $arg;
}
<?php

/**
 * @file
 * Drush integration for views.
 */

use Drush\Log\LogLevel;
use Drupal\views\Analyzer;
use Drupal\views\Entity\View;
use Drupal\views\Views;
use Drupal\Component\Utility\MapArray;

/**
 * Implements hook_drush_help().
 */
function views_drush_help($section) {
  switch ($section) {
    case 'meta:views:title':
      return dt('Views commands');
    case 'meta:views:summary':
      return dt('Views drush commands.');
  }
}

/**
 * Implements hook_drush_command().
 */
function views_drush_command() {
  $items = array();

  $base = array(
    'core' => array('8+'),
    'drupal dependencies' => array('views'),
  );

  $items['views-dev'] = array(
    'description' => 'Set the Views settings to more developer-oriented values.',
    'aliases' => array('vd'),
  ) + $base;

  $items['views-list'] = array(
    'description' => 'Get a list of all views in the system.',
    'aliases' => array('vl'),
    'options' => array(
      'name' => array(
        'description' => 'A string contained in the view\'s name to filter the results with.',
        'example-value' => 'node',
        'value' => 'required',
      ),
      'tags' => array(
        'description' => 'A comma-separated list of views tags by which to filter the results.',
        'example-value' => 'default',
        'value' => 'required',
      ),
      'status' => array(
        'description' => 'Status of the views by which to filter the results. Choices: enabled, disabled.',
        'example-value' => 'enabled',
        'value' => 'required',
      ),
    ),
    'examples' => array(
      'drush vl' => 'Show a list of all available views.',
      'drush vl --name=blog' => 'Show a list of views which names contain "blog".',
      'drush vl --tags=tag1,tag2' => 'Show a list of views tagged with "tag1" or "tag2".',
      'drush vl --status=enabled' => 'Show a list of enabled views.',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'list',
      'field-default' => array('name', 'label', 'description', 'status', 'tag'),
      'field-labels' => array('name' => 'Machine Name', 'label' => 'Name', 'description' => 'Description', 'status' => 'Status', 'tag' => 'Tag'),
      'output-data-type' => 'format-table',
    ),
  ) + $base;

  $items['views-execute'] = array(
    'description' => 'Execute a view and get the results.',
    'aliases' => array('vex'),
    'arguments' => array(
      'view' => 'The name of the view to execute.',
      'display' => 'The display ID to execute. If none specified, the default display will be used.',
    ),
    'required-arguments' => 1,
    'options' => array(
      'count' => array(
        'description' => 'Display a count of the results instead of each row.',
      ),
      'rendered' => array(
        'description' => 'Return the results as rendered HTML output for the display.',
      ),
      'show-admin-links' => array(
        'description' => 'Show contextual admin links in the rendered markup.',
      ),
    ),
    'outputformat' => array(
      'default' => 'print-r',
      'pipe-format' => 'var_export',
    ),
    'examples' => array(
      'drush views-execute my_view' => 'Show the result set of the default display for the my_view view.',
      'drush views-execute my_view page_1 --rendered' => 'Show the rendered output of the my_view:page_1 view.',
      'drush views-execute my_view page_1 3 --count' => 'Show a count of my_view:page_1 with an agument of 3 being passed.',
    ),
  ) + $base;

  $items['views-analyze'] = array(
    'drupal dependencies' => array('views', 'views_ui'),
    'description' => 'Get a list of all Views analyze warnings',
    'aliases' => array('va'),
    'options' => array(
      'format' => array(
        'description' => 'Define the output format. Known formats are: json, print_r, and export.',
      ),
    ),
  ) + $base;

  $items['views-enable'] = array(
    'description' => 'Enable the specified views.',
    'arguments' => array(
      'views' => 'A space delimited list of view names.',
    ),
    'required-arguments' => 1,
    'aliases' => array('ven'),
    'examples' => array(
      'drush ven frontpage taxonomy_term' => 'Enable the frontpage and taxonomy_term views.',
    ),
  ) + $base;

  $items['views-disable'] = array(
    'description' => 'Disable the specified views.',
    'arguments' => array(
      'views' => 'A space delimited list of view names.',
    ),
    'required-arguments' => 1,
    'aliases' => array('vdis'),
    'examples' => array(
      'drush vdis frontpage taxonomy_term' => 'Disable the frontpage and taxonomy_term views.',
    ),
  ) + $base;

  return $items;
}

/**
 * Command callback function for views-dev command.
 *
 * Changes the settings to more developer oriented values.
 */
function drush_views_dev() {
  $settings = array(
    'ui.show.listing_filters' => TRUE,
    'ui.show.master_display' => TRUE,
    'ui.show.advanced_column' => TRUE,
    'ui.always_live_preview' => FALSE,
    'ui.always_live_preview_button' => TRUE,
    'ui.show.preview_information' => TRUE,
    'ui.show.sql_query.enabled' => TRUE,
    'ui.show.sql_query.where' => 'above',
    'ui.show.performance_statistics' => TRUE,
    'ui.show.additional_queries' => TRUE,
    'debug.output' => TRUE,
    'debug.region' => 'message',
    'ui.show.display_embed' => TRUE,
  );

  $config = \Drupal::configFactory()->getEditable('views.settings');

  foreach ($settings as $setting => $value) {
    $config->set($setting, $value);
    // Convert boolean values into a string to print.
    if (is_bool($value)) {
      $value = $value ? 'TRUE' : 'FALSE';
    }
    // Wrap string values in quotes.
    elseif (is_string($value)) {
      $value = "\"$value\"";
    }
    drush_log(dt('!setting set to !value', array('!setting' => $setting, '!value' => $value)));
  }

  // Save the new config.
  $config->save();

  drush_log(dt('New views configuration saved.'), LogLevel::SUCCESS);
}

/**
 * Callback function for views-list command.
 */
function drush_views_list() {
  $disabled_views = array();
  $enabled_views = array();

  $format = drush_get_option('format', FALSE);

  $views = \Drupal::entityManager()->getStorage('view')->loadMultiple();

  // Get the --name option.
  $name = array_filter(drush_get_option_list('name'));
  $with_name = !empty($name) ? TRUE : FALSE;

  // Get the --tags option.
  $tags = array_filter(drush_get_option_list('tags'));
  $with_tags = !empty($tags) ? TRUE : FALSE;

  // Get the --status option. Store user input appart to reuse it after.
  $status = drush_get_option('status', FALSE);

  // Throw an error if it's an invalid status.
  if ($status && !in_array($status, array('enabled', 'disabled'))) {
    return drush_set_error(dt('Invalid status: @status. Available options are "enabled" or "disabled"', array('@status' => $status)));
  }

  // Setup a row for each view.
  foreach ($views as $view) {
    // If options were specified, check that first mismatch push the loop to the
    // next view.
    if ($with_name && !stristr($view->id(), $name[0])) {
      continue;
    }
    if ($with_tags && !in_array($view->get('tag'), $tags)) {
      continue;
    }

    $status_bool = $status == 'enabled';
    if ($status && ($view->status() !== $status_bool)) {
      continue;
    }

    $row = array(
      'name' => $view->id(),
      'label' => $view->label(),
      'description' =>  $view->get('description'),
      'status' =>  $view->status() ? dt('Enabled') : dt('Disabled'),
      'tag' =>  $view->get('tag'),
    );

    // Place the row in the appropiate array, so we can have disabled views at
    // the bottom.
    if ($view->status()) {
      $enabled_views[] = $row;
      }
    else{
      $disabled_views[] = $row;
    }
  }

  // Sort alphabeticaly.
  asort($disabled_views);
  asort($enabled_views);

  if (count($enabled_views) || count($disabled_views)) {
    $rows = array_merge($enabled_views, $disabled_views);
    return $rows;
  }
  else {
    drush_log(dt('No views found.'));
  }
}

/**
 * Drush views execute command.
 */
function drush_views_execute($view_name, $display_id = NULL) {
  $args = func_get_args();
  $view_args = array();

  // If it's more than 2, we have arguments. A display has to be specified in
  // that case.
  if (count($args) > 2) {
    $view_args = array_slice($args, 2);
  }

  if (!$view = Views::getView($view_name)) {
    return drush_set_error(dt('View: "@view" not found.', array('@view' => $view_name)));
  }

  // Set the display and execute the view.
  $view->setDisplay($display_id);
  $view->preExecute($view_args);
  $view->execute();

  if (drush_get_option('count', FALSE)) {
    drush_set_default_outputformat('string');
    return count($view->result);
  }
  elseif (!empty($view->result)) {
    if (drush_get_option('rendered', FALSE)) {
      drush_set_default_outputformat('string');
      // Don't show admin links in markup by default.
      $view->hide_admin_links = !drush_get_option('show-admin-links', FALSE);
      $output = $view->preview();
      return drupal_render($output);

    }
    else {
      return $view->result;
    }
  }
  else {
    drush_log(dt('No results returned for this view.') ,LogLevel::WARNING);
    return NULL;
  }
}

/**
 * Drush views analyze command.
 */
function drush_views_analyze() {
  $messages = NULL;
  $messages_count = 0;

  $format = drush_get_option('format', FALSE);

  $views = \Drupal::entityManager()->getStorage('view')->loadMultiple();

  if (!empty($views)) {
    $analyzer = \Drupal::service('views.analyzer');
    foreach ($views as $view_name => $view) {
      $view = $view->getExecutable();

      if ($messages = $analyzer->getMessages($view)) {
        if ($format) {
          $output = drush_format($messages, $format);
          drush_print($output);
          return $output;
        }
        else {
          drush_print($view_name);
          foreach ($messages as $message) {
            $messages_count++;
            drush_print($message['type'] .': '. $message['message'], 2);
          }
        }
      }
    }

    drush_log(dt('A total of @total views were analyzed and @messages problems were found.', array('@total' => count($views), '@messages' => $messages_count)), LogLevel::OK);
    return $messages;
  }
  else {
    return drush_set_error(dt('There are no views to analyze'));
  }
}

/**
 * Drush views enable command.
 */
function drush_views_enable() {
  $view_names = func_get_args();
  _views_drush_op('enable', $view_names);
}

/**
 * Drush views disable command.
 */
function drush_views_disable() {
  $view_names = func_get_args();
  _views_drush_op('disable', $view_names);
}

/**
 * Perform operations on view objects.
 *
 * @param string $op
 *   The operation to perform.
 * @param array $view_names
 *   An array of view names to load and perform this operation on.
 */
function _views_drush_op($op = '', array $view_names = array()) {
  $op_types = _views_drush_op_types();
  if (!in_array($op, array_keys($op_types))) {
    return drush_set_error(dt('Invalid op type'));
  }

  $view_names = array_combine($view_names, $view_names);

  if ($views = \Drupal::entityManager()->getStorage('view')->loadMultiple($view_names)) {
    foreach ($views as $view) {
      $tokens = array('@view' => $view->id(), '@action' => $op_types[$op]['action']);

      if ($op_types[$op]['validate']($view)) {
        $function = 'views_' . $op . '_view';
        drush_op($function, $view);
        drush_log(dt('View: @view has been @action', $tokens), LogLevel::SUCCESS);
      }
      else {
        drush_log(dt('View: @view is already @action', $tokens), LogLevel::NOTICE);
      }
      // Remove this view from the viewnames input list.
      unset($view_names[$view->id()]);
    }

    return $views;
  }
  else {
    drush_set_error(dt('No views have been loaded'));
  }

  // If we have some unmatched/leftover view names that weren't loaded.
  if (!empty($view_names)) {
    foreach ($view_names as $viewname) {
      drush_log(dt('View: @view could not be found.', array('@view' => $viewname)), LogLevel::ERROR);
    }
  }

}

/**
 * Returns an array of op types that can be performed on views.
 *
 * @return array
 *   An associative array keyed by op type => action name.
 */
function _views_drush_op_types() {
  return array(
    'enable' => array(
      'action' => dt('enabled'),
      'validate' => '_views_drush_view_is_disabled',
    ),
    'disable' => array(
      'action' => dt('disabled'),
      'validate' => '_views_drush_view_is_enabled',
    ),
  );
}

/**
 * Returns whether a view is enabled.
 *
 * @param Drupal\views\Entity\ViewDrupal\views\ $view
 *   The view object to check.
 *
 * @return bool
 *   TRUE if the View is enabled, FALSE otherwise.
 */
function _views_drush_view_is_enabled(View $view) {
  return $view->status();
}

/**
 * Returns whether a view is disabled.
 *
 * @param Drupal\views\Entity\View $view
 *   The view object to check.
 *
 * @return bool
 *   TRUE if the View is disabled, FALSE otherwise.
 */
function _views_drush_view_is_disabled(View $view) {
  return !$view->status();
}

/**
 * Implements hook_cache_clear. Adds a cache clear option for views.
 */
function views_drush_cache_clear(&$types, $include_bootstrapped_types) {
  if ($include_bootstrapped_types && \Drupal::moduleHandler()->moduleExists('views')) {
    $types['views'] = 'views_invalidate_cache';
  }
}

/**
 * Command argument complete callback.
 */
function views_views_enable_complete() {
  return _drush_views_complete();
}

/**
 * Command argument complete callback.
 */
function views_views_disable_complete() {
  return _drush_views_complete();
}

/**
 * Helper function to return a list of view names for complete callbacks.
 *
 * @return array
 *   An array of available view names.
 */
function _drush_views_complete() {
  drush_bootstrap_max();
  return array('values' => array_keys(\Drupal::entityManager()->getStorage('view')->loadMultiple()));
}
<?php

use Drush\Log\LogLevel;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Html;

/**
 * Implementation of hook_drush_help().
 */
function watchdog_drush_help($section) {
  switch ($section)  {
    case 'meta:watchdog:title':
      return dt('Watchdog commands');
    case 'meta:watchdog:summary':
      return dt('Interact with Drupal\'s db logging system.');
    case 'drush:watchdog-list':
      return dt('Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.');
    case 'drush:watchdog-show':
      return dt('Show watchdog messages. Arguments and options can be combined to configure which messages to show.');
    case 'drush:watchdog-delete':
      return dt('Delete watchdog messages. Arguments or options must be provided to specify which messages to delete.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function watchdog_drush_command() {
  $items['watchdog-list'] = array(
    'description' => 'Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.',
    'drupal dependencies' => array('dblog'),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'var_export',
      'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'),
      'fields-default' => array('wid', 'date', 'type', 'severity', 'message'),
      'column-widths' => array('type' => 8, 'severity' => 8),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('wd-list'),
  );
  $items['watchdog-show'] = array(
    'description' => 'Show watchdog messages.',
    'drupal dependencies' => array('dblog'),
    'arguments' => array(
      'wid' => 'Optional id of a watchdog message to show in detail. If not provided, a listing of most recent 10 messages will be displayed. Alternatively if a string is provided, watchdog messages will be filtered by it.',
    ),
    'options' => array(
      'count' => 'The number of messages to show. Defaults to 10.',
      'severity' => 'Restrict to messages of a given severity level.',
      'type' => 'Restrict to messages of a given type.',
      'tail' => 'Continuously show new watchdog messages until interrupted.',
      'sleep-delay' => 'To be used in conjunction with --tail. This is the number of seconds to wait between each poll to the database. Delay is 1 second by default.',
      'extended' => 'Return extended information about each message.',
    ),
    'examples' => array(
      'drush watchdog-show' => 'Show a listing of most recent 10 messages.',
      'drush watchdog-show 64' => 'Show in detail message with id 64.',
      'drush watchdog-show "cron run succesful"' => 'Show a listing of most recent 10 messages containing the string "cron run succesful".',
      'drush watchdog-show --count=46' => 'Show a listing of most recent 46 messages.',
      'drush watchdog-show --severity=notice' => 'Show a listing of most recent 10 messages with a severity of notice.',
      'drush watchdog-show --type=php' => 'Show a listing of most recent 10 messages of type php.',
      'drush watchdog-show --tail --extended' => 'Show a listing of most recent 10 messages with extended information about each one and continue showing messages as they are registered in the watchdog.',
      'drush watchdog-show --tail --sleep-delay=2' => 'Do a tail of the watchdog with a delay of two seconds between each poll to the database.',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'var_export',
      'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'),
      'fields-default' => array('wid', 'date', 'type', 'severity', 'message'),
      'column-widths' => array('type' => 8, 'severity' => 8),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('wd-show', 'ws'),
  );
  $items['watchdog-delete'] = array(
    'description' => 'Delete watchdog messages.',
    'drupal dependencies' => array('dblog'),
    'options' => array(
      'severity' => 'Delete messages of a given severity level.',
      'type' => 'Delete messages of a given type.',
    ),
    'examples' => array(
      'drush watchdog-delete all' => 'Delete all messages.',
      'drush watchdog-delete 64' => 'Delete messages with id 64.',
      'drush watchdog-delete "cron run succesful"' => 'Delete messages containing the string "cron run succesful".',
      'drush watchdog-delete --severity=notice' => 'Delete all messages with a severity of notice.',
      'drush watchdog-delete --type=cron' => 'Delete all messages of type cron.',
    ),
    'aliases' => array('wd-del', 'wd-delete'),
  );
  return $items;
}

/**
 * Command callback.
 */
function drush_core_watchdog_list() {
  drush_include_engine('drupal', 'environment');

  $options['-- types --'] = dt('== message types ==');
  $types = drush_watchdog_message_types();
  foreach ($types as $key => $type) {
    $options[$key] = $type;
  }
  $options['-- levels --'] = dt('== severity levels ==');
  $severities = drush_watchdog_severity_levels();
  foreach ($severities as $key => $value) {
    $options[$key] = "$value($key)";
  }
  $option = drush_choice($options, dt('Select a message type or severity level.'));
  if ($option === FALSE) {
    return drush_user_abort();
  }
  if (isset($types[$option])) {
    drush_set_option('type', $types[$option]);
  }
  else {
    drush_set_option('severity', $option - $ntypes);
  }
  return drush_core_watchdog_show_many();
}

/**
 * Command callback.
 */
function drush_core_watchdog_show($arg = NULL) {
  drush_include_engine('drupal', 'environment');

  if (is_numeric($arg)) {
    return drush_core_watchdog_show_one($arg);
  }
  else {
    return drush_core_watchdog_show_many($arg);
  }
}

/**
 * Print a watchdog message.
 *
 * @param $wid
 *    The id of the message to show.
 */
function drush_core_watchdog_show_one($wid) {
  drush_set_default_outputformat('key-value-list', array('fields-default' => array('wid', 'type', 'message', 'severity', 'date'),));
  $rsc = drush_db_select('watchdog', '*', 'wid = :wid', array(':wid' => $wid), 0, 1);
  $result = drush_db_fetch_object($rsc);
  if (!$result) {
    return drush_set_error(dt('Watchdog message #!wid not found.', array('!wid' => $wid)));
  }
  $result = core_watchdog_format_result($result, TRUE);
  return array($result->wid => (array)$result);
}

/**
 * Print a table of watchdog messages.
 *
 * @param $filter
 *   String to filter the message's text by.
 */
function drush_core_watchdog_show_many($filter = NULL) {
  $count = drush_get_option('count', 10);
  $type = drush_get_option('type');
  $severity = drush_get_option('severity');
  $tail = drush_get_option('tail', FALSE);
  $extended = drush_get_option('extended', FALSE);

  $where = core_watchdog_query($type, $severity, $filter);
  if ($where === FALSE) {
    return drush_log(dt('Aborting.'));
  }
  $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], 0, $count, 'wid', 'DESC');
  if ($rsc === FALSE) {
    return drush_log(dt('Aborting.'));
  }
  $table = array();
  while ($result = drush_db_fetch_object($rsc)) {
    $row = core_watchdog_format_result($result, $extended);
    $table[$row->wid] = (array)$row;
  }
  if (empty($table) && !$tail) {
    drush_log(dt('No log messages available.'), LogLevel::OK);
    return array();
  }
  else {
    drush_log(dt('Most recent !count watchdog log messages:', array('!count' => $count)));
  }
  if ($tail) {
    $field_list = array('wid' => 'ID', 'date' => 'Date', 'severity' => 'Severity', 'type' => 'Type', 'message' => 'Message');
    $table = array_reverse($table);
    $table_rows = drush_rows_of_key_value_to_array_table($table, $field_list, array());
    $tbl = drush_print_table($table_rows, TRUE);
    // Reuse the table object to display each line generated while in tail mode.
    // To make it possible some hacking is done on the object:
    // remove the header and reset the rows on each iteration.
    $tbl->_headers = NULL;
    // Obtain the last wid. If the table has no rows, start at 0.
    if (count($table_rows) > 1) {
      $last = array_pop($table_rows);
      $last_wid = $last[0];
    }
    else {
      $last_wid = 0;
    }
    // Adapt the where snippet.
    if ($where['where'] != '') {
      $where['where'] .= ' AND ';
    }
    $where['where'] .= 'wid > :wid';
    // sleep-delay
    $sleep_delay = drush_get_option('sleep-delay', 1);
    while (TRUE) {
      $where['args'][':wid'] = $last_wid;
      $table = array();
      // Reset table rows.
      $tbl->_data = array();
      $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], NULL, NULL, 'wid', 'ASC');
      while ($result = drush_db_fetch_object($rsc)) {
        $row = core_watchdog_format_result($result, $extended);
        $table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message);
        #$tbl->addRow(array($row->wid, $row->date, $row->severity, $row->type, $row->message));
        $last_wid = $row->wid;
      }
      $tbl->addData($table);
      print $tbl->_buildTable();
      sleep($sleep_delay);
    }
  }
  return $table;
}

/**
 * Format a watchdog database row.
 *
 * @param $result
 *   Array. A database result object.
 * @param $extended
 *   Boolean. Return extended message details.
 * @return
 *   Array. The result object with some attributes themed.
 */
function core_watchdog_format_result($result, $extended = FALSE) {
  // Severity.
  $severities = drush_watchdog_severity_levels();
  $result->severity = $severities[$result->severity];

  // Date.
  $result->date = format_date($result->timestamp, 'custom', 'd/M H:i');
  unset($result->timestamp);

  // Message.
  $variables = $result->variables;
  if (is_string($variables)) {
    $variables = unserialize($variables);
  }
  if (is_array($variables)) {
    $result->message = strtr($result->message, $variables);
  }
  unset($result->variables);
  $message_length = 188;

  // Print all the data available
  if ($extended) {
    // Possible empty values.
    if (empty($result->link)) {
      unset($result->link);
    }
    if (empty($result->referer)) {
      unset($result->referer);
    }
    // Username.
    if ($account = user_load($result->uid)) {
      $result->username = $account->name;
    }
    else {
      $result->username = dt('Anonymous');
    }
    unset($result->uid);
    $message_length = PHP_INT_MAX;
  }

  if (drush_drupal_major_version() >= 8) {
    $result->message = Unicode::truncate(strip_tags(Html::decodeEntities($result->message)), $message_length, FALSE, FALSE);
  }
  else {
    $result->message = truncate_utf8(strip_tags(decode_entities($result->message)), $message_length, FALSE, FALSE);
  }

  return $result;
}

/**
 * Command callback.
 *
 * @param $arg
 *   The id of the message to delete or 'all'.
 */
function drush_core_watchdog_delete($arg = NULL) {
  drush_include_engine('drupal', 'environment');

  if ($arg == 'all') {
    drush_print(dt('All watchdog messages will be deleted.'));
    if (!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
    drush_db_delete('watchdog');
    drush_log(dt('All watchdog messages have been deleted.'), LogLevel::OK);
  }
  else if (is_numeric($arg)) {
    drush_print(dt('Watchdog message #!wid will be deleted.', array('!wid' => $arg)));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
    $affected_rows = drush_db_delete('watchdog', 'wid=:wid', array(':wid' => $arg));
    if ($affected_rows == 1) {
      drush_log(dt('Watchdog message #!wid has been deleted.', array('!wid' => $arg)), LogLevel::OK);
    }
    else {
      return drush_set_error(dt('Watchdog message #!wid does not exist.', array('!wid' => $arg)));
    }
  }
  else {
    $type = drush_get_option('type');
    $severity = drush_get_option('severity');
    if ((!isset($arg))&&(!isset($type))&&(!isset($severity))) {
      return drush_set_error(dt('No options provided.'));
    }
    $where = core_watchdog_query($type, $severity, $arg, 'OR');
    if ($where === FALSE) {
      // Drush set error was already called by core_watchdog_query
      return FALSE;
    }
    drush_print(dt('All messages with !where will be deleted.', array('!where' => preg_replace("/message LIKE %$arg%/", "message body containing '$arg'" , strtr($where['where'], $where['args'])))));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
    $affected_rows = drush_db_delete('watchdog', $where['where'], $where['args']);
    drush_log(dt('!affected_rows watchdog messages have been deleted.', array('!affected_rows' => $affected_rows)), LogLevel::OK);
  }
}

/**
 * Build a WHERE snippet based on given parameters.
 *
 * @param $type
 *   String. Valid watchdog type.
 * @param $severity
 *   Int or String for a valid watchdog severity message.
 * @param $filter
 *   String. Value to filter watchdog messages by.
 * @param $criteria
 *   ('AND', 'OR'). Criteria for the WHERE snippet.
 * @return
 *   False or array with structure ('where' => string, 'args' => array())
 */
function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $criteria = 'AND') {
  $args = array();
  $conditions = array();
  if ($type) {
    $types = drush_watchdog_message_types();
    if (array_search($type, $types) === FALSE) {
      $msg = "Unrecognized message type: !type.\nRecognized types are: !types.";
      return drush_set_error('WATCHDOG_UNRECOGNIZED_TYPE', dt($msg, array('!type' => $type, '!types' => implode(', ', $types))));
    }
    $conditions[] = "type = :type";
    $args[':type'] = $type;
  }
  if (isset($severity)) {
    $severities = drush_watchdog_severity_levels();
    if (isset($severities[$severity])) {
      $level = $severity;
    }
    elseif (($key = array_search($severity, $severities)) !== FALSE) {
      $level = $key;
    }
    else {
      $level = FALSE;
    }
    if ($level === FALSE) {
      foreach ($severities as $key => $value) {
        $levels[] = "$value($key)";
      }
      $msg = "Unknown severity level: !severity.\nValid severity levels are: !levels.";
      return drush_set_error(dt($msg, array('!severity' => $severity, '!levels' => implode(', ', $levels))));
    }
    $conditions[] = 'severity = :severity';
    $args[':severity'] = $level;
  }
  if ($filter) {
    $conditions[] = "message LIKE :filter";
    $args[':filter'] = '%'.$filter.'%';
  }

  $where = implode(" $criteria ", $conditions);

  return array('where' => $where, 'args' => $args);
}
<?php
/**
 * @file
 * Functions for the generate makefile command.
 */

use Drush\Log\LogLevel;

/**
 * Generate the actual contents of the .make file.
 */
function _drush_make_generate_makefile_contents($projects, $libraries = array(), $core_version = NULL, $defaults = array()) {
  if (is_null($core_version)) {
    $core_version = drush_get_drupal_core_compatibility();
  }

  $header = array();
  $header[] = '; This file was auto-generated by drush make';
  $header['core'] = $core_version;
  $header['api'] = MAKE_API;
  $header[] = '';
  if (!empty($defaults)) {
    _drush_make_generate_defaults($defaults, $header);
    $header[] = '';
  }
  $header[] = '; Core';

  return _drush_make_generate_makefile_body($projects, $header) . _drush_make_generate_makefile_body($libraries);
}

function _drush_make_generate_makefile_body($projects, $output = array()) {
  $custom = FALSE;
  $previous_type = 'core';
  if (isset($projects)) {
    foreach ($projects as $name => $project) {
      $type = (isset($project['type']) && ($project['type'] == 'library')) ? 'libraries' : 'projects';
      if ($previous_type != $project['_type']) {
        $previous_type = $project['_type'];
        $output[] = '; ' . ucfirst($previous_type) . 's';
      }
      unset($project['_type']);
      if (!$project && is_string($name)) {
        $output[] = $type . '[] = "' . $name . '"';
        continue;
      }
      $base = $type . '[' . $name . ']';
      if (isset($project['custom_download'])) {
        $custom = TRUE;
        $output[] = '; Please fill the following out. Type may be one of get, git, bzr or svn,';
        $output[] = '; and url is the url of the download.';
        $output[$base . '[download][type]'] = '""';
        $output[$base . '[download][url]'] = '""';
        unset($project['custom_download']);
      }

      $output = array_merge($output, _drush_make_generate_lines($base, $project));
      $output[] = '';
    }
  }
  $string = '';
  foreach ($output as $k => $v) {
    if (!is_numeric($k)) {
      $string .= $k . ' = ' . $v;
    }
    else {
      $string .= $v;
    }
    $string .= "\n";
  }
  if ($custom) {
    drush_log(dt('Some of the properties in your makefile will have to be manually edited. Please do that now.'), LogLevel::WARNING);
  }
  return $string;
}

/**
 * Write a makefile based on data parsed from a previous makefile.
 *
 * @param $file
 *   The path to the file to write our generated makefile to, or TRUE to
 *   print to the terminal.
 * @param $makefile
 *   A makefile on which to base our generated one.
 */
function make_generate_from_makefile($file, $makefile) {
  if (!$info = make_parse_info_file($makefile)) {
    return drush_set_error('MAKE_GENERATE_FAILED_PARSE', dt('Failed to parse makefile :makefile.', array(':makefile' => $makefile)));
  }
  $projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE);
  if ($projects === FALSE) {
    $projects = make_prepare_projects(FALSE, $info);
    if (isset($projects['contrib'])) {
      $projects = array_merge($projects['core'], $projects['contrib']);
    }
  }

  $defaults = isset($info['defaults']) ? $info['defaults'] : array();
  $core = current($projects);
  $core = $core['core'];
  foreach ($projects as $name => $project) {
    // If a specific revision was requested, do not set the version.
    if (!isset($project['revision'])) {
      $projects[$name]['version'] = isset($project['download']['full_version']) ? $project['download']['full_version'] : '';
      if ($project['type'] != 'core' && strpos($projects[$name]['version'], $project['core']) === 0) {
        $projects[$name]['version'] = substr($projects[$name]['version'], strlen($project['core'] . '-'));
      }
    }
    else {
      unset($projects[$name]['version']);
    }
    $projects[$name]['_type'] = $project['type'];

    if ($project['download']['type'] == 'git') {
      drush_make_resolve_git_refs($projects[$name]);
    }

    // Don't clutter the makefile with defaults
    if (is_array($defaults)) {
      foreach ($defaults as $type => $defs) {
        if ($type == 'projects') {
          foreach ($defs as $key => $value) {
            if (isset($project[$key]) && $project[$key] == $value) {
              unset($projects[$name][$key]);
            }
          }
        }
      }
    }
    if ($project['name'] == $name) {
      unset($projects[$name]['name']);
    }
    if ($project['type'] == 'module' && !isset($info[$name]['type'])) {
      unset($projects[$name]['type']);  // Module is the default
    }
    if (!(isset($project['download']['type'])) || ($project['download']['type'] == 'pm')) {
      unset($projects[$name]['download']); // PM is the default
    }
    $ignore = array('build_path', 'contrib_destination', 'core', 'make_directory', 'l10n_url', 'download_type');
    foreach ($ignore as $key) {
      unset($projects[$name][$key]);
    }

    // Remove the location if it's the default.
    if ($projects[$name]['location'] == 'https://updates.drupal.org/release-history') {
      unset($projects[$name]['location']);
    }

    // Remove empty entries (e.g. 'directory_name')
    $projects[$name] = _make_generate_array_filter($projects[$name]);
  }

  $libraries = drush_get_option('DRUSH_MAKE_LIBRARIES', FALSE);
  if ($libraries === FALSE) {
    $libraries = isset($info['libraries']) ? $info['libraries'] : array();
  }
  if (is_array($libraries)) {
    foreach ($libraries as $name => $library) {
      $libraries[$name]['type'] = 'library';
      $libraries[$name]['_type'] = 'librarie';

      if ($library['download']['type'] == 'git') {
        drush_make_resolve_git_refs($libraries[$name]);
      }
    }
  }

  $contents = make_generate_makefile_contents($projects, $libraries, $core, $defaults);

  // Write or print our makefile.
  $file = $file !== TRUE ? $file : NULL;
  make_generate_print($contents, $file);
}

/**
 * Resolve branches and revisions for git-based projects.
 */
function drush_make_resolve_git_refs(&$project) {
  if (!isset($project['download']['branch'])) {
    $project['download']['branch'] = drush_make_resolve_git_branch($project);
  }
  if (!isset($project['download']['revision'])) {
    $project['download']['revision'] = drush_make_resolve_git_revision($project);
  }
}

/**
 * Resolve branch for a git-based project.
 */
function drush_make_resolve_git_branch($project) {
  drush_log(dt('Resolving default branch for repo at: :repo', array(':repo' => $project['download']['url'])));
  if (drush_shell_exec("git ls-remote %s HEAD", $project['download']['url'])) {
    $head_output = drush_shell_exec_output();
    list($head_commit) = explode("\t", $head_output[0]);

    drush_log(dt('Scanning branches in repo at: :repo', array(':repo' => $project['download']['url'])));
    drush_shell_exec("git ls-remote --heads %s", $project['download']['url']);
    $heads_output = drush_shell_exec_output();
    $branches = array();
    foreach ($heads_output as $key => $head) {
      list($commit, $ref) = explode("\t", $head);
      $branches[$commit] = explode("/", $ref)[2];
    }

    $branch = $branches[$head_commit];
    drush_log(dt('Resolved git branch to: :branch', array(':branch' => $branch)));
    return $branch;
  }
  else {
    drush_log(dt('Could not resolve branch for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning');
  }
}

/**
 * Resolve revision for a git-based project.
 */
function drush_make_resolve_git_revision($project) {
  drush_log(dt('Resolving head commit on `:branch` branch for repo at: :repo', array(':branch' => $project['download']['branch'], ':repo' => $project['download']['url'])));
  if (drush_shell_exec("git ls-remote %s %s", $project['download']['url'], $project['download']['branch'])) {
    $head_output = drush_shell_exec_output();
    list($revision) = explode("\t", $head_output[0]);
    drush_log(dt('Resolved git revision to: :revision', array(':revision' => $revision)));
    return $revision;
  }
  else {
    drush_log(dt('Could not resolve head commit for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning');
  }
}

/**
 * Generate makefile contents in the appropriate format.
 */
function make_generate_makefile_contents($projects,  $libraries = array(), $core = NULL, $defaults = array()) {
  $format = drush_get_option('format', 'yaml');
  $func = "make_generate_makefile_contents_$format";
  if (function_exists($func)) {
    $contents = call_user_func($func, $projects, $libraries, $core, $defaults);
  }
  else {
    return drush_set_error('MAKE_UNKNOWN_OUTPUT_FORMAT', dt('Generating makefiles in the :format output format is not yet supported. Implement :func() to add such support.', array(':format' => $format, ':func' => $func)));
  }
  return $contents;
}

/**
 * Generate makefile contents in (legacy) INI format.
 */
function make_generate_makefile_contents_ini($projects, $libraries, $core, $defaults) {
  return _drush_make_generate_makefile_contents($projects, $libraries, $core, $defaults);
}

/**
 * Generate makefile contents in YAML format.
 */
function make_generate_makefile_contents_yaml($projects, $libraries, $core, $defaults) {
  $info = array(
    'core' => $core,
    'api' => MAKE_API,
    'defaults' => $defaults,
    'projects' => $projects,
    'libraries' => $libraries,
  );

  $info = _make_generate_array_filter($info);
  $info = _make_generate_array_filter_key('_type', $info);
  $dumper = drush_load_engine('outputformat', 'yaml');
  $yaml = $dumper->format($info, array());

  return $yaml;
}

/**
 * Helper function to recursively remove empty values from an array (but not
 * '0'!).
 */
function _make_generate_array_filter($haystack) {
  foreach ($haystack as $key => $value) {
    if (is_array($value)) {
      $haystack[$key] = _make_generate_array_filter($haystack[$key]);
    }
    if (empty($value) && $value !== '0') {
      unset($haystack[$key]);
    }
  }
  return $haystack;
}

/**
 * Helper function to recursively remove elements matching a specific key from an array.
 */
function _make_generate_array_filter_key($needle, $haystack) {
  foreach ($haystack as $key => $value) {
    if ($key === $needle) {
      unset($haystack[$key]);
    }
    elseif (is_array($value)) {
      $haystack[$key] = _make_generate_array_filter_key($needle, $haystack[$key]);
    }
  }
  return $haystack;
}

/**
 * Print the generated makefile to the terminal, or write it to a file.
 *
 * @param $contents
 *   The formatted contents of a makefile.
 * @param $file
 *   (optional) The path to write the makefile.
 */
function make_generate_print($contents, $file = NULL) {
  if (!$file) {
    drush_print($contents);
  }
  elseif (file_put_contents($file, $contents)) {
    drush_log(dt("Wrote .make file @file", array('@file' => $file)), LogLevel::OK);
  }
  else {
    make_error('FILE_ERROR', dt("Unable to write .make file !file", array('!file' => $file)));
  }
}

/**
 * Utility function to generate the line or lines for a key/value pair in the
 * make file.
 *
 * @param $base
 *   The base for the configuration lines. Values will be appended to it as
 *   [$key] = $value, or if value is an array itself it will expand into as many
 *   lines as required.
 * @param $values
 *   May be a single value or an array.
 * @return
 *   An array of strings that represent lines for the make file.
 */
function _drush_make_generate_lines($base, $values) {
  $output = array();

  if (is_array($values)) {
    foreach ($values as $key => $value) {
      $newbase = $base . '[' . $key . ']';
      $output = array_merge($output, _drush_make_generate_lines($newbase, $value));
    }
  }
  else {
    $output[$base] = '"' . $values . '"';
  }

  return $output;
}

function _drush_make_generate_defaults($defaults, &$output = array()) {
  $output[] = '; Defaults';
  foreach ($defaults as $name => $project) {
    $type = 'defaults';
    if (!$project && is_string($name)) {
      $output[] = $type . '[] = "' . $name . '"';
      continue;
    }
    $base = $type . '[' . $name . ']';

    $output = array_merge($output, _drush_make_generate_lines($base, $project));
  }
}

<?php
/**
 * @file
 * Functions for the generate makefile command.
 */

include_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
include_once drupal_get_path('module', 'system') . '/system.install';
include_once 'generate.contents.make.inc';

/**
 * Drush callback; generate makefile from the current build.
 */
function drush_make_generate($file = NULL) {
  $version_options = _drush_make_generate_get_version_options();
  $all_extensions = drush_get_extensions();
  list($projects, $libraries) = _drush_make_generate_projects($all_extensions, $version_options);
  $core = drush_drupal_major_version() . '.x';
  $contents = make_generate_makefile_contents($projects, $libraries, $core);

  // Write or print our makefile.
  make_generate_print($contents, $file);
}

/**
 * Create the $version_options array from the --include-versions and
 * --exclude-versions command line options.
 */
function _drush_make_generate_get_version_options() {
  // What projects should we pin the versions for?
  // Check the command-line options for details.
  foreach (array("include", "exclude") as $option) {
    $version_options[$option] = drush_get_option("$option-versions");
    if ($version_options[$option] !== TRUE) {
      $version_options[$option] = array_filter(explode(",", $version_options[$option]));
    }
  }
  return $version_options;
}

/**
 * Generate the $projects makefile array for the current site.
 */
function _drush_make_generate_projects($all_extensions, $version_options) {
  $release_info = drush_get_engine('release_info');

  $projects = array();
  $project_libraries = array();

  $system_requirements = system_requirements('runtime');
  // Update xml expects the drupal version to be expressed as "7.x" or "8.x"
  // We used to check $system_requirements['drupal']['value'], but this now
  // contains values such as "7.10-dev".
  $drupal_major_version = drush_drupal_major_version() . '.x';
  $core_project = strtolower($system_requirements['drupal']['title']);
  $projects[$core_project] = array('_type' => 'core');
  if ($core_project != 'drupal') {
    $projects[$core_project]['custom_download'] = TRUE;
    $projects[$core_project]['type'] = 'core';
  }
  else {
    // Drupal core - we can determine the version if required.
    if (_drush_generate_track_version("drupal", $version_options)) {
      $projects[$core_project]["version"] = drush_drupal_version();
    }
  }

  $install_profile = drush_drupal_major_version() >= 7 ? drupal_get_profile() : variable_get('install_profile', '');
  if (!in_array($install_profile, array('default', 'standard', 'minimal', 'testing')) && $install_profile != '') {
    $projects[$install_profile]['type']
      = $projects[$install_profile]['_type'] = 'profile';
    $request = array(
      'name' => $install_profile,
      'drupal_version' => $drupal_major_version,
    );
    if (!$release_info->checkProject($request, 'profile')) {
      $projects[$install_profile]['custom_download'] = TRUE;
    }
  }

  // Iterate installed projects to build $projects array.
  $extensions = $all_extensions;
  $project_info = drush_get_projects($extensions);
  foreach ($project_info as $name => $project) {
    // Discard the extensions within this project. At the end $extensions will
    // contain only extensions part of custom projects (not from drupal.org or
    // other update service).
    foreach ($project['extensions'] as $ext) {
      unset($extensions[$ext]);
    }
    if ($name == 'drupal') {
      continue;
    }
    $type = $project['type'];
    // Discard projects with all modules disabled.
    if (($type == 'module') && (!$project['status'])) {
      continue;
    }
    $projects[$name] = array('_type' => $type);
    // Check the project is on drupal.org or its own update service.
    $request = array(
      'name' => $name,
      'drupal_version' => $drupal_major_version,
    );
    if (isset($project['status url'])) {
      $request['status url'] = $project['status url'];
      $projects[$name]['location'] = $project['status url'];
    }
    if (!$release_info->checkProject($request, $type)) {
      // It is not a project on drupal.org neither an external update service.
      $projects[$name]['type'] = $type;
      $projects[$name]['custom_download'] = TRUE;
    }
    // Add 'subdir' if the project is installed in a non-default location.
    if (isset($project['path'])) {
      $projects[$name] += _drush_generate_makefile_check_path($project);
    }
    // Add version number if this project's version is to be tracked.
    if (_drush_generate_track_version($name, $version_options) && $project["version"]) {
      $version = preg_replace("/^" . drush_get_drupal_core_compatibility() . "-/", "", $project["version"]);
      // Strip out MINOR+GIT_COMMIT strings for dev releases.
      if (substr($version, -4) == '-dev' && strpos($version, '+')) {
        $version = substr($version, 0, strrpos($version, '.')) . '.x-dev';
      }
      $projects[$name]['version'] = $version;
    }
    foreach ($project['extensions'] as $extension_name) {
      _drush_make_generate_add_patch_files($projects[$name], _drush_extension_get_path($all_extensions[$extension_name]));
    }
  }

  // Add a project for each unknown extension.
  foreach ($extensions as $name => $extension) {
    list($project_name, $project_data) = _drush_generate_custom_project($name, $extension, $version_options);
    $projects[$project_name] = $project_data;
  }

  // Add libraries.
  if (function_exists('libraries_get_libraries')) {
    $libraries = libraries_get_libraries();
    foreach ($libraries as $library_name => $library_path) {
      $path = explode('/', $library_path);
      $project_libraries[$library_name] = array(
        'directory_name' => $path[(count($path) - 1)],
        'custom_download' => TRUE,
        'type' => 'library',
        '_type' => 'librarie', // For plural.
      );
    }
  }
  return array($projects, $project_libraries);
}

/**
 * Record any patches that were applied to this project
 * per information stored in PATCHES.txt.
 */
function _drush_make_generate_add_patch_files(&$project, $location) {
  $patchfile = DRUPAL_ROOT . '/' . $location . '/PATCHES.txt';
  if (is_file($patchfile)) {
    foreach (file($patchfile) as $line) {
      if (substr($line, 0, 2) == '- ') {
        $project['patch'][] = trim(substr($line, 2));
      }
    }
  }
}

/**
 * Create a project record for an extension not downloaded from drupal.org
 */
function _drush_generate_custom_project($name, $extension, $version_options) {
  $project['_type'] = drush_extension_get_type($extension);
  $project['type'] = drush_extension_get_type($extension);
  $location = drush_extension_get_path($extension);
  // To start off, we will presume that our custom extension is
  // stored in a folder named after its project, and there are
  // no subfolders between the .info file and the project root.
  $project_name = basename($location);
  drush_shell_cd_and_exec($location, 'git rev-parse --git-dir 2> ' . drush_bit_bucket());
  $output = drush_shell_exec_output();
  if (!empty($output)) {
    $git_dir = $output[0];
    // Find the actual base of the git repository.
    $repo_root = $git_dir == ".git" ? $location : dirname($git_dir);
    // If the repository root is at the drupal root or some parent
    // of the drupal root, or some other location that could not
    // pausibly be a project, then there is nothing we can do.
    // (We can't tell Drush make to download some sub-part of a repo,
    // can we?)
    if ($repo_project_name = _drush_generate_validate_repo_location($repo_root)) {
      $project_name = $repo_project_name;
      drush_shell_cd_and_exec($repo_root, 'git remote show origin');
      $output = drush_shell_exec_output();
      foreach ($output as $line) {
        if (strpos($line, "Fetch URL:") !== FALSE) {
          $url = preg_replace('/ *Fetch URL: */', '', $line);
          if (!empty($url)) {
            // We use the unconventional-looking keys
            // `download][type` and `download][url` so that
            // we can produce output that appears to be two-dimensional
            // arrays from a single-dimensional array.
            $project['download][type'] = 'git';
            $project['download][url'] = $url;

            // Fill in the branch as well.
            drush_shell_cd_and_exec($repo_root, 'git branch');
            $output = drush_shell_exec_output();
            foreach ($output as $line) {
              if ($line{0} == '*') {
                $branch = substr($line, 2);
                if ($branch != "master") {
                  $project['download][branch'] = $branch;
                }
              }
            }

            // Put in the commit hash.
            drush_shell_cd_and_exec($repo_root, 'git log');
            $output = drush_shell_exec_output();
            if (substr($output[0], 0, 7) == "commit ") {
              $revision = substr($output[0], 7);
              if (_drush_generate_track_version($project_name, $version_options)) {
                $project['download][revision'] = $revision;
              }
            }

            // Add patch files, if any.
            _drush_make_generate_add_patch_files($project, $repo_root);
          }
        }
      }
    }
  }
  // If we could not figure out where the extension came from, then give up and
  // flag it as a "custom" download.
  if (!isset($project['download][type'])) {
    $project['custom_download'] = TRUE;
  }
  return array($project_name, $project);
}

/**
 * If the user has checked in the Drupal root, or the 'sites/all/modules'
 * folder into a git repository, then we do not want to confuse that location
 * with a "project".
 */
function _drush_generate_validate_repo_location($repo_root) {
  $project_name = basename($repo_root);
  // The Drupal root, or any folder immediately inside the Drupal
  // root cannot be a project location.
  if ((strlen(DRUPAL_ROOT) >= strlen($repo_root)) || (dirname($repo_root) == DRUPAL_ROOT)) {
    return NULL;
  }
  // Also exclude sites/* and sites/*/{modules,themes} and profile/* and
  // profile/*/{modules,themes}.
  return $project_name;
}

/**
 * Helper function to determine if a given project is to have its version
 * tracked.
 */
function _drush_generate_track_version($project, $version_options) {
  // A. If --exclude-versions has been specified:
  // A.a. if it's a boolean, check the --include-versions option.
  if ($version_options["exclude"] === TRUE) {
    // A.a.1 if --include-versions has been specified, ensure it's an array.
    if (is_array($version_options["include"])) {
      return in_array($project, $version_options["include"]);
    }
    // A.a.2 If no include array, then we're excluding versions for ALL
    // projects.
    return FALSE;
  }
  // A.b. if --exclude-versions is an array with items, check this project is in
  // it: if so, then return FALSE.
  elseif (is_array($version_options["exclude"]) && count($version_options["exclude"])) {
    return !in_array($project, $version_options["exclude"]);
  }

  // B. If by now no --exclude-versions, but --include-versions is an array,
  // examine it for this project.
  if (is_array($version_options["include"]) && count($version_options["include"])) {
    return in_array($project, $version_options["include"]);
  }

  // If none of the above conditions match, include version number by default.
  return TRUE;
}

/**
 * Helper function to check for a non-default installation location.
 */
function _drush_generate_makefile_check_path($project) {
  $info = array();
  $type = $project['type'];
  $path = dirname($project['path']);
  // Check to see if the path is in a subdir sites/all/modules or
  // profiles/profilename/modules
  if (preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path) || preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path)) {
    $subdir = preg_replace(array('@^[a-zA-Z0-9_]*/[a-zA-Z0-9_]*/' . $type . 's/*@', "@/$name" . '$@'), '', $path);
    if (!empty($subdir)) {
      $info['subdir'] = $subdir;
    }
  }
  return $info;
}
<?php

/**
 * @file
 * make-lock command implementation.
 */

/**
 * Command callback for make-lock.
 */
function drush_make_lock($makefile) {
  make_generate_from_makefile(drush_get_option('result-file'), $makefile);
}
<?php
/**
 * @file
 * Download-specific functions for Drush Make.
 */

use Drush\Log\LogLevel;

/**
 * Downloads the given package to the destination directory.
 *
 * @return mixed
 *   The destination path on success, FALSE on failure.
 */
function make_download_factory($name, $type, $download, $download_location) {
  $function = 'make_download_' . $download['type'];
  if (function_exists($function)) {
    return $function($name, $type, $download, $download_location);
  }
  else {
    return FALSE;
  }
}

/**
 * Download project using drush's pm-download command.
 */
function make_download_pm($name, $type, $download, $download_location) {
  $full_project_version = $name . '-' . $download['full_version'];

  $options = array(
    'destination' => dirname($download_location),
    'yes' => TRUE,
    'package-handler' => 'wget',
    'source' => $download['status url'],
    // This is only relevant for profiles, but we generally want the variant to
    // be 'profile-only' so we don't end up with extra copies of core.
    'variant' => $type == 'core' ? 'full' : $download['variant'],
    'cache' => TRUE,
  );
  if ($type == 'core') {
    $options['drupal-project-rename'] = basename($download_location);
  }
  if (drush_get_option('no-cache', FALSE)) {
    unset($options['cache']);
  }

  $backend_options = array();
  if (!drush_get_option(array('verbose', 'debug'), FALSE)) {
    $backend_options['integrate'] = TRUE;
    $backend_options['log'] = FALSE;
  }

  // Perform actual download with `drush pm-download`.
  $return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options);
  if (empty($return['error_log'])) {
    // @todo Report the URL we used for download. See
    // http://drupal.org/node/1452672.
    drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), LogLevel::OK);
  }
}

/**
 * Downloads a file to the specified location.
 *
 * @return mixed
 *   The destination directory on success, FALSE on failure.
 */
function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
  if ($filename = _make_download_file($download['url'], $cache_duration)) {
    if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) {
      return FALSE;
    }
    drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
    $download_filename = isset($download['filename']) ? $download['filename'] : '';
    $subtree = isset($download['subtree']) ? $download['subtree'] : NULL;
    return make_download_file_unpack($filename, $download_location, $download_filename, $subtree);
  }
  make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
  return FALSE;
}

/**
 * Wrapper to drush_download_file().
 *
 * @param string $download
 *   The url of the file to download.
 * @param int $cache_duration
 *   The time in seconds to cache the resultant download.
 *
 * @return string
 *   The location of the downloaded file, or FALSE on failure.
 */
function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
  if (drush_get_option('no-cache', FALSE)) {
    $cache_duration = 0;
  }

  $tmp_path = make_tmp();
  // Ensure that we aren't including the querystring when generating a filename
  // to save our download to.
  $file = basename(current(explode('?', $download, 2)));
  return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration);
}

/**
 * Unpacks a file to the specified download location.
 *
 * @return mixed
 *   The download location on success, FALSE on failure.
 */
function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) {
  $success = FALSE;

  if (drush_file_is_tarball($filename)) {
    $tmp_location = drush_tempdir();

    if (!drush_tarball_extract($filename, $tmp_location)) {
      return FALSE;
    }

    if ($subtree) {
      $tmp_location .= '/' . $subtree;
      if (!file_exists($tmp_location)) {
        return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename)));
      }
    }
    else {
      $files = scandir($tmp_location);
      unset($files[0]); // . directory
      unset($files[1]); // .. directory
      if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) {
        $tmp_location .= '/' . current($files);
      }
    }

    $success = drush_move_dir($tmp_location, $download_location, TRUE);

    // Remove the tarball.
    if (file_exists($filename)) {
      drush_delete_dir($filename, TRUE);
    }
  }
  else {
    // If this is an individual file, and no filename has been specified,
    // assume the original name.
    if (is_file($filename) && !$name) {
      $name = basename($filename);
    }

    // The destination directory has already been created by
    // findDownloadLocation().
    $destination = $download_location . ($name ? '/' . $name : '');
    $success = drush_move_dir($filename, $destination, TRUE);
  }
  return $success ? $download_location : FALSE;
}

/**
 * Move a downloaded and unpacked file or directory into place.
 */
function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) {
  $lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
  $main_directory = basename($download_location);
  if (count($lines) == 1) {
    $directory = array_shift($lines);
    if ($directory->basename != $main_directory) {
      drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE);
    }
    drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, FILE_EXISTS_OVERWRITE);
    drush_delete_dir($tmp_path, TRUE);
  }
  elseif (count($lines) > 1) {
    drush_delete_dir($download_location, TRUE);
    drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE);
  }

  // Remove the tarball.
  if (file_exists($filename)) {
    drush_delete_dir($filename, TRUE);
  }

  if (file_exists($tmp_path)) {
    drush_delete_dir($tmp_path, TRUE);
  }
  return TRUE;
}


/**
 * For backwards compatibility.
 */
function make_download_get($name, $type, $download, $download_location) {
  return make_download_file($name, $type, $download, $download_location);
}

/**
 * Copies a folder the specified location.
 *
 * @return mixed
 *   The TRUE on success, FALSE on failure.
 */
function make_download_copy($name, $type, $download, $download_location) {
    if ($folder = _make_download_copy($download['url'])) {
        drush_log(dt('@project copied from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
        return drush_copy_dir($folder, $download_location, FILE_EXISTS_OVERWRITE);
    }
    make_error('COPY_ERROR', dt('Unable to copy @project from @url.', array('@project' => $name, '@url' => $download['url'])));
    return FALSE;
}

/**
 * Wrapper to drush_download_copy().
 *
 * @param string $folder
 *   The location of the folder to copy.
 *
 * @return string
 *   The location of the folder, or FALSE on failure.
 */
function _make_download_copy($folder) {
    if (substr($folder, 0, 7) == 'file://') {
        $folder = substr($folder, 7);
    }

    if (is_dir($folder)) {
        return $folder;
    }
    return FALSE;
}

/**
 * Checks out a git repository to the specified download location.
 *
 * Allowed parameters in $download, in order of precedence:
 *   - 'tag'
 *   - 'revision'
 *   - 'branch'
 *
 * This will also attempt to write out release information to the
 * .info file if the 'no-gitinfofile' option is FALSE. If
 * $download['full_version'] is present, this will be used, otherwise,
 * version will be set in this order of precedence:
 *   - 'tag'
 *   - 'branch'
 *   - 'revision'
 *
 * @return mixed
 *   The download location on success, FALSE otherwise.
 */
function make_download_git($name, $type, $download, $download_location) {
  $tmp_path = make_tmp();
  $wc = _get_working_copy_option($download);
  $checkout_after_clone = TRUE;
  // If no download URL specified, assume anonymous clone from git.drupal.org.
  $download['url'] = isset($download['url']) ? $download['url'] : "http://git.drupal.org/project/$name.git";
  // If no working-copy download URL specified, assume it is the same.
  $download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url'];

  // If not a working copy, and if --no-cache has not been explicitly
  // declared, create a new git reference cache of the remote repository,
  // or update the existing cache to fetch recent changes.
  // @see package_handler_download_project()
  $cache = !$wc && !drush_get_option('no-cache', FALSE);
  if ($cache && ($git_cache = drush_directory_cache('git'))) {
    $project_cache = $git_cache . '/' . $name . '-' . md5($download['url']);
    // Set up a new cache, if it doesn't exist.
    if (!file_exists($project_cache)) {
      $command = 'git clone --mirror';
      if (drush_get_context('DRUSH_VERBOSE')) {
        $command .= ' --verbose --progress';
      }
      $command .= ' %s %s';
      drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache);
    }
    else {
      // Update the --mirror clone.
      drush_shell_cd_and_exec($project_cache, 'git remote update');
    }
    $git_cache = $project_cache;
  }

  // Use working-copy download URL if --working-copy specified.
  $url = $wc ? $download['wc_url'] : $download['url'];

  $tmp_location = drush_tempdir() . '/' . basename($download_location);

  $command = 'git clone %s %s';
  if (drush_get_context('DRUSH_VERBOSE')) {
    $command .= ' --verbose --progress';
  }
  if ($cache) {
    $command .= ' --reference ' . drush_escapeshellarg($git_cache);
  }

  // the shallow clone option is only applicable to git entries which reference a tag or a branch
  if (drush_get_option('shallow-clone', FALSE) &&
     (!empty($download['tag']) || !empty($download['branch']))) {

    $branch = (!empty($download['branch']) ? $download['branch'] : $download['tag']);
    $command .= " --depth=1 --branch=${branch}";

    // since the shallow copy option automatically "checks out" the requested branch, no further
    // actions are needed after the clone command
    $checkout_after_clone = FALSE;
  }

  // Before we can checkout anything, we need to clone the repository.
  if (!drush_shell_exec($command, $url, $tmp_location)) {
    make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url)));
    return FALSE;
  }

  drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), LogLevel::OK);

  if ($checkout_after_clone) {
    // Get the current directory (so we can move back later).
    $cwd = getcwd();
    // Change into the working copy of the cloned repo.
    chdir($tmp_location);

    // We want to use the most specific target possible, so first try a refspec.
    if (!empty($download['refspec'])) {
      if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) {
        drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), LogLevel::OK);

        if (drush_shell_exec("git checkout FETCH_HEAD")) {
          drush_log(dt("Checked out FETCH_HEAD."), LogLevel::INFO);
        }
      }
      else {
        make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name)));
      }
    }

    // If there wasn't a refspec, try a tag.
    elseif (!empty($download['tag'])) {
      // @TODO: change checkout to refs path.
      if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) {
        drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), LogLevel::OK);
      }
      else {
        make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag'])));
      }
    }

    // If there wasn't a tag, try a specific revision hash.
    elseif (!empty($download['revision'])) {
      if (drush_shell_exec("git checkout %s", $download['revision'])) {
        drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), LogLevel::OK);
      }
      else {
        make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision'])));
      }
    }

    // If not, see if we at least have a branch.
    elseif (!empty($download['branch'])) {
      if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) {
        drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), LogLevel::OK);
      }
      elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) {
        drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), LogLevel::OK);
      }
      else {
        make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch'])));
      }
    }

    if (!empty($download['submodule'])) {
      $command = 'git submodule update';
      foreach ($download['submodule'] as $option) {
        $command .= ' --%s';
      }
      if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) {
        drush_log(dt('Initialized registered submodules.'), LogLevel::OK);
      }
      else {
        make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.'));
      }
    }

    // Move back to last current directory (first line).
    chdir($cwd);
  }

  // Move the directory into the final resting location.
  drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);

  return dirname($tmp_location);
}

/**
 * Checks out a Bazaar repository to the specified download location.
 *
 * @return mixed
 *   The download location on success, FALSE otherwise.
 */
function make_download_bzr($name, $type, $download, $download_location) {
  $tmp_path = make_tmp();
  $tmp_location = drush_tempdir() . '/' . basename($download_location);
  $wc = _get_working_copy_option($download);
  if (!empty($download['url'])) {
    $args = array();
    $command = 'bzr';
    if ($wc) {
      $command .= ' branch  --use-existing-dir';
    }
    else {
      $command .= ' export';
    }
    if (isset($download['revision'])) {
      $command .= ' -r %s';
      $args[] = $download['revision'];
    }
    $command .= ' %s %s';
    if ($wc) {
      $args[] = $download['url'];
      $args[] = $tmp_location;
    }
    else {
      $args[] = $tmp_location;
      $args[] = $download['url'];
    }
    array_unshift($args, $command);
    if (call_user_func_array('drush_shell_exec', $args)) {
      drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
      drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);
      return dirname($download_location);
    }
  }
  else {
    $download['url'] = dt("unspecified location");
  }
  make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
  drush_delete_dir(dirname($tmp_location), TRUE);
  return FALSE;
}

/**
 * Checks out an SVN repository to the specified download location.
 *
 * @return mixed
 *   The download location on success, FALSE otherwise.
 */
function make_download_svn($name, $type, $download, $download_location) {
  $wc = _get_working_copy_option($download);
  if (!empty($download['url'])) {
    if (!empty($download['interactive'])) {
      $function = 'drush_shell_exec_interactive';
    }
    else {
      $options = ' --non-interactive';
      $function = 'drush_shell_exec';
    }
    if (!isset($download['force']) || $download['force']) {
      $options = ' --force';
    }
    if ($wc) {
      $command = 'svn' . $options . ' checkout';
    }
    else {
      $command = 'svn' . $options . ' export';
    }

    $args = array();

    if (isset($download['revision'])) {
      $command .= ' -r%s';
      $args[] = $download['revision'];
    }

    $command .= ' %s %s';
    $args[] = $download['url'];
    $args[] = $download_location;

    if (!empty($download['username'])) {
      $command .= ' --username %s';
      $args[] = $download['username'];
      if (!empty($download['password'])) {
        $command .= ' --password %s';
        $args[] = $download['password'];
      }
    }
    array_unshift($args, $command);
    $result = call_user_func_array($function, $args);
    if ($result) {
      $args = array(
        '@project' => $name,
        '@command' => $command,
        '@url' => $download['url'],
      );
      drush_log(dt('@project @command from @url.', $args), LogLevel::OK);
      return $download_location;
    }
    else {
      $download['url'] = dt("unspecified location");
    }
  }
  else {
    make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
    return FALSE;
  }
}

/**
 * Test that any supplied hash values match the hash of the file content.
 *
 * Unsupported hash algorithms are reported as failure.
 */
function _make_verify_checksums($info, $filename) {
  $hash_algos = array('md5', 'sha1', 'sha256', 'sha512');
  // We only have something to do if a key is an
  // available function.
  if (array_intersect(array_keys($info), $hash_algos)) {
    $content = file_get_contents($filename);
    foreach ($hash_algos as $algo) {
      if (!empty($info[$algo])) {
        $hash = _make_hash($algo, $content);
        if ($hash !== $info[$algo]) {
          $args = array(
            '@algo' => $algo,
            '@file' => basename($filename),
            '@expected' => $info[$algo],
            '@hash' => $hash,
          );
          make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args));
          return FALSE;
        }
      }
    }
  }
  return TRUE;
}

/**
 * Calculate the hash of a string for a given algorithm.
 */
function _make_hash($algo, $string) {
  switch ($algo) {
    case 'md5':
      return md5($string);
    case 'sha1':
      return sha1($string);
    default:
      return function_exists('hash') ? hash($algo, $string) : '';
  }
}
<?php
/**
 * @file
 * Drush Make commands.
 */

use Drush\Log\LogLevel;
use Drush\UpdateService\ReleaseInfo;

/**
 * Default localization server for downloading translations.
 */
define('MAKE_DEFAULT_L10N_SERVER', 'http://ftp.drupal.org/files/translations/l10n_server.xml');

/**
 * Make refuses to build makefiles whose api version is mismatched
 * with make command.
 */
define('MAKE_API', 2);

include_once 'make.utilities.inc';
include_once 'make.download.inc';
include_once 'make.project.inc';
include_once 'generate.contents.make.inc';

/**
 * Implements hook_drush_help().
 */
function make_drush_help($section) {
  switch ($section) {
    case 'meta:make:title':
      return dt('Make commands');
    case 'meta:make:summary':
      return dt('Manage Drupal codebases using manifests of projects and libraries.');
    case 'drush:make':
      return dt('Turns a makefile into a Drupal codebase. For a full description of options and makefile syntax, see docs/make.txt and examples/example.make.');
    case 'drush:make-generate':
      return dt('Generate a makefile from the current Drupal site, specifying project version numbers unless not known or otherwise specified. Unversioned projects will be interpreted later by drush make as "most recent stable release"');
  }
}

/**
 * Implements hook_drush_command().
 */
function make_drush_command() {
  $projects = array(
    'description' => 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.',
    'example-value' => 'views,ctools',
  );
  $libraries = array(
    'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.',
    'example-value' => 'tinymce',
  );

  $items['make'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Turns a makefile into a working Drupal codebase.',
    'arguments' => array(
      'makefile' => 'Filename of the makefile to use for this build.',
      'build path' => 'The path at which to build the makefile.',
    ),
    'examples' => array(
      'drush make example.make example' => 'Build the example.make makefile in the example directory.',
      'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site',
      'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.',
      'drush make example.make --no-build --lock=example.lock' => 'Write a new makefile to example.lock. All project versions will be resolved.',
    ),
    'options' => array(
      'version' => 'Print the make API version and exit.',
      'concurrency' => array(
        'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.',
        'example-value' => '1',
      ),
      'contrib-destination' => 'Specify a path under which modules and themes should be placed. Defaults to sites/all for Drupal 6,7 and the corresponding directory in the Drupal root for Drupal 8 and above.',
      'force-complete' => 'Force a complete build even if errors occur.',
      'ignore-checksums' => 'Ignore md5 checksums for downloads.',
      'md5' => array(
        'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.',
        'example-value' => 'print',
        'value' => 'optional',
      ),
      'make-update-default-url' => 'The default location to load the XML update information from.',
      'no-build' => 'Do not build a codebase. Makes the `build path` argument optional.',
      'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).',
      'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.',
      'no-core' => 'Do not require a Drupal core project to be specified.',
      'no-recursion' => 'Do not recurse into the makefiles of any downloaded projects; you can also set [do_recursion] = 0 on a per-project basis in the makefile.',
      'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.',
      'no-gitinfofile' => 'Do not modify .info files when cloning from Git.',
      'force-gitinfofile' => 'Force a modification of .info files when cloning from Git even if repository isn\'t hosted on Drupal.org.',
      'no-gitprojectinfo' => 'Do not inject project info into .info files when cloning from Git.',
      'overwrite' => 'Overwrite existing directories. Default is to merge.',
      'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.',
      'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.',
      'test' => 'Run a temporary test build and clean up.',
      'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.',
      'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.',
      'download-mechanism' => 'How to download files. Should be autodetected, but this is an override if it doesn\'t work. Options are "curl" and "make" (a native download method).',
      'projects' => $projects,
      'libraries' => $libraries,
      'allow-override' => array(
        'description' => 'Restrict the make options to a comma-separated list. Defaults to unrestricted.',
      ),
      'lock' => array(
        'description' => 'Generate a makefile, based on the one passed in, with all versions *resolved*. Defaults to printing to the terminal, but an output file may be provided.',
        'example-value' => 'example.make.lock',
      ),
      'shallow-clone' => array(
        'description' => 'For makefile entries which use git for downloading, this option will utilize shallow clones where possible (ie. by using the git-clone\'s depth=1 option). If the "working-copy" option is not desired, this option will significantly speed up makes which involve modules stored in very large git repos. In fact, if "working-copy" option is enabled, this option cannot be used.',
      ),
      'bundle-lockfile' => array(
        'description' => 'Generate a lockfile for this build and copy it into the codebase (at sites/all/drush/platform.lock). An alternate path (relative to the Drupal root) can also be specified',
        'example-value' => 'sites/all/drush/example.make.lock',
      ),
      'format' => array(
        'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
        'example-value' => 'ini',
      ),
      'core-quick-drupal' => array(
        'description' => 'Return project info for use by core-quick-drupal.',
        'hidden' => TRUE,
      ),
      'includes' => 'A list of makefiles to include at build-time.',
      'overrides' => 'A list of makefiles to that can override values in other makefiles.',
    ),
    'engines' => array('release_info'),
    'topics' => array('docs-make', 'docs-make-example'),
  );

  $items['make-generate'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
    'description' => 'Generate a makefile from the current Drupal site.',
    'examples' => array(
      'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)',
      'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned',
      'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK',
      'drush generate-makefile example.make --include-versions=admin_menu,og,ctools (--exclude-versions)' => 'Generate a makefile with NO projects versioned EXCEPT Admin Menu, OG and CTools.',
    ),
    'options' => array(
      'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning',
      'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)',
      'format' => array(
        'description' => 'The format for generated makefile. Options are "yaml" or "ini". Defaults to "yaml".',
        'example-value' => 'ini',
      ),
    ),
    'engines' => array('release_info'),
    'aliases' => array('generate-makefile'),
  );

  $items['make-convert'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Convert a legacy makefile into another format. Defaults to converting .make => .make.yml.',
    'arguments' => array(
      'makefile' => 'Filename of the makefile to convert.',
    ),
    'options' => array(
      'projects' => $projects,
      'libraries' => $libraries,
      'includes' => 'A list of makefiles to include at build-time.',
      'format' => 'The format to which the make file should be converted. Accepted values include make, composer, and yml.',
    ),
    'required-arguments' => TRUE,
    'examples' => array(
      'drush make-convert example.make --format=composer  > composer.json' => 'Convert example.make to composer.json',
      'drush make-convert example.make --format=yml > example.make.yml' => 'Convert example.make to example.make.yml',
      'drush make-convert composer.lock --format=make > example.make' => 'Convert composer.lock example.make',
    ),
  );

  // Hidden command to build a group of projects.
  $items['make-process'] = array(
    'hidden' => TRUE,
    'arguments' => array(
      'directory' => 'The temporary working directory to use',
    ),
    'options' => array(
      'projects-location' => 'Name of a temporary file containing json-encoded output of make_projects().',
      'manifest' => 'An array of projects already being processed.',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'engines' => array('release_info'),
  );

  $items['make-update'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Process a makefile and outputs an equivalent makefile with projects version resolved to latest available.',
    'arguments' => array(
      'makefile' => 'Filename of the makefile to use for this build.',
    ),
    'options' => array(
      'result-file' => array(
        'description' => 'Save to a file. If not provided, the updated makefile will be dumped to stdout.',
        'example-value' => 'updated.make',
      ),
      'format' => array(
        'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
        'example-value' => 'ini',
      ),
      'includes' => 'A list of makefiles to include at build-time.',
    ),
    'engines' => array('release_info', 'update_status'),
  );

  $items['make-lock'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Process a makefile and outputs an equivalent makefile with projects version *resolved*. Respects pinned versions.',
    'arguments' => array(
      'makefile' => 'Filename of the makefile to use for this build.',
    ),
    'options' => array(
      'result-file' => array(
        'description' => 'Save to a file. If not provided, the lockfile will be dumped to stdout.',
        'example-value' => 'platform.lock',
      ),
      'format' => array(
        'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
        'example-value' => 'ini',
      ),
      'includes' => 'A list of makefiles to include at build-time.',
    ),
    'allow-additional-options' => TRUE,
    'engines' => array('release_info', 'update_status'),
  );

  // Add docs topic.
  $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
  $items['docs-make'] = array(
    'description' => 'Drush Make overview with examples',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/docs/make.md'),
  );
  $items['docs-make-example'] = array(
    'description' => 'Drush Make example makefile',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array($docs_dir . '/examples/example.make.yml'),
  );
  return $items;
}

/**
 * Command argument complete callback.
 *
 * @return array
 *   Strong glob of files to complete on.
 */
function make_make_complete() {
  return array(
    'files' => array(
      'directories' => array(
        'pattern' => '*',
        'flags' => GLOB_ONLYDIR,
      ),
      'make' => array(
        'pattern' => '*.make',
      ),
    ),
  );
}

/**
 * Validation callback for make command.
 */
function drush_make_validate($makefile = NULL, $build_path = NULL) {
  // Don't validate if --version option is supplied.
  if (drush_get_option('version', FALSE)) {
    return;
  }

  if (drush_get_option('shallow-clone', FALSE) && drush_get_option('working-copy', FALSE)) {
    return drush_set_error('MAKE_SHALLOW_CLONE_WORKING_COPY_CONFLICT', dt('You cannot use "--shallow-clone" and "--working-copy" options together.'));
  }

  // Error out if the build path is not valid and --no-build was not supplied.
  if (!drush_get_option('no-build', FALSE) && !make_build_path($build_path)) {
    return FALSE;
  }
}

/**
 * Implements drush_hook_pre_COMMAND().
 *
 * If --version option is supplied, print it and prevent execution of the command.
 */
function drush_make_pre_make($makefile = NULL, $build_path = NULL) {
  if (drush_get_option('version', FALSE)) {
    drush_print(dt('Drush make API version !version', array('!version' => MAKE_API)));
    drush_print_pipe(MAKE_API);
    // Prevent command execution.
    return FALSE;
  }
}

/**
 * Drush callback; make based on the makefile.
 */
function drush_make($makefile = NULL, $build_path = NULL) {
  // Set the cache option based on our '--no-cache' option.
  _make_enable_cache();

  // Build.
  if (!drush_get_option('no-build', FALSE)) {
    $info = make_parse_info_file($makefile);
    drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), LogLevel::OK);

    // Default contrib destination depends on Drupal core version.
    $core_version = str_replace('.x', '', $info['core'][0]);
    $sitewide = drush_drupal_sitewide_directory($core_version);
    $contrib_destination = drush_get_option('contrib-destination', $sitewide);

    $build_path = make_build_path($build_path);
    $make_dir = realpath(dirname($makefile));

    $success = make_projects(FALSE, $contrib_destination, $info, $build_path, $make_dir);
    if ($success) {
      make_libraries(FALSE, $contrib_destination, $info, $build_path, $make_dir);

      if (drush_get_option('prepare-install')) {
        make_prepare_install($build_path);
      }
      if ($option = drush_get_option('md5')) {
        $md5 = make_md5();
        if ($option === 'print') {
          drush_print($md5);
        }
        else {
          drush_log(dt('Build hash: %md5', array('%md5' => $md5)), LogLevel::OK);
        }
      }
      // Only take final build steps if not in testing mode.
      if (!drush_get_option('test')) {
        if (drush_get_option('tar')) {
          make_tar($build_path);
        }
        else {
          make_move_build($build_path);
        }
      }
      make_clean_tmp();
    }
    else {
      return make_error('MAKE_PROJECTS_FAILED', dt('Drush Make failed to download all projects. See the log above for the specific errors.'));
    }
  }

  // Process --lock and --bundle-lockfile
  $lockfiles = array();
  if ($result_file = drush_get_option('bundle-lockfile', FALSE)) {
    if ($result_file === TRUE) {
      $result_file = 'sites/all/drush/platform.make';
    }
    $lockfiles[] = $build_path . '/' . $result_file;
  }
  if ($result_file = drush_get_option('lock', FALSE)) {
    $lockfiles[] = $result_file;
  }
  if (count($lockfiles)) {
    foreach ($lockfiles as $lockfile) {
      if ($lockfile !== TRUE) {
        $result_file = drush_normalize_path($lockfile);
        drush_mkdir(dirname($result_file), $required = TRUE);
        drush_set_option('result-file', $result_file);
      }
      drush_invoke('make-lock', $makefile);
      drush_unset_option('result-file');
    }
  }

  // Used by core-quick-drupal command.
  // @see drush_core_quick_drupal().
  if (drush_get_option('core-quick-drupal', FALSE)) {
    return $info;
  }
}

/**
 * Command callback; convert ini makefile to YAML.
 */
function drush_make_convert($source) {
  $dest_format = drush_get_option('format', 'yml');

  // Load source data.
  $source_format = pathinfo($source, PATHINFO_EXTENSION);

  if ($source_format == $dest_format || $source_format == 'lock' && $dest_format == 'composer') {
    drush_print('The source format cannot be the same as the destination format.');
  }

  // Obtain drush make $info array, converting if necessary.
  switch ($source_format) {
    case 'make':
    case 'yml':
    case 'yaml':
      $info = make_parse_info_file($source);
      break;
    case 'lock':
      $composer_json_file = str_replace('lock', 'json', $source);
      if (!file_exists($composer_json_file)) {
        drush_print('Please ensure that a composer.json file is in the same directory as the specified composer.lock file.');
        return FALSE;
      }
      $composer_json = json_decode(make_get_data($composer_json_file), TRUE);
      $composer_lock = json_decode(make_get_data($source), TRUE);
      $info = drush_make_convert_composer_to_make($composer_lock, $composer_json);
      break;
    case 'json':
      drush_print('Please use composer.lock instead of composer.json as source for conversion.');
      return FALSE;
      break;
  }

  // Output into destination formation.
  switch ($dest_format) {
    case 'yml':
    case 'yaml':
      $output = drush_make_convert_make_to_yml($info);
      break;

    case 'make':
      foreach ($info['projects'] as $key => $project) {
        $info['projects'][$key]['_type'] = $info['projects'][$key]['type'];
      }
      foreach ($info['libraries'] as $key => $library) {
        $info['libraries'][$key]['_type'] = 'librarie';
      }
      $output = _drush_make_generate_makefile_contents($info['projects'], $info['libraries'], $info['core'], $info['defaults']);

      break;

    case 'composer':
      $output = drush_make_convert_make_to_composer($info);
      break;
  }

  drush_print($output);
}

/**
 * Converts a composer.lock array into a traditional drush make array.
 *
 * @param array $composer_lock
 *   An array of composer.lock data.
 *
 * @param array $composer_json
 *   An array of composer.json data.
 *
 * @return array A traditional drush make info array.
 * A traditional drush make info array.
 */
function drush_make_convert_composer_to_make($composer_lock, $composer_json) {
  $info = array(
    'core' => array(),
    'api' => 2,
    'defaults' => array(
      'projects' => array(
        'subdir' => 'contrib',
      ),
    ),
    'projects' => array(),
    'libraries' => array(),
  );

  // The make generation function requires that projects be grouped by type,
  // or else duplicative project groups will be created.
  $core = array();
  $modules = array();
  $themes = array();
  $profiles = array();
  $libraries = array();
  foreach ($composer_lock['packages'] as $key => $package) {
    if (strpos($package['name'], 'drupal/') === 0 && in_array($package['type'], array('drupal-core', 'drupal-theme', 'drupal-module', 'drupal-profile'))) {
      $project_name = str_replace('drupal/', '', $package['name']);

      switch ($package['type']) {
        case 'drupal-core':
          $project_name = 'drupal';
          $group =& $core;
          $group[$project_name]['type'] = 'core';
          $info['core'] = substr($package['version'], 0, 1) . '.x';
          break;
        case 'drupal-theme':
          $group =& $themes;
          $group[$project_name]['type'] = 'theme';
          break;
        case 'drupal-module':
          $group =& $modules;
          $group[$project_name]['type'] = 'module';
          break;
        case 'drupal-profile':
          $group =& $profiles;
          $group[$project_name]['type'] = 'profile';
          break;
      }

      $group[$project_name]['download']['type'] = 'git';
      $group[$project_name]['download']['url'] = $package['source']['url'];
      // Dev versions should use git branch + revision, otherwise a tag is used.
      if (strstr($package['version'], 'dev')) {
        // 'dev-' prefix indicates a branch-alias. Stripping the dev prefix from
        // the branch name is sufficient.
        // @see https://getcomposer.org/doc/articles/aliases.md
        if (strpos($package['version'], 'dev-') === 0) {
          $group[$project_name]['download']['branch'] = substr($package['version'], 4);
        }
        // Otherwise, leave as is. Version may already use '-dev' suffix.
        else {
          $group[$project_name]['download']['branch'] = $package['version'];
        }
        $group[$project_name]['download']['revision'] = $package['source']['reference'];
      }
      elseif ($package['type'] == 'drupal-core') {
        // For 7.x tags, replace 7.xx.0 with 7.xx.
        if ($info['core'] == '7.x') {
          $group[$project_name]['download']['tag']= substr($package['version'], 0, 4);
        }
        else {
          $group[$project_name]['download']['tag'] = $package['version'];
        }
      }
      else {
        // Make tag versioning drupal-friendly. 8.1.0-alpha1 => 8.x-1.0-alpha1.
        $major_version = substr($package['version'], 0 ,1);
        $the_rest = substr($package['version'], 2, strlen($package['version']));
        $group[$project_name]['download']['tag'] = "$major_version.x-$the_rest";
      }

      if (!empty($package['extra']['patches_applied'])) {
        foreach ($package['extra']['patches_applied'] as $desc => $url) {
          $group[$project_name]['patch'][] = $url;
        }
      }
    }
    // Include any non-drupal libraries that exist in both .lock and .json.
    elseif (!in_array($package['type'], array('composer-plugin', 'metapackage'))
      && array_key_exists($package['name'], $composer_json['require'])) {
      $project_name = $package['name'];
      $libraries[$project_name]['type'] = 'library';
      $libraries[$project_name]['download']['type'] = 'git';
      $libraries[$project_name]['download']['url'] = $package['source']['url'];
      $libraries[$project_name]['download']['branch'] = $package['version'];
      $libraries[$project_name]['download']['revision'] = $package['source']['reference'];
    }
  }

  $info['projects'] = $core + $modules + $themes;
  $info['libraries'] = $libraries;

  return $info;
}

/**
 * Converts a drush info array to a composer.json array.
 *
 * @param array $info
 *   A drush make info array.
 *
 * @return string
 *   A json encoded composer.json schema object.
 */
function drush_make_convert_make_to_composer($info) {
  $core_major_version = substr($info['core'], 0, 1);
  $core_project_name = $core_major_version == 7 ? 'drupal/drupal' : 'drupal/core';

  // Add default projects.
  $projects = array(
    'composer/installers' => '^1.0.20',
    'cweagans/composer-patches' => '~1.0',
    $core_project_name => str_replace('x', '*', $info['core']),
  );

  $patches = array();

  // Iterate over projects, populating composer-friendly array.
  foreach ($info['projects'] as $project_name => $project) {
    switch ($project['type']) {
      case 'core':
        $project['name'] = 'drupal/core';
        $projects[$project['name']] = str_replace('x', '*', $project['version']);
        break;

      default:
        $project['name'] = "drupal/$project_name";
        $projects[$project['name']] = drush_make_convert_project_to_composer($project, $core_major_version);
        break;
    }

    // Add project patches.
    if (!empty($project['patch'])) {
      foreach($project['patch'] as $key => $patch) {
        $patch_description = "Enter {$project['name']} patch #$key description here";
        $patches[$project['name']][$patch_description] = $patch;
      }
    }
  }

  // Iterate over libraries, populating composer-friendly array.
  if (!empty($info['libraries'])) {
    foreach ($info['libraries'] as $library_name => $library) {
      $library_name = 'Verify project name: ' . $library_name;
      $projects[$library_name] = drush_make_convert_project_to_composer($library, $core_major_version);
    }
  }

  $output = array(
    'name' => 'Enter project name here',
    'description' => 'Enter project description here',
    'type' => 'project',
    'repositories' => array(
      array('type' => 'composer', 'url' => 'https://packagist.drupal-composer.org'),
    ),
    'require' => $projects,
    'minimum-stability' => 'dev',
    'prefer-stable' => TRUE,
    'extra' => array(
      'installer-paths' => array(
        'core' => array('type:drupal-core'),
        'docroot/modules/contrib/{$name}' => array('type:drupal-module'),
        'docroot/profiles/contrib/{$name}' => array('type:drupal-profile'),
        'docroot/themes/contrib/{$name}' => array('type:drupal-theme'),
        'drush/contrib/{$name}' => array('type:drupal-drush'),
      ),
      'patches' => $patches,
    ),
  );

  $output = json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

  return $output;
}

/**
 * Converts a make file project array into a composer project version string.
 *
 * @param array $original_project
 *   A project dependency, as defined in a make file.
 *
 * @param string $core_major_version
 *   The major core version. E.g., 6, 7, 8, etc.
 *
 * @return string
 *   The project version, in composer syntax.
 *
 */
function drush_make_convert_project_to_composer($original_project, $core_major_version) {
  // Typical specified version with major version "x" removed.
  if (!empty($original_project['version'])) {
    $version = str_replace('x', '0', $original_project['version']);
  }
  // Git branch or revision.
  elseif (!empty($original_project['download'])) {
    switch ($original_project['download']['type']) {
      case 'git':
        if (!empty($original_project['download']['branch'])) {
          // @todo Determine if '0' will always be correct.
          $version = str_replace('x', '0', $original_project['download']['branch']);
        }
        if (!empty($original_project['download']['tag'])) {
          // @todo Determine if '0' will always be correct.
          $version = str_replace('x', '0', $original_project['download']['tag']);
        }
        if (!empty($project['download']['revision'])) {
          $version .= '#' . $original_project['download']['revision'];
        }
        break;

      default:
        $version = 'Enter correct project name and version number';
        break;
    }
  }

  $version = "$core_major_version." . $version;

  return $version;
}

/**
 * Converts a drush info array to a YAML array.
 *
 * @param array $info
 *   A drush make info array.
 *
 * @return string
 *   A yaml encoded info array.
 */
function drush_make_convert_make_to_yml($info) {
  // Remove incorrect value.
  unset($info['format']);

  // Replace "*" with "~" for project versions.
  foreach ($info['projects'] as $key => $project) {
    if ($project['version'] == '*') {
      $info['projects'][$key]['version'] = '~';
    }
  }

  $dumper = drush_load_engine('outputformat', 'yaml');
  $output = $dumper->format($info, array());

  return $output;
}

/**
 * Drush callback: hidden file to process an individual project.
 *
 * @param string $directory
 *   Directory where the project is being built.
 */
function drush_make_process($directory) {
  drush_get_engine('release_info');

  // Set the temporary directory.
  make_tmp(TRUE, $directory);
  if (!$projects_location = drush_get_option('projects-location')) {
    return drush_set_error('MAKE-PROCESS', dt('No projects passed to drush_make_process'));
  }
  $projects = json_decode(file_get_contents($projects_location), TRUE);
  $manifest = drush_get_option('manifest', array());

  foreach ($projects as $project) {
    if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
      $instance->setManifest($manifest);
      $instance->make();
    }
    else {
      make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project.', array('%type' => $project['type'], '%project' => $project['name'])));
    }
  }
}

/**
 * Gather additional data on all projects specified in the make file.
 */
function make_prepare_projects($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  $release_info = drush_get_engine('release_info');

  // Nothing to make if the project list is empty. Maybe complain about it.
  if (empty($info['projects'])) {
    if (drush_get_option('no-core') || $recursion) {
      return TRUE;
    }
    else {
      return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
    }
  }

  // Obtain translations to download along with the projects.
  $translations = array();
  if (isset($info['translations'])) {
    $translations = $info['translations'];
  }
  if ($arg_translations = drush_get_option('translations', FALSE)) {
    $translations = array_merge(explode(',', $arg_translations), $translations);
  }

  // Normalize projects.
  $projects = array();
  $ignore_checksums = drush_get_option('ignore-checksums');
  foreach ($info['projects'] as $key => $project) {
    // Merge the known data onto the project info.
    $project += array(
      'name'                => $key,
      'type'                => 'module',
      'core'                => $info['core'],
      'translations'        => $translations,
      'build_path'          => $build_path,
      'contrib_destination' => $contrib_destination,
      'version'             => '',
      'location'            => drush_get_option('make-update-default-url', ReleaseInfo::DEFAULT_URL),
      'subdir'              => '',
      'directory_name'      => '',
      'make_directory'      => $make_dir,
      'options'             => array(),
    );
    // MD5 Checksum.
    if ($ignore_checksums) {
      unset($project['download']['md5']);
    }
    elseif (!empty($project['md5'])) {
      $project['download']['md5'] = $project['md5'];
    }

    // If download components are specified, but not the download
    // type, default to git.
    if (isset($project['download']) && !isset($project['download']['type'])) {
      $project['download']['type'] = 'git';
    }
    // Localization server.
    if (!isset($project['l10n_url']) && ($project['location'] == ReleaseInfo::DEFAULT_URL)) {
      $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER;
    }
    // Classify projects in core or contrib.
    if ($project['type'] == 'core') {
      $project['download_type'] = 'core';
    }
    elseif ($project['location'] != ReleaseInfo::DEFAULT_URL || !isset($project['download'])) {
      $request = make_prepare_request($project);
      $is_core = $release_info->checkProject($request, 'core');
      $project['download_type'] = ($is_core ? 'core' : 'contrib');
      $project['type'] = $is_core ? 'core' : $project['type'];
    }
    else {
      $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib');
    }
    $projects[$project['download_type']][$project['name']] = $project;
  }

  // Verify there're enough cores, but not too many.
  $cores = !empty($projects['core']) ? count($projects['core']) : 0;
  if (drush_get_option('no-core')) {
    unset($projects['core']);
  }
  elseif ($cores == 0 && !$recursion) {
    return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  }
  elseif ($cores == 1 && $recursion) {
    unset($projects['core']);
  }
  elseif ($cores > 1) {
    return drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.'));
  }

  // Set download type = pm for suitable projects.
  foreach (array_keys($projects) as $project_type) {
    foreach ($projects[$project_type] as $project) {
      if (make_project_needs_release_info($project)) {
        $request = make_prepare_request($project, $project_type);
        $release = $release_info->selectReleaseBasedOnStrategy($request, '', 'ignore');
        if ($release === FALSE) {
          return FALSE;
        }
        // Override default project type with data from update service.
        if (!isset($info['projects'][$project['name']]['type'])) {
          $project['type'] = $release_info->get($request)->getType();
        }

        if (!isset($project['download'])) {
          $project['download'] = array(
            'type' => 'pm',
            'full_version' => $release['version'],
            'download_link' => $release['download_link'],
            'status url' => $request['status url'],
          );
        }
      }
      $projects[$project_type][$project['name']] = $project;
    }
  }
  if (!$recursion) {
    $projects += array(
      'core' => array(),
      'contrib' => array(),
    );
    drush_set_option('DRUSH_MAKE_PROJECTS', array_merge($projects['core'], $projects['contrib']));
  }
  return $projects;
}

/**
 * Process all projects specified in the make file.
 */
function make_projects($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  $projects = make_prepare_projects($recursion, $info, $contrib_destination, $build_path, $make_dir);
  // Abort if there was an error processing projects.
  if ($projects === FALSE) {
    return FALSE;
  }

  // Core is built in place, rather than using make-process.
  if (!empty($projects['core']) && count($projects['core'])) {
    $project = current($projects['core']);
    $project = DrushMakeProject::getInstance('core', $project);
    $project->make();
  }

  // Process all projects concurrently using make-process.
  if (isset($projects['contrib'])) {
    $concurrency = drush_get_option('concurrency', 1);
    // Generate $concurrency sub-processes to do the actual work.
    $invocations = array();
    $thread = 0;
    foreach ($projects['contrib'] as $project) {
      $thread = ++$thread % $concurrency;
      // Ensure that we've set this sub-process up.
      if (!isset($invocations[$thread])) {
        $invocations[$thread] = array(
          'args' => array(
            make_tmp(),
          ),
          'options' => array(
            'projects' => array(),
          ),
          'site' => array(),
        );
      }
      // Add the project to this sub-process.
      $invocations[$thread]['options']['projects'][] = $project;
      // Add the manifest so recursive downloads do not override projects.
      $invocations[$thread]['options']['manifest'] = array_keys($projects['contrib']);
    }
    if (!empty($invocations)) {
      // Backend options.
      $backend_options = array(
        'concurrency' => $concurrency,
        'method' => 'POST',
      );

      // Store projects in temporary files since passing this much data on the
      // pipe buffer can break on certain systems.
      _make_write_project_json($invocations);

      $common_options = drush_redispatch_get_options();
      // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180.
      $common_options = array_merge($common_options, drush_get_context('stdin'));
      // Package handler should use 'wget'.
      $common_options['package-handler'] = 'wget';

      // Avoid any prompts from CLI.
      $common_options['yes'] = TRUE;

      // Use cache unless explicitly turned off.
      if (!drush_get_option('no-cache', FALSE)) {
        $common_options['cache'] = TRUE;
      }
      // Unless --verbose or --debug are passed, quiter backend output.
      if (empty($common_options['verbose']) && empty($common_options['debug'])) {
        $backend_options['#output-label'] = FALSE;
        $backend_options['integrate'] = TRUE;
      }
      $results = drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none');
      if (count($results['error_log'])) {
        return FALSE;
      }
    }
  }
  return TRUE;
}

/**
 * Writes out project data to temporary files.
 *
 * @param array &$invocations
 *   An array containing projects sorted by thread.
 */
function _make_write_project_json(array &$invocations) {
  foreach ($invocations as $thread => $info) {
    $projects = $info['options']['projects'];
    unset($invocations[$thread]['options']['projects']);
    $temp_file = drush_tempnam('make_projects');
    file_put_contents($temp_file, json_encode($projects));
    $invocations[$thread]['options']['projects-location'] = $temp_file;
  }
}

/**
 * Gather additional data on all libraries specified in the make file.
 */
function make_prepare_libraries($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  // Nothing to make if the libraries list is empty.
  if (empty($info['libraries'])) {
    return;
  }

  $libraries = array();
  $ignore_checksums = drush_get_option('ignore-checksums');
  foreach ($info['libraries'] as $key => $library) {
    if (!is_string($key) || !is_array($library)) {
      // TODO Print a prettier message.
      continue;
    }
    // Merge the known data onto the library info.
    $library += array(
      'name'                => $key,
      'core'                => $info['core'],
      'build_path'          => $build_path,
      'contrib_destination' => $contrib_destination,
      'subdir'              => '',
      'directory_name'      => $key,
      'make_directory'      => $make_dir,
    );
    if ($ignore_checksums) {
      unset($library['download']['md5']);
    }
    $libraries[$key] = $library;
  }
  if (!$recursion) {
    drush_set_option('DRUSH_MAKE_LIBRARIES', $info['libraries']);
  }
  return $libraries;
}

/**
 * Process all libraries specified in the make file.
 */
function make_libraries($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  $libraries = make_prepare_libraries($recursion, $info, $contrib_destination, $build_path, $make_dir);
  if (empty($libraries)) {
    return;
  }
  foreach ($libraries as $key => $library) {
    $class = DrushMakeProject::getInstance('library', $library);
    $class->make();
  }
}

/**
 * The path where the final build will be placed.
 */
function make_build_path($build_path) {
  static $saved_path;
  if (isset($saved_path)) {
    return $saved_path;
  }

  // Determine the base of the build.
  if (drush_get_option('tar')) {
    $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz';
  }
  elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) {
    $build_path = rtrim($build_path, '/');
  }
  // Allow tests to run without a specified base path.
  elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) {
    $build_path = '.';
  }
  else {
    return drush_user_abort(dt('Build aborted.'));
  }
  if ($build_path != '.' && file_exists($build_path) && !drush_get_option('no-core', FALSE)) {
    return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists.', array('%path' => $build_path)));
  }
  $saved_path = $build_path;
  return $build_path;
}

/**
 * Move the completed build into place.
 */
function make_move_build($build_path) {
  $tmp_path = make_tmp();
  $ret = TRUE;
  if ($build_path == '.' || (drush_get_option('no-core', FALSE) && file_exists($build_path))) {
    $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
    foreach ($info as $file) {
      $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename;
      if (file_exists($destination)) {
        // To prevent the removal of top-level directories such as 'modules' or
        // 'themes', descend in a level if the file exists.
        // TODO: This only protects one level of directories from being removed.
        $overwrite = drush_get_option('overwrite', FALSE) ? FILE_EXISTS_OVERWRITE : FILE_EXISTS_MERGE;
        if (is_dir($destination)) {
          $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE);
          foreach ($files as $file) {
            $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, $overwrite);
          }
        }
        else {
          $ret = $ret && drush_copy_dir($file->filename, $destination, $overwrite);
        }
      }
      else {
        $ret = $ret && drush_copy_dir($file->filename, $destination);
      }
    }
  }
  else {
    drush_mkdir(dirname($build_path));
    $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE);
    $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path);
  }

  // Copying to final destination resets write permissions. Re-apply.
  if (drush_get_option('prepare-install')) {
    $default = $build_path . '/sites/default';
    chmod($default . '/settings.php', 0666);
    chmod($default . '/files', 0777);
  }

  if (!$ret) {
    return drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place."));
  }
  return $ret;
}

/**
 * Create a request array suitable for release_info engine.
 *
 * This is a convenience function to easily integrate drush_make
 * with drush release_info engine.
 *
 * @todo: refactor 'make' to internally work with release_info keys.
 *
 * @param array $project
 *   Project array.
 * @param string $type
 *   'contrib' or 'core'.
 */
function make_prepare_request($project, $type = 'contrib') {
  $request = array(
    'name' => $project['name'],
    'drupal_version' => $project['core'],
    'status url' => $project['location'],
  );
  if ($project['version'] != '') {
    $request['project_version'] = $project['version'];
    $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version'];
  }
  return $request;
}

/**
 * Determine if the release information is required for this
 * project. When it is determined that it is, this potentially results
 * in the use of pm-download to process the project.
 *
 * If the location of the project is not customized (uses d.o), and
 * one of the following is true, then release information is required:
 *
 * - $project['type'] has not been specified
 * - $project['download'] has not been specified
 *
 * @see make_projects()
 */
function make_project_needs_release_info($project) {
  return isset($project['location'])
    // Only fetch release info if the project type is unknown OR if
    // download attributes are unspecified.
    && (!isset($project['type']) || !isset($project['download']));
}

/**
 * Enables caching if not explicitly disabled.
 *
 * @return bool
 *   The previous value of the 'cache' option.
 */
function _make_enable_cache() {
  $cache_before = drush_get_option('cache');
  if (!drush_get_option('no-cache', FALSE)) {
    drush_set_option('cache', TRUE);
  }
  return $cache_before;
}
<?php
/**
 * @file
 * Drush Make processing classes.
 */

use Drush\Log\LogLevel;

/**
 * The base project class.
 */
class DrushMakeProject {

  /**
   * TRUE if make() has been called, otherwise FALSE.
   */
  protected $made = FALSE;

  /**
   * TRUE if download() method has been called successfully, otherwise FALSE.
   */
  protected $downloaded = NULL;

  /**
   * Download location to use.
   */
  protected $download_location = NULL;

  /**
   * Keep track of instances.
   *
   * @see DrushMakeProject::getInstance()
   */
  protected static $self = array();

  /**
   * Keeps track of projects being processed to prevent recursive conflicts.
   *
   * Simple array of machine names.
   *
   * @var array
   */
  protected $manifest = array();

  /**
   * Default to overwrite to allow recursive builds to process properly.
   *
   * TODO refactor this to be more selective. Ideally a merge would take place
   * instead of an overwrite.
   */
  protected $overwrite = TRUE;

  /**
   * Recursively process any makefiles found in downloaded projects.
   */
  protected $do_recursion = TRUE;

  /**
   * Which variant of profiles to download.
   */
  protected $variant = 'profile-only';

  /**
   * Set attributes and retrieve project information.
   */
  protected function __construct($project) {
    $project['base_contrib_destination'] = $project['contrib_destination'];
    foreach ($project as $key => $value) {
      $this->{$key} = $value;
    }
    if (!empty($this->options['working-copy'])) {
      $this->download['working-copy'] = TRUE;
    }
    // Don't recurse when we're using a pre-built profile tarball.
    if ($this->variant == 'projects') {
      $this->do_recursion = FALSE;
    }
  }

  /**
   * Get an instance for the type and project.
   *
   * @param string $type
   *   Type of project: core, library, module, profile, or translation.
   * @param array $project
   *   Project information.
   *
   * @return mixed
   *   An instance for the project or FALSE if invalid type.
   */
  public static function getInstance($type, $project) {
    if (!isset(self::$self[$type][$project['name']])) {
      $class = 'DrushMakeProject_' . $type;
      self::$self[$type][$project['name']] = class_exists($class) ? new $class($project) : FALSE;
    }
    return self::$self[$type][$project['name']];
  }

  /**
   * Set the manifest array.
   *
   * @param array $manifest
   *   An array of projects as generated by `make_projects`.
   */
  public function setManifest($manifest) {
    $this->manifest = $manifest;
  }

  /**
   * Download a project.
   */
  function download() {
    $this->downloaded = TRUE;

    // In some cases, make_download_factory() is going to need to know the
    // full version string of the project we're trying to download. However,
    // the version is a project-level attribute, not a download-level
    // attribute. So, if we don't already have a full version string in the
    // download array (e.g. if it was initialized via the release history XML
    // for the PM case), we take the version info from the project-level
    // attribute, convert it into a full version string, and stuff it into
    // $this->download so that the download backend has access to it, too.
    if (!empty($this->version) && empty($this->download['full_version'])) {
      $full_version = '';
      $matches = array();
      // Core needs different conversion rules than contrib.
      if (!empty($this->type) && $this->type == 'core') {
        // Generally, the version for core is already set properly.
        $full_version = $this->version;
        // However, it might just be something like '7' or '7.x', in which
        // case we need to turn that into '7.x-dev';
        if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) {
          // If there's no '.x' already, append it.
          if (empty($matches[1])) {
            $full_version .= '.x';
          }
          $full_version .= '-dev';
        }
      }
      // Contrib.
      else {
        // If the version doesn't already define a core version, prepend it.
        if (!preg_match('/^\d+\.x-\d+.*$/', $this->version)) {
          // Just find the major version from $this->core so we don't end up
          // with version strings like '7.12-2.0'.
          $core_parts = explode('.', $this->core);
          $full_version = $core_parts[0] . '.x-';
        }
        $full_version .= $this->version;
        // If the project-level version attribute is just a number it's a major
        // version.
        if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) {
          // If there's no '.x' already, append it.
          if (empty($matches[1])) {
            $full_version .= '.x';
          }
          $full_version .= '-dev';
        }
      }
      $this->download['full_version'] = $full_version;
    }

    $this->download['variant'] = $this->variant;

    if (make_download_factory($this->name, $this->type, $this->download, $this->download_location) === FALSE) {
      $this->downloaded = FALSE;
    }
    return $this->downloaded;
  }

  /**
   * Build a project.
   */
  function make() {
    if ($this->made) {
      drush_log(dt('Attempt to build project @project more then once prevented.', array('@project' => $this->name)));
      return TRUE;
    }
    $this->made = TRUE;

    if (!isset($this->download_location)) {
      $this->download_location = $this->findDownloadLocation();
    }
    if ($this->download() === FALSE) {
      return FALSE;
    }
    if (!$this->addLockfile($this->download_location)) {
      return FALSE;
    }
    if (!$this->applyPatches($this->download_location)) {
      return FALSE;
    }
    if (!$this->getTranslations($this->download_location)) {
      return FALSE;
    }
    // Handle .info file re-writing (if so desired).
    if (!drush_get_option('no-gitinfofile', FALSE) && isset($this->download['type']) && $this->download['type'] == 'git') {
      $this->processGitInfoFiles();
    }
    // Clean-up .git directories.
    if (!_get_working_copy_option($this->download)) {
      $this->removeGitDirectory();
    }
    if (!$this->recurse($this->download_location)) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Determine the location to download project to.
   */
  function findDownloadLocation() {
    $this->path = $this->generatePath();
    $this->project_directory = !empty($this->directory_name) ? $this->directory_name : $this->name;
    $this->download_location = $this->path . '/' . $this->project_directory;
    // This directory shouldn't exist yet -- if it does, stop,
    // unless overwrite has been set to TRUE.
    if (is_dir($this->download_location) && !$this->overwrite) {
      return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location)));
    }
    elseif ($this->download['type'] === 'pm') {
      // pm-download will create the final contrib directory.
      drush_mkdir(dirname($this->download_location));
    }
    else {
      drush_mkdir($this->download_location);
    }
    return $this->download_location;
  }

  /**
   * Rewrite relative URLs and file:/// URLs
   *
   * relative path -> absolute path using the make_directory
   * local file:/// urls -> local paths
   *
   * @param mixed &$info
   *   Either an array or a simple url string. The `$info` variable will be
   *   transformed into an array.
   */
  protected function preprocessLocalFileUrl(&$info) {
    if (is_string($info)) {
      $info = array('url' => $info, 'local' => FALSE);
    }

    if (!_drush_is_url($info['url']) && !drush_is_absolute_path($info['url'])) {
      $info['url'] = $this->make_directory . '/' . $info['url'];
      $info['local'] = TRUE;
    } elseif (substr($info['url'], 0, 8) == 'file:///') {
      $info['url'] = substr($info['url'], 7);
      $info['local'] = TRUE;
    }
  }

  /**
   * Retrieve and apply any patches specified by the makefile to this project.
   */
  function applyPatches($project_directory) {
    if (empty($this->patch)) {
      return TRUE;
    }

    $patches_txt = '';
    $local_patches = array();
    $ignore_checksums = drush_get_option('ignore-checksums');
    foreach ($this->patch as $info) {
      $this->preprocessLocalFileUrl($info);

      // Download the patch.
      if ($filename = _make_download_file($info['url'])) {
        $patched = FALSE;
        $output = '';
        // Test each patch style; -p1 is the default with git. See
        // http://drupal.org/node/1054616
        $patch_levels = array('-p1', '-p0');
        foreach ($patch_levels as $patch_level) {
          $checked = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply --check %s %s --verbose', $patch_level, $filename);
          if ($checked) {
            // Apply the first successful style.
            $patched = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply %s %s --verbose', $patch_level, $filename);
            break;
          }
        }

        // In some rare cases, git will fail to apply a patch, fallback to using
        // the 'patch' command.
        if (!$patched) {
          foreach ($patch_levels as $patch_level) {
            // --no-backup-if-mismatch here is a hack that fixes some
            // differences between how patch works on windows and unix.
            if ($patched = drush_shell_exec("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $project_directory, $filename)) {
              break;
            }
          }
        }

        if ($output = drush_shell_exec_output()) {
          // Log any command output, visible only in --verbose or --debug mode.
          drush_log(implode("\n", $output));
        }

        // Set up string placeholders to pass to dt().
        $dt_args = array(
          '@name' => $this->name,
          '@filename' => basename($filename),
        );

        if ($patched) {
          if (!$ignore_checksums && !_make_verify_checksums($info, $filename)) {
             return FALSE;
          }
          $patch_url = $info['url'];

          // If this is a local patch, copy that into place as well.
          if ($info['local']) {
            $local_patches[] = $info['url'];
            // Use a local path for the PATCHES.txt file.
            $pathinfo = pathinfo($patch_url);
            $patch_url = $pathinfo['basename'];
          }
          $patches_txt .= '- ' . $patch_url . "\n";

          drush_log(dt('@name patched with @filename.', $dt_args), LogLevel::OK);
        }
        else {
          make_error('PATCH_ERROR', dt("Unable to patch @name with @filename.", $dt_args));
        }
        drush_op('unlink', $filename);
      }
      else {
        make_error('DOWNLOAD_ERROR', 'Unable to download ' . $info['url'] . '.');
        return FALSE;
      }
    }
    if (!empty($patches_txt) && !drush_get_option('no-patch-txt') && !file_exists($project_directory . '/PATCHES.txt')) {
      $patches_txt = "The following patches have been applied to this project:\n" .
        $patches_txt .
        "\nThis file was automatically generated by Drush Make (http://drupal.org/project/drush).\n";
      file_put_contents($project_directory . '/PATCHES.txt', $patches_txt);
      drush_log('Generated PATCHES.txt file for ' . $this->name, LogLevel::OK);

      // Copy local patches into place.
      foreach ($local_patches as $url) {
        $pathinfo = pathinfo($url);
        drush_copy_dir($url, $project_directory . '/' . $pathinfo['basename']);
      }
    }
    return TRUE;
  }

  /**
   * Process info files when downloading things from git.
   */
  function processGitInfoFiles() {
    // Bail out if this isn't hosted on Drupal.org (unless --force-gitinfofile option was specified).
    if (!drush_get_option('force-gitinfofile', FALSE) && isset($this->download['url']) && strpos($this->download['url'], 'drupal.org') === FALSE) {
      return;
    }

    // Figure out the proper version string to use based on the .make file.
    // Best case is the .make file author told us directly.
    if (!empty($this->download['full_version'])) {
      $full_version = $this->download['full_version'];
    }
    // Next best is if we have a tag, since those are identical to versions.
    elseif (!empty($this->download['tag'])) {
      $full_version = $this->download['tag'];
    }
    // If we have a branch, append '-dev'.
    elseif (!empty($this->download['branch'])) {
      $full_version = $this->download['branch'] . '-dev';
    }
    // Ugh. Not sure what else we can do in this case.
    elseif (!empty($this->download['revision'])) {
      $full_version = $this->download['revision'];
    }
    // Probably can never reach this case.
    else {
      $full_version = 'unknown';
    }

    // If the version string ends in '.x-dev' do the Git magic to figure out
    // the appropriate 'rebuild version' string, e.g. '7.x-1.2+7-dev'.
    $matches = array();
    if (preg_match('/^(.+).x-dev$/', $full_version, $matches)) {
      require_once dirname(__FILE__) . '/../pm/package_handler/git_drupalorg.inc';
      $rebuild_version = drush_pm_git_drupalorg_compute_rebuild_version($this->download_location, $matches[1]);
      if ($rebuild_version) {
        $full_version = $rebuild_version;
      }
    }
    require_once dirname(__FILE__) . '/../pm/pm.drush.inc';
    if (drush_shell_cd_and_exec($this->download_location, 'git log -1 --pretty=format:%ct')) {
      $output = drush_shell_exec_output();
      $datestamp = $output[0];
    }
    else {
      $datestamp = time();
    }
    drush_pm_inject_info_file_metadata($this->download_location, $this->name, $full_version, $datestamp);
  }

  /**
   * Remove the .git directory from a project.
   */
  function removeGitDirectory() {
    if (isset($this->download['type']) && $this->download['type'] == 'git' && file_exists($this->download_location . '/.git')) {
      drush_delete_dir($this->download_location . '/.git', TRUE);
    }
  }

  /**
   * Add a lock file.
   */
  function addLockfile($project_directory) {
    if (!empty($this->lock)) {
      file_put_contents($project_directory . '/.drush-lock-update', $this->lock);
    }
    return TRUE;
  }

  /**
   * Retrieve translations for this project.
   */
  function getTranslations($project_directory) {
    static $cache = array();
    $langcodes = $this->translations;
    if ($langcodes && in_array($this->type, array('core', 'module', 'profile', 'theme'), TRUE)) {
      // Support the l10n_path, l10n_url keys from l10n_update. Note that the
      // l10n_server key is not supported.
      if (isset($this->l10n_path)) {
        $update_url = $this->l10n_path;
      }
      else {
        if (isset($this->l10n_url)) {
          $l10n_server = $this->l10n_url;
        }
        else {
          $l10n_server = FALSE;
        }
        if ($l10n_server) {
          if (!isset($cache[$l10n_server])) {
            $this->preprocessLocalFileUrl($l10n_server);
            $l10n_server = $l10n_server['url'];
            if ($filename = _make_download_file($l10n_server)) {
              $server_info = simplexml_load_string(file_get_contents($filename));
              $cache[$l10n_server] = !empty($server_info->update_url) ? $server_info->update_url : FALSE;
            }
          }
          if ($cache[$l10n_server]) {
            $update_url = $cache[$l10n_server];
          }
          else {
            make_error('XML_ERROR', dt("Could not retrieve l10n update url for !project.", array('!project' => $this->name)));
            return FALSE;
          }
        }
      }
      if ($update_url) {
        $failed = array();
        foreach ($langcodes as $langcode) {
          $variables = array(
            '%project' => $this->name,
            '%release' => $this->download['full_version'],
            '%core' => $this->core,
            '%language' => $langcode,
            '%filename' => '%filename',
          );
          $url = strtr($update_url, $variables);

          // Download the translation file.  Since its contents are volatile,
          // cache for only 4 hours.
          if ($filename = _make_download_file($url, 3600 * 4)) {
            // If this is the core project type, download the translation file
            // and place it in every profile and an additional copy in
            // modules/system/translations where it can be detected for import
            // by other non-default install profiles.
            if ($this->type === 'core') {
              $profiles = drush_scan_directory($project_directory . '/profiles', '/.*/', array(), 0, FALSE, 'filename', 0, TRUE);
              foreach ($profiles as $profile) {
                if (is_dir($project_directory . '/profiles/' . $profile->basename)) {
                  drush_mkdir($project_directory . '/profiles/' . $profile->basename . '/translations');
                  drush_copy_dir($filename, $project_directory . '/profiles/' . $profile->basename . '/translations/' . $langcode . '.po');
                }
              }
              drush_mkdir($project_directory . '/modules/system/translations');
              drush_copy_dir($filename, $project_directory . '/modules/system/translations/' . $langcode . '.po');
            }
            else {
              drush_mkdir($project_directory . '/translations');
              drush_copy_dir($filename, $project_directory . '/translations/' . $langcode . '.po', FILE_EXISTS_OVERWRITE);
            }
          }
          else {
            $failed[] = $langcode;
          }
        }
        if (empty($failed)) {
          drush_log('All translations downloaded for ' . $this->name, LogLevel::OK);
        }
        else {
          drush_log('Unable to download translations for ' . $this->name . ': ' . implode(', ', $failed), LogLevel::WARNING);
        }
      }
    }
    return TRUE;
  }

  /**
   * Generate the proper path for this project type.
   *
   * @param boolean $base
   *   Whether include the base part (tmp dir). Defaults to TRUE.
   */
  protected function generatePath($base = TRUE) {
    $path = array();
    if ($base) {
      $path[] = make_tmp();
      $path[] = '__build__';
    }
    if (!empty($this->contrib_destination)) {
      $path[] = $this->contrib_destination;
    }
    if (!empty($this->subdir)) {
      $path[] = $this->subdir;
    }
    return implode('/', $path);
  }

  /**
   * Return the proper path for dependencies to be placed in.
   *
   * @return string
   *   The path that dependencies will be placed in.
   */
  protected function buildPath($directory) {
    return $this->base_contrib_destination;
  }

  /**
   * Recurse to process additional makefiles that may be found during
   * processing.
   */
  function recurse($path) {
    if (!$this->do_recursion || drush_get_option('no-recursion')) {
      drush_log(dt("Preventing recursive makefile parsing for !project",
                array("!project" => $this->name)), LogLevel::NOTICE);
      return TRUE;
    }
    $candidates = array(
      $this->name . '.make.yml',
      $this->name . '.make',
      'drupal-org.make.yml',
      'drupal-org.make',
    );
    $makefile = FALSE;
    foreach ($candidates as $filename) {
      if (file_exists($this->download_location . '/' . $filename)) {
        $makefile = $this->download_location . '/' . $filename;
        break;
      }
    }

    if (!$makefile) {
      return TRUE;
    }

    drush_log(dt("Found makefile: !makefile", array("!makefile" => basename($makefile))), LogLevel::OK);

    // Save the original state of the 'custom' context.
    $custom_context = &drush_get_context('custom');
    $original_custom_context_values = $custom_context;

    $info = make_parse_info_file($makefile, TRUE, $this->options);
    if (!($info = make_validate_info_file($info))) {
      $result = FALSE;
    }
    else {
      // Inherit the translations specified in the extender makefile.
      if (!empty($this->translations)) {
        $info['translations'] = $this->translations;
      }
      // Strip out any modules that have already been processed before this.
      foreach ($this->manifest as $name) {
        unset($info['projects'][$name]);
      }
      $build_path = $this->buildPath($this->name);
      make_projects(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location);
      make_libraries(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location);
      $result = TRUE;
    }
    // Restore original 'custom' context so that any
    // settings changes made are used.
    $custom_context = $original_custom_context_values;

    return $result;
  }
}

/**
 * For processing Drupal core projects.
 */
class DrushMakeProject_Core extends DrushMakeProject {
  /**
   * Override constructor for core to adjust project info.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    // subdir and contrib_destination are not allowed for core.
    $this->subdir = '';
    $this->contrib_destination = '';
  }

  /**
   * Determine the location to download project to.
   */
  function findDownloadLocation() {
    $this->path = $this->download_location = $this->generatePath();
    $this->project_directory = '';
    if (is_dir($this->download_location)) {
      return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location)));
    }
    elseif ($this->download['type'] === 'pm') {
      // pm-download will create the final __build__ directory, so nothing to do
      // here.
    }
    else {
      drush_mkdir($this->download_location);
    }
    return $this->download_location;
  }
}

/**
 * For processing libraries.
 */
class DrushMakeProject_Library extends DrushMakeProject {
  /**
   * Override constructor for libraries to properly set contrib destination.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    // Allow libraries to specify where they should live in the build path.
    if (isset($project['destination'])) {
      $project_path = $project['destination'];
    }
    else {
      $project_path = 'libraries';
    }

    $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . $project_path;
  }

  /**
   * No recursion for libraries, sorry :-(
   */
  function recurse($path) {
    // Return TRUE so that processing continues in the make() method.
    return TRUE;
  }

  /**
   * No translations for libraries.
   */
  function getTranslations($download_location) {
    // Return TRUE so that processing continues in the make() method.
    return TRUE;
  }
}

/**
 * For processing modules.
 */
class DrushMakeProject_Module extends DrushMakeProject {
  /**
   * Override constructor for modules to properly set contrib destination.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'modules';
  }
}

/**
 * For processing installation profiles.
 */
class DrushMakeProject_Profile extends DrushMakeProject {
  /**
   * Override contructor for installation profiles to properly set contrib
   * destination.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    $this->contrib_destination = (!empty($this->destination) ? $this->destination : 'profiles');
  }

  /**
   * Find the build path.
   */
  protected function buildPath($directory) {
    return $this->generatePath(FALSE) . '/' . $directory;
  }
}

/**
 * For processing themes.
 */
class DrushMakeProject_Theme extends DrushMakeProject {
  /**
   * Override contructor for themes to properly set contrib destination.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'themes';
  }
}

/**
 * For processing translations.
 */
class DrushMakeProject_Translation extends DrushMakeProject {
  /**
   * Override constructor for translations to properly set contrib destination.
   */
  protected function __construct(&$project) {
    parent::__construct($project);
    switch ($project['core']) {
      case '5.x':
        // Don't think there's an automatic place we can put 5.x translations,
        // so we'll toss them in a translations directory in the Drupal root.
        $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'translations';
        break;

      default:
        $this->contrib_destination = '';
        break;
    }
  }
}
<?php
/**
 * @file
 * General utility functions for Drush Make.
 */

use Drush\Log\LogLevel;
use Drush\Make\Parser\ParserIni;
use Drush\Make\Parser\ParserYaml;

/**
 * Helper function to parse a makefile and prune projects.
 */
function make_parse_info_file($makefile) {
  $info = _make_parse_info_file($makefile);

  // Support making just a portion of a make file.
  $include_only = array(
    'projects' => array_filter(drush_get_option_list('projects')),
    'libraries' => array_filter(drush_get_option_list('libraries')),
  );
  $info = make_prune_info_file($info, $include_only);

  if ($info === FALSE || ($info = make_validate_info_file($info)) === FALSE) {
    return FALSE;
  }

  return $info;
}

/**
 * Parse makefile recursively.
 */
function _make_parse_info_file($makefile, $element = 'includes') {
  if (!($data = make_get_data($makefile))) {
    return drush_set_error('MAKE_INVALID_MAKE_FILE', dt('Invalid or empty make file: !makefile', array('!makefile' => $makefile)));
  }

  // $info['format'] will specify the determined format.
  $info = _make_determine_format($data);

  // Set any allowed options.
  if (!empty($info['options'])) {
    foreach ($info['options'] as $key => $value) {
      if (_make_is_override_allowed($key)) {
        // n.b. 'custom' context has lower priority than 'cli', so
        // options entered on the command line will "mask" makefile options.
        drush_set_option($key, $value, 'custom');
      }
    }
  }

  // Include any makefiles specified on the command line.
  if ($include_makefiles = drush_get_option_list('includes', FALSE)) {
    drush_unset_option('includes'); // Avoid infinite loop.
    $info['includes'] = is_array($info['includes']) ? $info['includes'] : array();
    foreach ($include_makefiles as $include_make) {
      if (!array_search($include_make, $info['includes'])) {
        $info['includes'][] = $include_make;
      }
    }
  }

  // Override elements with values from makefiles specified on the command line.
  if ($overrides = drush_get_option_list('overrides', FALSE)) {
    drush_unset_option('overrides'); // Avoid infinite loop.
    $info['overrides'] = is_array($info['overrides']) ? $info['overrides'] : array();
    foreach ($overrides as $override) {
      if (!array_search($override, $info['overrides'])) {
        $info['overrides'][] = $override;
      }
    }
  }

  $info = _make_merge_includes_recursively($info, $makefile);
  $info = _make_merge_includes_recursively($info, $makefile, 'overrides');

  return $info;
}

/**
 * Helper function to merge includes recursively.
 */
function _make_merge_includes_recursively($info, $makefile, $element = 'includes') {
  if (!empty($info[$element])) {
    if (is_array($info[$element])) {
      $includes = array();
      foreach ($info[$element] as $key => $include) {
        if (!empty($include)) {
          if (!$include_makefile = _make_get_include_path($include, $makefile)) {
            return make_error('BUILD_ERROR', dt("Cannot determine include file location: !include", array('!include' => $include)));
          }

          if ($element == 'overrides') {
            $info = array_replace_recursive($info, _make_parse_info_file($include_makefile, $element));
          }
          else {
            $info = array_replace_recursive(_make_parse_info_file($include_makefile), $info);
          }
          unset($info[$element][$key]);
          // Move core back to the top of the list, where
          // make_generate_from_makefile() expects it.
          if (!empty($info['projects'])) {
            array_reverse($info['projects']);
          }
        }
      }
    }
  }
  // Ensure $info['projects'] is an associative array, so that we can merge
  // includes properly.
  make_normalize_info($info);

  return $info;
}

/**
 * Helper function to determine the proper path for an include makefile.
 */
function _make_get_include_path($include, $makefile) {
  if (is_array($include) && $include['download']['type'] = 'git') {
    $tmp_dir = make_tmp();
    make_download_git($include['makefile'], $include['download']['type'], $include['download'], $tmp_dir);
    $include_makefile = $tmp_dir . '/' . $include['makefile'];
  }
  elseif (is_string($include)) {
    $include_path = dirname($makefile);
    if (make_valid_url($include, TRUE)) {
      $include_makefile = $include;
    }
    elseif (file_exists($include_path . '/' . $include)) {
      $include_makefile = $include_path . '/' . $include;
    }
    elseif (file_exists($include)) {
      $include_makefile = $include;
    }
    else {
      return make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $include)));
    }
  }
  else {
    return FALSE;
  }
  return $include_makefile;
}

/**
 * Expand shorthand elements, so that we have an associative array.
 */
function make_normalize_info(&$info) {
  if (isset($info['projects'])) {
    foreach($info['projects'] as $key => $project) {
      if (is_numeric($key) && is_string($project)) {
        unset($info['projects'][$key]);
        $info['projects'][$project] = array(
          'version' => '',
        );
      }
      if (is_string($key) && is_numeric($project)) {
        $info['projects'][$key] = array(
          'version' => $project,
        );
      }
    }
  }
}

/**
 * Remove entries in the info file in accordance with the options passed in.
 * Entries are either explicitly 'allowed' (with the $include_only parameter) in
 * which case all *other* entries will be excluded.
 *
 * @param array $info
 *   A parsed info file.
 *
 * @param array $include_only
 *   (Optional) Array keyed by entry type (e.g. 'libraries') against an array of
 *   allowed keys for that type. The special value '*' means 'all entries of
 *   this type'. If this parameter is omitted, no entries will be excluded.
 *
 * @return array
 *   The $info array, pruned if necessary.
 */
function make_prune_info_file($info, $include_only = array()) {

  // We may get passed FALSE in some cases.
  // Also we cannot prune an empty array, so no point in this code running!
  if (empty($info)) {
    return $info;
  }

  // We will accrue an explanation of our activities here.
  $msg = array();
  $msg['scope'] = dt("Drush make restricted to the following entries:");

  $pruned = FALSE;

  if (count(array_filter($include_only))) {
    $pruned = TRUE;
    foreach ($include_only as $type => $keys) {

      if (!isset($info[$type])) {
        continue;
      }
      // For translating
      // dt("Projects");
      // dt("Libraries");
      $type_title = dt(ucfirst($type));

      // Handle the special '*' value.
      if (in_array('*', $keys)) {
        $msg[$type] = dt("!entry_type: <All>", array('!entry_type' => $type_title));
      }

      // Handle a (possibly empty) array of keys to include/exclude.
      else {
        $info[$type] = array_intersect_key($info[$type], array_fill_keys($keys, 1));
        unset($msg[$type]);
        if (!empty($info[$type])) {
          $msg[$type] = dt("!entry_type: !make_entries", array('!entry_type' => $type_title, '!make_entries' => implode(', ', array_keys($info[$type]))));
        }
      }
    }
  }

  if ($pruned) {
    // Make it clear to the user what's going on.
    drush_log(implode("\n", $msg), LogLevel::OK);

    // Throw an error if these restrictions reduced the make to nothing.
    if (empty($info['projects']) && empty($info['libraries'])) {
      // This error mentions the options explicitly to make it as clear as
      // possible to the user why this error has occurred.
      make_error('BUILD_ERROR', dt("All projects and libraries have been excluded. Review the 'projects' and 'libraries' options."));
    }
  }

  return $info;
}

/**
 * Validate the make file.
 */
function make_validate_info_file($info) {
  // Assume no errors to start.
  $errors = FALSE;

  if (empty($info['core'])) {
    make_error('BUILD_ERROR', dt("The 'core' attribute is required"));
    $errors = TRUE;
  }
  // Standardize on core.
  elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) {
    // An exact version of core has been specified, so pass that to an
    // internal variable for storage.
    if (isset($matches[4])) {
      $info['core_release'] = $info['core'];
    }
    // Format the core attribute consistently.
    $info['core'] = $matches[1] . '.x';
  }
  else {
    make_error('BUILD_ERROR', dt("The 'core' attribute !core has an incorrect format.", array('!core' => $info['core'])));
    $errors = TRUE;
  }

  if (!isset($info['api'])) {
    $info['api'] = MAKE_API;
    drush_log(dt("You need to specify an API version of two in your makefile:\napi = !api", array("!api" => MAKE_API)), LogLevel::WARNING);
  }
  elseif ($info['api'] != MAKE_API) {
    make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make."));
    $errors = TRUE;
  }

  $names = array();

  // Process projects.
  if (isset($info['projects'])) {
    if (!is_array($info['projects'])) {
      make_error('BUILD_ERROR', dt("'projects' attribute must be an array."));
      $errors = TRUE;
    }
    else {
      // Filter out entries that have been forcibly removed via [foo] = FALSE.
      $info['projects'] = array_filter($info['projects']);

      foreach ($info['projects'] as $project => $project_data) {
        // Project has an attributes array.
        if (is_string($project) && is_array($project_data)) {
          if (in_array($project, $names)) {
            make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
            $errors = TRUE;
          }
          $names[] = $project;
          foreach ($project_data as $attribute => $value) {
            // Prevent malicious attempts to access other areas of the
            // filesystem.
            if (in_array($attribute, array('subdir', 'directory_name', 'contrib_destination')) && !make_safe_path($value)) {
              $args = array(
                '!path' => $value,
                '!attribute' => $attribute,
                '!project' => $project,
              );
              make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in project !project.", $args));
              $errors = TRUE;
            }
          }
        }
        // Cover if there is no project info, it's just a project name.
        elseif (is_numeric($project) && is_string($project_data)) {
          if (in_array($project_data, $names)) {
            make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project_data)));
            $errors = TRUE;
          }
          $names[] = $project_data;
          unset($info['projects'][$project]);
          $info['projects'][$project_data] = array();
        }
        // Convert shorthand project version style to array format.
        elseif (is_string($project_data)) {
          if (in_array($project, $names)) {
            make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
            $errors = TRUE;
          }
          $names[] = $project;
          $info['projects'][$project] = array('version' => $project_data);
        }
        else {
          make_error('BUILD_ERROR', dt('Project !project incorrectly specified.', array('!project' => $project)));
          $errors = TRUE;
        }
      }
    }
  }
  if (isset($info['libraries'])) {
    if (!is_array($info['libraries'])) {
      make_error('BUILD_ERROR', dt("'libraries' attribute must be an array."));
      $errors = TRUE;
    }
    else {
      // Filter out entries that have been forcibly removed via [foo] = FALSE.
      $info['libraries'] = array_filter($info['libraries']);

      foreach ($info['libraries'] as $library => $library_data) {
        if (is_array($library_data)) {
          foreach ($library_data as $attribute => $value) {
            // Unset disallowed attributes.
            if (in_array($attribute, array('contrib_destination'))) {
              unset($info['libraries'][$library][$attribute]);
            }
            // Prevent malicious attempts to access other areas of the
            // filesystem.
            elseif (in_array($attribute, array('contrib_destination', 'directory_name')) && !make_safe_path($value)) {
              $args = array(
                '!path' => $value,
                '!attribute' => $attribute,
                '!library' => $library,
              );
              make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in library !library.", $args));
              $errors = TRUE;
            }
          }
        }
      }
    }
  }

  // Convert shorthand project/library download style to array format.
  foreach (array('projects', 'libraries') as $type) {
    if (isset($info[$type]) && is_array($info[$type])) {
      foreach ($info[$type] as $name => $item) {
        if (!empty($item['download']) && is_string($item['download'])) {
          $info[$type][$name]['download'] = array('url' => $item['download']);
        }
      }
    }
  }

  // Apply defaults after projects[] array has been expanded, but prior to
  // external validation.
  make_apply_defaults($info);

  foreach (drush_command_implements('make_validate_info') as $module) {
    $function = $module . '_make_validate_info';
    $return = $function($info);
    if ($return) {
      $info = $return;
    }
    else {
      $errors = TRUE;
    }
  }

  if ($errors) {
    return FALSE;
  }
  return $info;
}

/**
 * Verify the syntax of the given URL.
 *
 * Copied verbatim from includes/common.inc
 *
 * @see valid_url
 */
function make_valid_url($url, $absolute = FALSE) {
  if ($absolute) {
    return (bool) preg_match("
      /^                                                      # Start at the beginning of the text
      (?:ftp|https?):\/\/                                     # Look for ftp, http, or https schemes
      (?:                                                     # Userinfo (optional) which is typically
        (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*      # a username or a username and password
        (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@          # combination
      )?
      (?:
        (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                        # A domain name or a IPv4 address
        |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])         # or a well formed IPv6 address
      )
      (?::[0-9]+)?                                            # Server port number (optional)
      (?:[\/|\?]
        (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})   # The path and query (optional)
      *)?
    $/xi", $url);
  }
  else {
    return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
  }
}

/**
 * Find, and possibly create, a temporary directory.
 *
 * @param boolean $set
 *   Must be TRUE to create a directory.
 * @param string $directory
 *   Pass in a directory to use. This is required if using any
 *   concurrent operations.
 *
 * @todo Merge with drush_tempdir().
 */
function make_tmp($set = TRUE, $directory = NULL) {
  static $tmp_dir;

  if (isset($directory) && !isset($tmp_dir)) {
    $tmp_dir = $directory;
  }

  if (!isset($tmp_dir) && $set) {
    $tmp_dir = drush_find_tmp();
    if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) {
      $tmp_dir .= 'make_tmp_' . time() . '_' . uniqid();
    }
    else {
      $tmp_dir .= '/make_tmp_' . time() . '_' . uniqid();
    }
    if (!drush_get_option('no-clean', FALSE)) {
      drush_register_file_for_deletion($tmp_dir);
    }
    if (file_exists($tmp_dir)) {
      return make_tmp(TRUE);
    }
    // Create the directory.
    drush_mkdir($tmp_dir);
  }
  return $tmp_dir;
}

/**
 * Removes the temporary build directory. On failed builds, this is handled by
 * drush_register_file_for_deletion().
 */
function make_clean_tmp() {
  if (!($tmp_dir = make_tmp(FALSE))) {
    return;
  }
  if (!drush_get_option('no-clean', FALSE)) {
    drush_delete_dir($tmp_dir);
  }
  else {
    drush_log(dt('Temporary directory: !dir', array('!dir' => $tmp_dir)), LogLevel::OK);
  }
}

/**
 * Prepare a Drupal installation, copying default.settings.php to settings.php.
 */
function make_prepare_install($build_path) {
  $default = make_tmp() . '/__build__/sites/default';
  drush_copy_dir($default . DIRECTORY_SEPARATOR . 'default.settings.php', $default . DIRECTORY_SEPARATOR . 'settings.php', FILE_EXISTS_OVERWRITE);
  drush_mkdir($default . '/files');
  chmod($default . DIRECTORY_SEPARATOR . 'settings.php', 0666);
  chmod($default . DIRECTORY_SEPARATOR . 'files', 0777);
}

/**
 * Calculate a cksum on each file in the build, and md5 the resulting hashes.
 */
function make_md5() {
  return drush_dir_md5(make_tmp());
}

/**
 * @todo drush_archive_dump() also makes a tar. Consolidate?
 */
function make_tar($build_path) {
  $tmp_path = make_tmp();

  drush_mkdir(dirname($build_path));
  $filename = basename($build_path);
  $dirname = basename($build_path, '.tar.gz');
  // Move the build directory to a more human-friendly name, so that tar will
  // use it instead.
  drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . $dirname, TRUE);
  // Only move the tar file to it's final location if it's been built
  // successfully.
  if (drush_shell_exec("%s -C %s -Pczf %s %s", drush_get_tar_executable(), $tmp_path, $tmp_path . '/' . $filename, $dirname)) {
    drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $filename, $build_path, TRUE);
  };
  // Move the build directory back to it's original location for consistency.
  drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $dirname, $tmp_path . DIRECTORY_SEPARATOR . '__build__');
}

/**
 * Logs an error unless the --force-complete command line option is specified.
 */
function make_error($error_code, $message) {
  if (drush_get_option('force-complete')) {
    drush_log("$error_code: $message -- build forced", LogLevel::WARNING);
  }
  else {
    return drush_set_error($error_code, $message);
  }
}

/**
 * Checks an attribute's path to ensure it's not maliciously crafted.
 *
 * @param string $path
 *   The path to check.
 */
function make_safe_path($path) {
  return !preg_match("+^/|^\.\.|/\.\./+", $path);
}
/**
 * Get data based on the source.
 *
 * This is a helper function to abstract the retrieval of data, so that it can
 * come from files, STDIN, etc.  Currently supports filepath and STDIN.
 *
 * @param string $data_source
 *   The path to a file, or '-' for STDIN.
 *
 * @return string
 *   The raw data as a string.
 */
function make_get_data($data_source) {
  if ($data_source == '-') {
    // See http://drupal.org/node/499758 before changing this.
    $stdin = fopen('php://stdin', 'r');
    $data = '';
    $has_input = FALSE;

    while ($line = fgets($stdin)) {
      $has_input = TRUE;
      $data .= $line;
    }

    if ($has_input) {
      return $data;
    }
    return FALSE;
  }
  // Local file.
  elseif (!strpos($data_source, '://')) {
    $data = file_get_contents($data_source);
  }
  // Remote file.
  else {
    $file = _make_download_file($data_source);
    $data = file_get_contents($file);
    drush_op('unlink', $file);
  }
  return $data;
}

/**
 * Apply any defaults.
 *
 * @param array &$info
 *   A parsed make array.
 */
function make_apply_defaults(&$info) {
  if (isset($info['defaults'])) {
    $defaults = $info['defaults'];

    foreach ($defaults as $type => $default_data) {
      if (isset($info[$type])) {
        foreach ($info[$type] as $project => $data) {
          $info[$type][$project] = _drush_array_overlay_recursive($default_data, $info[$type][$project]);
        }
      }
      else {
        drush_log(dt("Unknown attribute '@type' in defaults array", array('@type' => $type)), LogLevel::WARNING);
      }
    }
  }
}

/**
 * Check if makefile overrides are allowed
 *
 * @param array $option
 *   The option to check.
 */
function _make_is_override_allowed ($option) {
  $allow_override = drush_get_option('allow-override', 'all');

  if ($allow_override == 'all') {
    $allow_override = array();
  }
  elseif (!is_array($allow_override)) {
    $allow_override = _convert_csv_to_array($allow_override);
  }

  if ((empty($allow_override)) || ((in_array($option, $allow_override)) && (!in_array('none', $allow_override)))) {
    return TRUE;
  }
  drush_log(dt("'!option' not allowed; use --allow-override=!option or --allow-override=all to permit", array("!option" => $option)), LogLevel::WARNING);
  return FALSE;
}

/**
 * Gather any working copy options.
 *
 * @param array $download
 *   The download array.
 */
function _get_working_copy_option($download) {
  $wc = '';

  if (_make_is_override_allowed('working-copy') && isset ($download['working-copy'])) {
    $wc = $download['working-copy'];
  }
  else {
    $wc = drush_get_option('working-copy');
  }
  return $wc;
}

/**
 * Given data from stdin, determine format.
 *
 * @return array|bool
 *   Returns parsed data if it matches any known format.
 */
function _make_determine_format($data) {
  // Most .make files will have a `core` attribute. Use this to determine
  // the format.
  if (preg_match('/^\s*core:/m', $data)) {
    $parsed = ParserYaml::parse($data);
    $parsed['format'] = 'yaml';
    return $parsed;
  }
  elseif (preg_match('/^\s*core\s*=/m', $data)) {
    $parsed = ParserIni::parse($data);
    $parsed['format'] = 'ini';
    return $parsed;
  }

  // If the .make file did not have a core attribute, it is being included
  // by another .make file. Test YAML first to avoid segmentation faults from
  // preg_match in INI parser.
  $yaml_parse_exception = FALSE;
  try {
    if ($parsed = ParserYaml::parse($data)) {
      $parsed['format'] = 'yaml';
      return $parsed;
    }
  }
  catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
    // Note that an exception was thrown, and display after .ini parsing.
    $yaml_parse_exception = $e;
  }

  // Try INI format.
  if ($parsed = ParserIni::parse($data)) {
    $parsed['format'] = 'ini';
    return $parsed;
  }

  if ($yaml_parse_exception) {
    throw $e;
  }

  return drush_set_error('MAKE_STDIN_ERROR', dt('Unknown make file format'));
}
<?php

/**
 * @file
 * make-update command implementation.
 */

/**
 * Command callback for make-update.
 */
function drush_make_update($makefile = NULL) {
  // Process makefile and get projects array.
  $info = _make_parse_info_file($makefile);

  make_prepare_projects(FALSE, $info);
  $make_projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE);

  // Pick projects coming from drupal.org and adjust its structure
  // to feed update_status engine.
  // We provide here some heuristics to determine if a git clone comes
  // from drupal.org and also guess its version.
  // #TODO# move git checks to make_prepare_projects() and use it to leverage
  // git_drupalorg engine.
  $projects = array();
  foreach ($make_projects as $project_name => $project) {
    if (($project['download']['type'] == 'git') && !empty($project['download']['url'])) {
      // TODO check that tag or branch are valid version strings (with pm_parse_version()).
      if (!empty($project['download']['tag'])) {
        $version = $project['download']['tag'];
      }
      elseif (!empty($project['download']['branch'])) {
        $version = $project['download']['branch'] . '-dev';
      }
      /*
      elseif (!empty($project['download']['refspec'])) {
        #TODO# Parse refspec.
      }
      */
      else {
        // If no tag or branch, we can't match a d.o version.
        continue;
      }
      $projects[$project_name] = $project + array(
        'path'    => '',
        'label'   => $project_name,
        'version' => $version,
      );
    }
    elseif ($project['download']['type'] == 'pm') {
      $projects[$project_name] = $project + array(
        'path'  => '',
        'label' => $project_name,
      );
    }
  }

  // Check for updates.
  $update_status = drush_get_engine('update_status');
  $update_info = $update_status->getStatus($projects, TRUE);

  $security_only = drush_get_option('security-only', FALSE);
  foreach ($update_info as $project_name => $project_update_info) {
    if (!$security_only || ($security_only && $project_update_info['status'] == DRUSH_UPDATESTATUS_NOT_SECURE)) {
      $make_projects[$project_name]['download']['full_version'] = $project_update_info['recommended'];
    }
  }

  // Inject back make projects and generate the updated makefile.
  drush_set_option('DRUSH_MAKE_PROJECTS', $make_projects);
  make_generate_from_makefile(drush_get_option('result-file'), $makefile);
}

<?php

/**
 * @file
 * pm-download command implementation.
 */

use Drush\Log\LogLevel;
use Drush\UpdateService\ReleaseInfo;

/**
 * Implements drush_hook_COMMAND_validate().
 */
function drush_pm_download_validate() {
  // Accomodate --select to the values accepted by release_info.
  $select = drush_get_option('select', 'auto');
  if ($select === TRUE) {
    drush_set_option('select', 'always');
  }
  else if ($select === FALSE) {
    drush_set_option('select', 'never');
  }

  // Validate the user specified destination directory.
  $destination = drush_get_option('destination');
  if (!empty($destination)) {
    $destination = rtrim($destination, DIRECTORY_SEPARATOR);
    if (!is_dir($destination)) {
      drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination)));
      if (!drush_get_context('DRUSH_SIMULATE')) {
        if (drush_confirm(dt('Would you like to create it?'))) {
          drush_mkdir($destination, TRUE);
        }
        if (!is_dir($destination)) {
          return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination)));
        }
      }
    }
    if (!is_writable($destination)) {
      return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination)));
    }
    // Ignore --use-site-dir, if given.
    if (drush_get_option('use-site-dir', FALSE)) {
      drush_set_option('use-site-dir', FALSE);
    }
  }

  // Validate --variant or enforce a sane default.
  $variant = drush_get_option('variant', FALSE);
  if ($variant) {
    $variants = array('full', 'projects', 'profile-only');
    if (!in_array($variant, $variants)) {
      return drush_set_error('DRUSH_PM_PROFILE_INVALID_VARIANT', dt('Invalid variant !variant. Valid values: !variants.', array('!variant' => $variant, '!variants' => implode(', ', $variants))));
    }
  }
  // 'full' and 'projects' variants are only valid for wget package handler.
  $package_handler = drush_get_option('package-handler', 'wget');
  if (($package_handler != 'wget') && ($variant != 'profile-only')) {
    $new_variant = 'profile-only';
    if ($variant) {
      drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), LogLevel::WARNING);
    }
  }
  // If we are working on a drupal root, full variant is not an option.
  else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
    if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) {
      $new_variant = 'projects';
    }
    if ($variant == 'full') {
      drush_log(dt('Variant full is not a valid option within a Drupal root.'), LogLevel::WARNING);
    }
  }

  if (isset($new_variant)) {
    drush_set_option('variant', $new_variant);
    if ($variant) {
      drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), LogLevel::OK);
    }
  }
}

/**
 * Command callback. Download Drupal core or any project.
 */
function drush_pm_download() {
  $release_info = drush_get_engine('release_info');

  if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) {
    $requests = array('drupal');
  }

  // Pick cli options.
  $status_url = drush_get_option('source', ReleaseInfo::DEFAULT_URL);
  $restrict_to = drush_get_option('dev', '');
  $select = drush_get_option('select', 'auto');
  $all = drush_get_option('all', FALSE);
  // If we've bootstrapped a Drupal site and the user may have the chance
  // to select from a list of filtered releases, we want to pass
  // the installed project version, if any.
  $projects = array();
  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
    if (!$all and in_array($select, array('auto', 'always'))) {
      $projects = drush_get_projects();
    }
  }

  // Get release history for each request and download the project.
  foreach ($requests as $request) {
    $request = pm_parse_request($request, $status_url, $projects);
    $version = isset($projects[$request['name']]) ? $projects[$request['name']]['version'] : NULL;
    $release = $release_info->selectReleaseBasedOnStrategy($request, $restrict_to, $select, $all, $version);
    if ($release == FALSE) {
      // Stop working on the first failure. Return silently on user abort.
      if (drush_get_context('DRUSH_USER_ABORT', FALSE)) {
        return FALSE;
      }
      // Signal that the command failed for all other problems.
      return drush_set_error('DRUSH_DOWNLOAD_FAILED', dt("Could not download requested project(s)."));
    }
    $request['version'] = $release['version'];

    $project_release_info = $release_info->get($request);
    $request['project_type'] = $project_release_info->getType();

    // Determine the name of the directory that will contain the project.
    // We face here all the assymetries to make it smooth for package handlers.
    // For Drupal core: --drupal-project-rename or drupal-x.y
    if (($request['project_type'] == 'core') ||
        (($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) {
      // Avoid downloading core into existing core.
      if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
        if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) {
          return drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name'])));
        }
      }

      if ($rename = drush_get_option('drupal-project-rename', FALSE)) {
        if ($rename === TRUE) {
          $request['project_dir'] = $request['name'];
        }
        else {
          $request['project_dir'] = $rename;
        }
      }
      else {
        // Set to drupal-x.y, the expected name for .tar.gz contents.
        // Explicitly needed for cvs package handler.
        $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-'));
      }
    }
    // For the other project types we want the project name. Including core
    // variant for profiles.  Note those come with drupal-x.y in the .tar.gz.
    else {
      $request['project_dir'] = $request['name'];
    }

    // Download the project to a temporary location.
    drush_log(dt('Downloading project !name ...', array('!name' => $request['name'])));
    $request['full_project_path'] = package_handler_download_project($request, $release);
    if (!$request['full_project_path']) {
      // Delete the cached update service file since it may be invalid.
      $release_info->clearCached($request);
      drush_log(dt('Error downloading !name', array('!name' => $request['name']), LogLevel::ERROR));
      continue;
    }

    // Determine the install location for the project.  User provided
    // --destination has preference.
    $destination = drush_get_option('destination');
    if (!empty($destination)) {
      if (!file_exists($destination)) {
        drush_mkdir($destination);
      }
      $request['project_install_location'] = realpath($destination);
    }
    else {
      $request['project_install_location'] = _pm_download_destination($request['project_type']);
    }

    // If user did not provide --destination, then call the
    // download-destination-alter hook to give the chance to any commandfiles
    // to adjust the install location or abort it.
    if (empty($destination)) {
      $result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release);
      if (array_search(FALSE, $result, TRUE) !== FALSE) {
        return FALSE;
      }
    }

    // Load version control engine and detect if (the parent directory of) the
    // project install location is under a vcs.
    if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) {
      continue;
    }

    $request['project_install_location'] .= '/' . $request['project_dir'];

    if ($version_control->engine == 'backup') {
      // Check if install location already exists.
      if (is_dir($request['project_install_location'])) {
        if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) {
          drush_delete_dir($request['project_install_location'], TRUE);
        }
        else {
          drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), LogLevel::WARNING);
          continue;
        }
      }
    }
    else {
      // Find and unlink all files but the ones in the vcs control directories.
      $skip_list = array('.', '..');
      $skip_list = array_merge($skip_list, drush_version_control_reserved_files());
      drush_scan_directory($request['project_install_location'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
    }

    // Copy the project to the install location.
    if (drush_op('_drush_recursive_copy', $request['full_project_path'], $request['project_install_location'])) {
      drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), LogLevel::SUCCESS);
      // Adjust full_project_path to the final project location.
      $request['full_project_path'] = $request['project_install_location'];

      // If the version control engine is a proper vcs we also need to remove
      // orphan directories.
      if ($version_control->engine != 'backup') {
        $empty_dirs = drush_find_empty_directories($request['full_project_path'], $version_control->reserved_files());
        foreach ($empty_dirs as $empty_dir) {
          // Some VCS files are read-only on Windows (e.g., .svn/entries).
          drush_delete_dir($empty_dir, TRUE);
        }
      }

      // Post download actions.
      package_handler_post_download($request, $release);
      drush_command_invoke_all('drush_pm_post_download', $request, $release);
      $version_control->post_download($request);

      // Print release notes if --notes option is set.
      if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) {
        $project_release_info->getReleaseNotes($release['version'], FALSE);
      }

      // Inform the user about available modules a/o themes in the downloaded project.
      drush_pm_extensions_in_project($request);
    }
    else {
      // We don't `return` here in order to proceed with downloading additional projects.
      drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])));
    }

    // Notify about this project.
    if (drush_notify_allowed('pm-download')) {
      $msg = dt('Project !project (!version) downloaded to !install.', array(
        '!project' => $name,
        '!version' => $release['version'],
        '!install' => $request['project_install_location'],
      ));
      drush_notify_send(drush_notify_command_message('pm-download', $msg));
    }
  }
}

/**
 * Implementation of hook_drush_pm_download_destination_alter().
 *
 * Built-in download-destination-alter hook. This particular version of
 * the hook will move modules that contain only Drush commands to
 * /usr/share/drush/commands if it exists, or $HOME/.drush if the
 * site-wide location does not exist.
 */
function pm_drush_pm_download_destination_alter(&$request, $release) {
  // A module is a pure Drush command if it has no .info.yml (8+) and contains no
  // .drush.inc files.  Skip this test for Drush itself, though; we do
  // not want to download Drush to the ~/.drush folder.
  if (in_array($request['project_type'], array('module', 'utility')) && ($request['name'] != 'drush')) {
    $drush_command_files = drush_scan_directory($request['full_project_path'], '/.*\.drush.inc/');
    if (!empty($drush_command_files)) {
      $pattern = drush_drupal_major_version() >= 8 ? '/.*\.info/' : '/.*\.module/';
      $module_files = drush_scan_directory($request['full_project_path'], $pattern);
      if (empty($module_files)) {
        $install_dir = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES');
        if (!is_dir($install_dir) || !is_writable($install_dir)) {
          $install_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION');
        }
        // Make the .drush dir if it does not already exist.
        if (!is_dir($install_dir)) {
          drush_mkdir($install_dir, FALSE);
        }
        // Change the location if the mkdir worked.
        if (is_dir($install_dir)) {
          $request['project_install_location'] = $install_dir;
        }
      }
      // We need to clear the Drush commandfile cache so that
      // our newly-downloaded Drush extension commandfiles can be found.
      drush_cache_clear_all();
    }
  }
}

/**
 * Determines a candidate destination directory for a particular site path.
 *
 * Optionally attempts to create the directory.
 *
 * @return String the candidate destination if it exists.
 */
function _pm_download_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) {
  // Profiles in Drupal < 8
  if (($type == 'profile') && (drush_drupal_major_version() < 8)) {
    $destination = 'profiles';
  }
  // Type: module, theme or profile.
  else {
    if ($type == 'theme engine') {
      $destination = 'themes/engines';
    } else {
      $destination = $type . 's';
    }
    // Prefer /contrib if it exists.
    if ($sitepath) {
      $destination = $sitepath . '/' . $destination;
    }
    $contrib = $destination . '/contrib';
    if (is_dir($contrib)) {
      $destination = $contrib;
    }
  }
  if ($create) {
    drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination)));
    drush_mkdir($destination, TRUE);
  }
  if (is_dir($destination)) {
    drush_log(dt('Using destination directory !dir', array('!dir' => $destination)));
    return $destination;
  }
  drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination)));
  return FALSE;
}

/**
 * Returns the best destination for a particular download type we can find.
 *
 * It is based on the project type and drupal and site contexts.
 */
function _pm_download_destination($type) {
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT');
  $full_site_root = (empty($drupal_root) || empty($site_root)) ? '' : $drupal_root .'/'. $site_root;
  $sitewide = empty($drupal_root) ? '' : $drupal_root . '/' . drush_drupal_sitewide_directory();

  $in_site_directory = FALSE;
  // Check if we are running within the site directory.
  if (strpos(realpath(drush_cwd()), realpath($full_site_root)) !== FALSE || (drush_get_option('use-site-dir', FALSE))) {
    $in_site_directory = TRUE;
  }

  $destination = '';
  if ($type != 'core') {
    // Attempt 1: If we are in a specific site directory, and the destination
    // directory already exists, then we use that.
    if (empty($destination) && $site_root && $in_site_directory) {
      $create_dir = drush_get_option('use-site-dir', FALSE);
      $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, $create_dir);
    }
    // Attempt 2: If the destination directory already exists for
    // the sitewide directory, use that.
    if (empty($destination) && $drupal_root) {
      $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide);
    }
    // Attempt 3: If a specific (non default) site directory exists and
    // the sitewide directory does not exist, then create destination
    // in the site specific directory.
    if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sitewide)) {
      $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
    }
    // Attempt 4: If sitewide directory exists, then create destination there.
    if (empty($destination) && is_dir($sitewide)) {
      $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide, TRUE);
    }
    // Attempt 5: If site directory exists (even default), then create
    // destination in that directory.
    if (empty($destination) && $site_root && is_dir($full_site_root)) {
      $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
    }
  }
  // Attempt 6: If we didn't find a valid directory yet (or we somehow found
  // one that doesn't exist) we always fall back to the current directory.
  if (empty($destination) || !is_dir($destination)) {
    $destination = drush_cwd();
  }

  return $destination;
}
<?php

/**
 * @file
 * pm-info command implementation.
 */

use Drush\Log\LogLevel;

/**
 * Command callback. Show detailed info for one or more extensions.
 */
function drush_pm_info() {
  $result = array();
  $args = pm_parse_arguments(func_get_args());

  $extension_info = drush_get_extensions(FALSE);
  _drush_pm_expand_extensions($args, $extension_info);
  // If no extensions are provided, show all.
  if (count($args) == 0) {
    $args = array_keys($extension_info);
  }

  foreach ($args as $extension) {
    if (isset($extension_info[$extension])) {
      $info = $extension_info[$extension];
    }
    else {
      drush_log(dt('!extension was not found.', array('!extension' => $extension)), LogLevel::WARNING);
      continue;
    }
    if (drush_extension_get_type($info) == 'module') {
      $data = _drush_pm_info_module($info);
    }
    else {
      $data = _drush_pm_info_theme($info);
    }
    $result[$extension] = $data;
  }
  return $result;
}

/**
 * Output format formatter-filter callback.
 *
 * @see drush_parse_command()
 * @see drush_outputformat
 */
function _drush_pm_info_format_table_data($data) {
  $result = array();
  foreach ($data as $extension => $info) {
    foreach($info as $key => $value) {
      if (is_array($value)) {
        if (empty($value)) {
          $value = 'none';
        }
        else {
          $value = implode(', ', $value);
        }
      }
      $result[$extension][$key] = $value;
    }
  }
  return $result;
}

/**
 * Return an array with general info of an extension.
 */
function _drush_pm_info_extension($info) {
  $data['extension'] = drush_extension_get_name($info);
  $data['project'] = isset($info->info['project'])?$info->info['project']:dt('Unknown');
  $data['type'] = drush_extension_get_type($info);
  $data['title'] = $info->info['name'];
  $data['config'] = isset($info->info['configure']) ? $info->info['configure'] : dt('None');
  $data['description'] = $info->info['description'];
  $data['version'] = $info->info['version'];
  $data['date'] = isset($info->info['datestamp']) ? format_date($info->info['datestamp'], 'custom', 'Y-m-d') : NULL;
  $data['package'] = $info->info['package'];
  $data['core'] = $info->info['core'];
  $data['php'] = $info->info['php'];
  $data['status'] = drush_get_extension_status($info);
  $data['path'] = drush_extension_get_path($info);

  return $data;
}

/**
 * Return an array with info of a module.
 */
function _drush_pm_info_module($info) {
  $major_version = drush_drupal_major_version();

  $data = _drush_pm_info_extension($info);
  if ($info->schema_version > 0) {
    $schema_version = $info->schema_version;
  }
  elseif ($info->schema_version == -1) {
    $schema_version = "no schema installed";
  }
  else {
    $schema_version = "module has no schema";
  }
  $data['schema_version'] = $schema_version;
  if ($major_version == 7) {
    $data['files'] = $info->info['files'];
  }
  $data['requires'] = $info->info['dependencies'];

  if ($major_version == 6) {
    $requiredby = $info->info['dependents'];
  }
  else {
    $requiredby = array_keys($info->required_by);
  }
  $data['required_by'] = $requiredby;
  if ($info->status == 1) {
    $role = drush_role_get_class();
    $data['permissions'] = $role->getModulePerms(drush_extension_get_name($info));
  }
  return $data;
}

/**
 * Return an array with info of a theme.
 */
function _drush_pm_info_theme($info) {
  $major_version = drush_drupal_major_version();

  $data = _drush_pm_info_extension($info);

  $data['core'] = $info->info['core'];
  $data['php'] = $info->info['php'];
  $data['engine'] = $info->info['engine'];
  $data['base_theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : '';
  $regions = $info->info['regions'];
  $data['regions'] = $regions;
  $features = $info->info['features'];
  $data['features'] = $features;
  if (count($info->info['stylesheets']) > 0) {
    $data['stylesheets'] = '';
    foreach ($info->info['stylesheets'] as $media => $files) {
      $files = array_keys($files);
      $data['media '.$media] = $files;
    }
  }
  if (count($info->info['scripts']) > 0) {
    $scripts = array_keys($info->info['scripts']);
    $data['scripts'] = $scripts;
  }
  return $data;
}
<?php

/**
 * @file
 * Drush PM drupal.org Git extension.
 */

use Drush\Log\LogLevel;

/**
 * Validate this package handler can run.
 */
function package_handler_validate() {
  // Check git command exists. Disable possible output.
  $debug = drush_get_context('DRUSH_DEBUG');
  drush_set_context('DRUSH_DEBUG', FALSE);

  // We need to check for a git executable and then make sure version is >=1.7
  // (avoid drush_shell_exec because we want to run this even in --simulated mode.)
  $success = exec('git --version', $git);
  $git_version_array = explode(" ", $git[0]);
  $git_version = $git_version_array[2];

  drush_set_context('DRUSH_DEBUG', $debug);
  if (!$success) {
    return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.'));
  } elseif ($git_version < '1.7') {
    return drush_set_error('GIT_VERSION_UNSUPPORTED', dt('Your git version !git_version is not supported; please upgrade to git 1.7 or later.', array('!git_version' => $git_version)));
  }
  // Check git_deploy is enabled. Only for bootstrapped sites.
  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
    drush_include_engine('drupal', 'environment');
    if (!drush_get_option('gitinfofile') && !drush_module_exists('git_deploy')) {
      drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), LogLevel::WARNING);
    }
  }

  return TRUE;
}

/**
 * Download a project.
 *
 * @param $request
 *   The project array with name, base and full (final) paths.
 * @param $release
 *   The release details array from drupal.org.
 */
function package_handler_download_project(&$request, $release) {
  if ($username = drush_get_option('gitusername')) {
    // Uses SSH, which enables pushing changes back to git.drupal.org.
    $repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git';
  }
  else {
    $repository = 'git://git.drupal.org/project/' . $request['name'] . '.git';
  }
  $request['repository'] = $repository;
  $tag = $release['tag'];

  // If the --cache option was given, create a new git reference cache of the
  // remote repository, or update the existing cache to fetch recent changes.
  if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) {
    $gitcache = $cachedir . '/git';
    $projectcache = $gitcache . '/' . $request['name'] . '.git';
    drush_mkdir($gitcache);
    // Setup a new cache, if we don't have this project yet.
    if (!file_exists($projectcache)) {
      // --mirror works similar to --bare, but retrieves all tags, local
      // branches, remote branches, and any other refs (notes, stashes, etc).
      // @see http://stackoverflow.com/questions/3959924
      $command = 'git clone --mirror';
      if (drush_get_context('DRUSH_VERBOSE')) {
        $command .= ' --verbose --progress';
      }
      $command .= ' %s %s';
      drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git');
    }
    // If we already have this project, update it to speed up subsequent clones.
    else {
      // A --mirror clone is fully synchronized with `git remote update` instead
      // of `git fetch --all`.
      // @see http://stackoverflow.com/questions/6150188
      drush_shell_cd_and_exec($projectcache, 'git remote update');
    }
    $gitcache = $projectcache;
  }

  // Clone the repo into a temporary path.
  $clone_path = drush_tempdir();

  $command  = 'git clone';
  $command .= ' ' . drush_get_option('gitcloneparams');
  if (drush_get_option('cache')) {
    $command .= ' --reference ' . drush_escapeshellarg($gitcache);
  }
  if (drush_get_context('DRUSH_VERBOSE')) {
    $command .= ' --verbose --progress';
  }
  $command .= ' ' . drush_escapeshellarg($repository);
  $command .= ' ' . drush_escapeshellarg($clone_path);
  if (!drush_shell_exec($command)) {
    return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
  }

  // Check if the 'tag' from the release feed is a tag or a branch.
  // If the tag exists, git will return it
  if (!drush_shell_cd_and_exec($clone_path, 'git tag -l ' . drush_escapeshellarg($tag))) {
    return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
  }
  $output = drush_shell_exec_output();

  if (isset($output[0]) && ($output[0] == $tag)) {
    // If we want a tag, simply checkout it. The checkout will end up in
    // "detached head" state.
    $command  = 'git checkout ' . drush_get_option('gitcheckoutparams');
    $command .= ' ' . drush_escapeshellarg($tag);
    if (!drush_shell_cd_and_exec($clone_path, $command)) {
      return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
    }
  }
  else {
    // Else, we want to checkout a branch.
    // First check if we are not already in the correct branch.
    if (!drush_shell_cd_and_exec($clone_path, 'git symbolic-ref HEAD')) {
      return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
    }
    $output = drush_shell_exec_output();
    $current_branch = preg_replace('@^refs/heads/@', '', $output[0]);

    // If we are not on the correct branch already, switch to the correct one.
    if ($current_branch != $tag) {
      $command  = 'git checkout';
      $command .= ' ' . drush_get_option('gitcheckoutparams');
      $command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag);
      if (!drush_shell_cd_and_exec($clone_path, $command)) {
        return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
      }
    }
  }

  return $clone_path;
}

/**
 * Update a project (so far, only modules are supported).
 *
 * @param $request
 *   The project array with name, base and full (final) paths.
 * @param $release
 *   The release details array from drupal.org.
 */
function package_handler_update_project($request, $release) {
  drush_log('Updating project ' . $request['name'] . ' ...');

  $commands = array();
  if ((!empty($release['version_extra'])) && ($release['version_extra'] == 'dev')) {
    // Update the branch of the development repository.
    $commands[] = 'git pull';
    $commands[] = drush_get_option('gitpullparams');
  }
  else {
    // Use a stable repository.
    $commands[] = 'git fetch';
    $commands[] = drush_get_option('gitfetchparams');
    $commands[] = ';';
    $commands[] = 'git checkout';
    $commands[] = drush_get_option('gitcheckoutparams');
    $commands[] = $release['version'];
  }

  if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) {
    return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.');
  }

  return TRUE;
}

/**
 * Post download action.
 *
 * This action take place once the project is placed in its final location.
 *
 * Here we add the project as a git submodule.
 */
function package_handler_post_download($project, $release) {
  if (drush_get_option('gitsubmodule', FALSE)) {
    // Obtain the superproject path, then add as submodule.
    if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) {
      $output = drush_shell_exec_output();
      $superproject = $output[0];
      // Add the downloaded project as a submodule of its git superproject.
      $command = array();
      $command[] = 'git submodule add';
      $command[] = drush_get_option('gitsubmoduleaddparams');
      $command[] = $project['repository'];
      // We need the submodule relative path.
      $command[] = substr(realpath($project['full_project_path']), strlen(realpath($superproject)) + 1);
      if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) {
        return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject)));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path']))));
    }
  }

  if (drush_get_option('gitinfofile', FALSE)) {
    $matches = array();
    if (preg_match('/^(.+).x-dev$/', $release['version'], $matches)) {
      $full_version = drush_pm_git_drupalorg_compute_rebuild_version($project['full_project_path'], $matches[1]);
    }
    else {
      $full_version = $release['version'];
    }
    if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git log -1 --pretty=format:%ct')) {
      $output = drush_shell_exec_output();
      $datestamp = $output[0];
    }
    else {
      $datestamp = time();
    }
    drush_pm_inject_info_file_metadata($project['full_project_path'], $project['name'], $full_version, $datestamp);
  }

}

/**
 * Helper function to compute the rebulid version string for a project.
 *
 * This does some magic in Git to find the latest release tag along
 * the branch we're packaging from, count the number of commits since
 * then, and use that to construct this fancy alternate version string
 * which is useful for the version-specific dependency support in Drupal
 * 7 and higher.
 *
 * NOTE: A similar function lives in git_deploy and in the drupal.org
 * packaging script (see DrupalorgProjectPackageRelease.class.php inside
 * drupalorg/drupalorg_project/plugins/release_packager). Any changes to the
 * actual logic in here should probably be reflected in the other places.
 *
 * @param string $project_dir
 *   The full path to the root directory of the project to operate on.
 * @param string $branch
 *   The branch that we're using for -dev. This should only include the
 *   core version, the dash, and the branch's major version (eg. '7.x-2').
 *
 * @return string
 *   The full 'rebuild version string' in the given Git checkout.
 */
function drush_pm_git_drupalorg_compute_rebuild_version($project_dir, $branch) {
  $rebuild_version = '';
  $branch_preg = preg_quote($branch);

  if (drush_shell_cd_and_exec($project_dir, 'git describe --tags')) {
    $shell_output = drush_shell_exec_output();
    $last_tag = $shell_output[0];
    // Make sure the tag starts as Drupal formatted (for eg.
    // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not master)
    // then it's on that branch.
    if (preg_match('/^(?<drupalversion>' . $branch_preg . '\.\d+(?:-[^-]+)?)(?<gitextra>-(?<numberofcommits>\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) {
      // If we found additional git metadata (in particular, number of commits)
      // then use that info to build the version string.
      if (isset($matches['gitextra'])) {
        $rebuild_version = $matches['drupalversion'] . '+' . $matches['numberofcommits'] . 'dev';
      }
      // Otherwise, the branch tip is pointing to the same commit as the
      // last tag on the branch, in which case we use the prior tag and
      // add '+0-dev' to indicate we're still on a -dev branch.
      else {
        $rebuild_version = $last_tag . '+0-dev';
      }
    }
  }
  return $rebuild_version;
}
<?php

/**
 * @file
 * Drush PM Wget extension
 */

/**
 * Validate this package handler can run.
 */
function package_handler_validate() {
  // Check wget or curl command exists. Disable possible output.
  $debug = drush_get_context('DRUSH_DEBUG');
  drush_set_context('DRUSH_DEBUG', FALSE);
  $success = drush_shell_exec('wget --version');
  if (!$success) {
    $success = drush_shell_exec('curl --version');
    // Old version of curl shipped in darwin returns error status for --version
    // and --help. Give the chance to use it.
    if (!$success) {
      $success = drush_shell_exec('which curl');
    }
  }
  drush_set_context('DRUSH_DEBUG', $debug);
  if (!$success) {
    return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('wget nor curl executables found.'));
  }

  return TRUE;
}

/**
 * Download a project.
 *
 * @param $request Array with information on the request to download.
 * @param $release The release details array from drupal.org.
 */
function package_handler_download_project(&$request, $release) {
  // Install profiles come in several variants. User may specify which one she wants.
  if ($request['project_type'] == 'profile') {
    $variant = drush_get_option('variant', 'full');
    foreach ($release['files'] as $file) {
      if ($file['variant'] == $variant && $file['archive_type'] == 'tar.gz') {
        $release = array_merge($release, $file);
        break;
      }
    }
  }

  // Add <date> to download link, so it is part of the cache key. Dev snapshots can then be cached forever.
  $download_link = $release['download_link'];
  if (strpos($release['download_link'], '-dev') !== FALSE) {
    $download_link .= '?date=' . $release['date'];
  }
  // Cache for a year by default.
  $cache_duration = (drush_get_option('cache', TRUE)) ? 86400*365 : 0;

  // Prepare download path. On Windows file name cannot contain '?'.
  // See http://drupal.org/node/1782444
  $filename = str_replace('?', '_', basename($download_link));
  $download_path = drush_tempdir() . '/' . $filename;

  // Download the tarball.
  $download_path = drush_download_file($download_link, $download_path, $cache_duration);
  if ($download_path || drush_get_context('DRUSH_SIMULATE')) {
    drush_log(dt('Downloading !filename was successful.', array('!filename' => $filename)));
  }
  else {
    return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Unable to download !project to !path from !url.', array('!project' => $request['name'], '!path' => $download_path, '!url' => $download_link)));
  }

  // Check Md5 hash.
  if (!drush_get_option('no-md5')) {
    if (drush_op('md5_file', $download_path) !== $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) {
      drush_delete_dir(drush_download_file_name($download_link, TRUE));
      return drush_set_error('DRUSH_PM_FILE_CORRUPT', dt('File !filename is corrupt (wrong md5 checksum).', array('!filename' => $filename)));
    }
    else {
      drush_log(dt('Md5 checksum of !filename verified.', array('!filename' => $filename)));
    }
  }

  // Extract the tarball in place and return the full path to the untarred directory.
  $download_base = dirname($download_path);
  if (!$tar_file_list = drush_tarball_extract($download_path, $download_base, TRUE)) {
    // An error has been logged.
    return FALSE;
  }
  $tar_directory = drush_trim_path($tar_file_list[0]);

  return $download_base . '/' . $tar_directory;
}

/**
 * Update a project.
 *
 * @return bool
 *   Success or failure. An error message will be logged.
 */
function package_handler_update_project(&$request, $release) {
  $download_path = package_handler_download_project($request, $release);
  if ($download_path) {
    return drush_move_dir($download_path, $request['full_project_path']);
  }
  else {
    return FALSE;
  }
}

/**
 * Post download action.
 *
 * This action take place once the project is placed in its final location.
 */
function package_handler_post_download($project) {
}
<?php

/**
 * @file
 *  The drush Project Manager
 *
 * Terminology:
 * - Request: a requested project (string or keyed array), with a name and (optionally) version.
 * - Project: a drupal.org project (i.e drupal.org/project/*), such as cck or zen.
 * - Extension: a drupal.org module, theme or profile.
 * - Version: a requested version, such as 1.0 or 1.x-dev.
 * - Release: a specific release of a project, with associated metadata (from the drupal.org update service).
 */

use Drush\Log\LogLevel;

/**
 * @defgroup update_status_constants Update Status Constants
 * @{
 * Represents update status of projects.
 *
 * The first set is a mapping of some constants declared in update.module.
 * We only declare the ones we're interested in.
 * The rest of the constants are used by pm-updatestatus to represent
 * a status when the user asked for updates to specific versions or
 * other circumstances not managed by Drupal.
 */

/**
 * Project is missing security update(s).
 *
 * Maps UPDATE_NOT_SECURE.
 */
const DRUSH_UPDATESTATUS_NOT_SECURE = 1;

/**
 * Current release has been unpublished and is no longer available.
 *
 * Maps UPDATE_REVOKED.
 */
const DRUSH_UPDATESTATUS_REVOKED = 2;

/**
 * Current release is no longer supported by the project maintainer.
 *
 * Maps UPDATE_NOT_SUPPORTED.
 */
const DRUSH_UPDATESTATUS_NOT_SUPPORTED = 3;

/**
 * Project has a new release available, but it is not a security release.
 *
 * Maps UPDATE_NOT_CURRENT.
 */
const DRUSH_UPDATESTATUS_NOT_CURRENT = 4;

/**
 * Project is up to date.
 *
 * Maps UPDATE_CURRENT.
 */
const DRUSH_UPDATESTATUS_CURRENT = 5;

/**
 * Project's status cannot be checked.
 *
 * Maps UPDATE_NOT_CHECKED.
 */
const DRUSH_UPDATESTATUS_NOT_CHECKED = -1;

/**
 * No available update data was found for project.
 *
 * Maps UPDATE_UNKNOWN.
 */
const DRUSH_UPDATESTATUS_UNKNOWN = -2;

/**
 * There was a failure fetching available update data for this project.
 *
 * Maps UPDATE_NOT_FETCHED.
 */
const DRUSH_UPDATESTATUS_NOT_FETCHED = -3;

/**
 * We need to (re)fetch available update data for this project.
 *
 * Maps UPDATE_FETCH_PENDING.
 */
const DRUSH_UPDATESTATUS_FETCH_PENDING = -4;

/**
 * Project was not packaged by drupal.org.
 */
const DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED = 101;

/**
 * Requested project is not updateable.
 */
const DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE = 102;

/**
 * Requested project not found.
 */
const DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND = 103;

/**
 * Requested version not found.
 */
const DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND = 104;

/**
 * Requested version available.
 */
const DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT = 105;

/**
 * Requested version already installed.
 */
const DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT = 106;

/**
 * @} End of "defgroup update_status_constants".
 */

/**
 * Implementation of hook_drush_help().
 */
function pm_drush_help($section) {
  switch ($section) {
    case 'meta:pm:title':
      return dt('Project manager commands');
    case 'meta:pm:summary':
      return dt('Download, enable, examine and update your modules and themes.');
    case 'drush:pm-enable':
      return dt('Enable one or more extensions (modules or themes). Enable dependant extensions as well.');
    case 'drush:pm-disable':
      return dt('Disable one or more extensions (modules or themes). Disable dependant extensions as well.');
    case 'drush:pm-updatecode':
    case 'drush:pm-update':
      $message = dt("Display available update information for Drupal core and all enabled projects and allow updating to latest recommended releases.");
      if ($section == 'drush:pm-update') {
        $message .= ' '.dt("Also apply any database updates required (same as pm-updatecode + updatedb).");
      }
      $message .= ' '.dt("Note: The user is asked to confirm before the actual update. Backups are performed unless directory is already under version control. Updated projects can potentially break your site. It is NOT recommended to update production sites without prior testing.");
      return $message;
    case 'drush:pm-updatecode-postupdate':
      return dt("This is a helper command needed by updatecode. It is used to check for db updates in a backend process after code updated have been performed. We need to run this task in a separate process to not conflict with old code already in memory.");
    case 'drush:pm-download':
      return dt("Download Drupal core or projects from drupal.org (Drupal core, modules, themes or profiles) and other sources. It will automatically figure out which project version you want based on its recommended release, or you may specify a particular version.

If no --destination is provided, then destination depends on the project type:
  - Profiles will be downloaded to profiles/ in your Drupal root.
  - Modules and themes will be downloaded to the site specific directory (sites/example.com/modules|themes) if available, or to the site wide directory otherwise.
  - If you're downloading drupal core or you are not running the command within a bootstrapped drupal site, the default location is the current directory.
  - Drush commands will be relocated to @site_wide_location (if available) or ~/.drush. Relocation is determined once the project is downloaded by examining its content. Note you can provide your own function in a commandfile to determine the relocation of any project.", array('@site_wide_location' => drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES')));
  }
}

/**
 * Implementation of hook_drush_command().
 */
function pm_drush_command() {
  $update_options = array(
    'lock' => array(
      'description' => 'Add a persistent lock to remove the specified projects from consideration during updates.  Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode.  The lock does not affect pm-download.  See also the update_advanced project for similar and improved functionality.',
      'example-value' => 'foo,bar',
    ),
  );
  $update_suboptions = array(
    'lock' => array(
      'lock-message' => array(
        'description' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode.  Optional.',
        'example-value' => 'message',
      ),
      'unlock' => array(
        'description' => 'Remove the persistent lock from the specified projects so that they may be updated again.',
        'example-value' => 'foo,bar',
      ),
    ),
  );

  $items['pm-enable'] = array(
    'description' => 'Enable one or more extensions (modules or themes).',
    'arguments' => array(
      'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.',
    ),
    'options' => array(
      'resolve-dependencies' => 'Attempt to download any missing dependencies. At the moment, only works when the module name is the same as the project name.',
      'skip' => 'Skip automatic downloading of libraries (c.f. devel).',
    ),
    'aliases' => array('en'),
    'engines' => array(
      'release_info' => array(
        'add-options-to-command' => FALSE,
      ),
    ),
  );
  $items['pm-disable'] = array(
    'description' => 'Disable one or more extensions (modules or themes).',
    'arguments' => array(
      'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.',
    ),
    'aliases' => array('dis'),
    'engines' => array(
      'version_control',
      'package_handler',
      'release_info' => array(
        'add-options-to-command' => FALSE,
      ),
    ),
  );
  $items['pm-info'] = array(
    'description' => 'Show detailed info for one or more extensions (modules or themes).',
    'arguments' => array(
      'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches. If no argument is provided it will show info for all available extensions.',
    ),
    'aliases' => array('pmi'),
    'outputformat' => array(
      'default' => 'key-value-list',
      'pipe-format' => 'json',
      'formatted-filter' => '_drush_pm_info_format_table_data',
      'field-labels' => array(
        'extension' => 'Extension',
        'project' => 'Project',
        'type' => 'Type',
        'title' => 'Title',
        'description' => 'Description',
        'version' => 'Version',
        'date' => 'Date',
        'package' => 'Package',
        'core' => 'Core',
        'php' => 'PHP',
        'status' => 'Status',
        'path' => 'Path',
        'schema_version' => 'Schema version',
        'files' => 'Files',
        'requires' => 'Requires',
        'required_by' => 'Required by',
        'permissions' => 'Permissions',
        'config' => 'Configure',
        'engine' => 'Engine',
        'base_theme' => 'Base theme',
        'regions' => 'Regions',
        'features' => 'Features',
        'stylesheets' => 'Stylesheets',
        // 'media_' . $media  => 'Media '. $media for each $info->info['stylesheets'] as $media => $files
        'scripts' => 'Scripts',
      ),
      'output-data-type' => 'format-table',
    ),
  );

  $items['pm-projectinfo'] = array(
    'description' => 'Show a report of available projects and their extensions.',
    'arguments' => array(
      'projects' => 'Optional. A list of installed projects to show.',
    ),
    'options' => array(
      'drush' => 'Optional. Only incude projects that have one or more Drush commands.',
      'status' => array(
        'description' => 'Filter by project status. Choices: enabled, disabled. A project is considered enabled when at least one of its extensions is enabled.',
        'example-value' => 'enabled',
      ),
    ),
    'outputformat' => array(
      'default' => 'key-value-list',
      'pipe-format' => 'json',
      'field-labels' => array(
        'label'      => 'Name',
        'type'       => 'Type',
        'version'    => 'Version',
        'status'     => 'Status',
        'extensions' => 'Extensions',
        'drush'      => 'Drush Commands',
        'datestamp'  => 'Datestamp',
        'path'       => 'Path',
      ),
      'fields-default' => array('label', 'type', 'version', 'status', 'extensions', 'drush', 'datestamp', 'path'),
      'fields-pipe' => array('label'),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('pmpi'),
  );

  // Install command is reserved for the download and enable of projects including dependencies.
  // @see http://drupal.org/node/112692 for more information.
  // $items['install'] = array(
  //     'description' => 'Download and enable one or more modules',
  //   );
  $items['pm-uninstall'] = array(
    'description' => 'Uninstall one or more modules and their dependent modules.',
    'arguments' => array(
      'modules' => 'A list of modules.',
    ),
    'aliases' => array('pmu'),
  );
  $items['pm-list'] = array(
    'description' => 'Show a list of available extensions (modules and themes).',
    'callback arguments' => array(array(), FALSE),
    'options' => array(
      'type' => array(
        'description' => 'Filter by extension type. Choices: module, theme.',
        'example-value' => 'module',
      ),
      'status' => array(
        'description' => 'Filter by extension status. Choices: enabled, disabled and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").',
        'example-value' => 'disabled',
      ),
      'package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").',
      'core' => 'Filter out extensions that are not in drupal core.',
      'no-core' => 'Filter out extensions that are provided by drupal core.',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'list',
      'field-labels' => array('package' => 'Package', 'name' => 'Name', 'type' => 'Type', 'status' => 'Status', 'version' => 'Version'),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('pml'),
  );
  $items['pm-refresh'] = array(
    'description' => 'Refresh update status information.',
    'engines' => array(
      'update_status' => array(
        'add-options-to-command' => FALSE,
      ),
    ),
    'aliases' => array('rf'),
  );
  $items['pm-updatestatus'] = array(
    'description' => 'Show a report of available minor updates to Drupal core and contrib projects.',
    'arguments' => array(
      'projects' => 'Optional. A list of installed projects to show.',
    ),
    'options' => array(
      'pipe' => 'Return a list of the projects with any extensions enabled that need updating, one project per line.',
    ) + $update_options,
    'sub-options' => $update_suboptions,
    'engines' => array(
      'update_status',
    ),
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'list',
      'field-labels' => array('name' => 'Short Name', 'label' => 'Name', 'existing_version' => 'Installed Version', 'status' => 'Status', 'status_msg' => 'Message', 'candidate_version' => 'Proposed version'),
      'fields-default' => array('label', 'existing_version', 'candidate_version', 'status_msg' ),
      'fields-pipe' => array('name', 'existing_version', 'candidate_version', 'status_msg'),
      'output-data-type' => 'format-table',
    ),
    'aliases' => array('ups'),
  );
  $items['pm-updatecode'] = array(
    'description' => 'Update Drupal core and contrib projects to latest recommended releases.',
    'examples' => array(
      'drush pm-updatecode --no-core' => 'Update contrib projects, but skip core.',
      'drush pm-updatestatus --format=csv --list-separator=" " --fields="name,existing_version,candidate_version,status_msg"' => 'To show a list of projects with their update status, use pm-updatestatus instead of pm-updatecode.',
    ),
    'arguments' => array(
      'projects' => 'Optional. A list of installed projects to update.',
    ),
    'options' => array(
      'notes' => 'Show release notes for each project to be updated.',
      'no-core' => 'Only update modules and skip the core update.',
      'check-updatedb' => 'Check to see if an updatedb is needed after updating the code. Default is on; use --check-updatedb=0 to disable.',
    ) + $update_options,
    'sub-options' => $update_suboptions,
    'aliases' => array('upc'),
    'topics' => array('docs-policy'),
    'engines' => array(
      'version_control',
      'package_handler',
      'release_info' => array(
        'add-options-to-command' => FALSE,
      ),
      'update_status',
    ),
  );
  // Merge all items from above.
  $items['pm-update'] = array(
    'description' => 'Update Drupal core and contrib projects and apply any pending database updates (Same as pm-updatecode + updatedb).',
    'aliases' => array('up'),
    'allow-additional-options' => array('pm-updatecode', 'updatedb'),
  );
  $items['pm-updatecode-postupdate'] = array(
    'description' => 'Notify of pending db updates.',
    'hidden' => TRUE,
  );
  $items['pm-releasenotes'] = array(
    'description' => 'Print release notes for given projects.',
    'arguments' => array(
      'projects' => 'A list of project names, with optional version. Defaults to \'drupal\'',
    ),
    'options' => array(
      'html' => dt('Display release notes in HTML rather than plain text.'),
    ),
    'examples' => array(
      'drush rln cck' => 'Prints the release notes for the recommended version of CCK project.',
      'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.',
      'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.',
    ),
    'aliases' => array('rln'),
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'engines' => array(
      'release_info',
    ),
  );
  $items['pm-releases'] = array(
    'description' => 'Print release information for given projects.',
    'arguments' => array(
      'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'',
    ),
    'examples' => array(
      'drush pm-releases cck zen' => 'View releases for cck and Zen projects for your Drupal version.',
    ),
    'options' => array(
      'default-major' => 'Show releases compatible with the specified major version of Drupal.',
    ),
    'aliases' => array('rl'),
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'outputformat' => array(
      'default' => 'table',
      'pipe-format' => 'csv',
      'field-labels' => array(
        'project' => 'Project',
        'version' => 'Release',
        'date' => 'Date',
        'status' => 'Status',
        'release_link' => 'Release link',
        'download_link' => 'Download link',
      ),
      'fields-default' => array('project', 'version', 'date', 'status'),
      'fields-pipe' => array('project', 'version', 'date', 'status'),
      'output-data-type' => 'format-table',
    ),
    'engines' => array(
      'release_info',
    ),
  );
  $items['pm-download'] = array(
    'description' => 'Download projects from drupal.org or other sources.',
    'examples' => array(
      'drush dl drupal' => 'Download latest recommended release of Drupal core.',
      'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.',
      'drush dl drupal-6' => 'Download latest recommended release of Drupal 6.x.',
      'drush dl cck zen' => 'Download latest versions of CCK and Zen projects.',
      'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.',
      'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.',
      'drush dl views --select' => 'Show a list of recent releases of the views project, prompt for which one to download.',
      'drush dl webform --dev' => 'Download the latest dev release of webform.',
      'drush dl webform --cache' => 'Download webform. Fetch and populate the download cache as needed.',
    ),
    'arguments' => array(
      'projects' => 'A comma delimited list of drupal.org project names, with optional version. Defaults to \'drupal\'',
    ),
    'options' => array(
      'destination' => array(
        'description' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).',
        'example-value' => 'path',
      ),
      'use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.',
      'notes' => 'Show release notes after each project is downloaded.',
      'variant' => array(
        'description' => "Only useful for install profiles. Possible values: 'full', 'projects', 'profile-only'.",
        'example-value' => 'full',
      ),
      'select' => "Select the version to download interactively from a list of available releases.",
      'drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. Defaults to "drupal".',
      'default-major' => array(
        'description' => 'Specify the default major version of modules to download when there is no bootstrapped Drupal site.  Defaults to "8".',
        'example-value' => '7',
      ),
      'skip' => 'Skip automatic downloading of libraries (c.f. devel).',
      'pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.',
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_MAX,
    'aliases' => array('dl'),
    'engines' => array(
      'version_control',
      'package_handler',
      'release_info',
    ),
  );
  return $items;
}

/**
 * @defgroup extensions Extensions management.
 * @{
 * Functions to manage extensions.
 */

/**
 * Command argument complete callback.
 */
function pm_pm_enable_complete() {
  return pm_complete_extensions();
}

/**
 * Command argument complete callback.
 */
function pm_pm_disable_complete() {
  return pm_complete_extensions();
}

/**
 * Command argument complete callback.
 */
function pm_pm_uninstall_complete() {
  return pm_complete_extensions();
}

/**
 * Command argument complete callback.
 */
function pm_pm_info_complete() {
  return pm_complete_extensions();
}

/**
 * Command argument complete callback.
 */
function pm_pm_releasenotes_complete() {
  return pm_complete_projects();
}

/**
 * Command argument complete callback.
 */
function pm_pm_releases_complete() {
  return pm_complete_projects();
}

/**
 * Command argument complete callback.
 */
function pm_pm_updatecode_complete() {
  return pm_complete_projects();
}

/**
 * Command argument complete callback.
 */
function pm_pm_update_complete() {
  return pm_complete_projects();
}

/**
 * List extensions for completion.
 *
 * @return
 *  Array of available extensions.
 */
function pm_complete_extensions() {
  if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    $extension_info = drush_get_extensions(FALSE);
    return array('values' => array_keys($extension_info));
  }
}

/**
 * List projects for completion.
 *
 * @return
 *  Array of installed projects.
 */
function pm_complete_projects() {
  if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
    return array('values' => array_keys(drush_get_projects()));
  }
}

/**
 * Sort callback function for sorting extensions.
 *
 * It will sort first by type, second by package and third by name.
 */
function _drush_pm_sort_extensions($a, $b) {
  $a_type = drush_extension_get_type($a);
  $b_type = drush_extension_get_type($b);
  if ($a_type == 'module' && $b_type == 'theme') {
    return -1;
  }
  if ($a_type == 'theme' && $b_type == 'module') {
    return 1;
  }
  $cmp = strcasecmp($a->info['package'], $b->info['package']);
  if ($cmp == 0) {
    $cmp = strcasecmp($a->info['name'], $b->info['name']);
  }
  return $cmp;
}

/**
 * Calculate an extension status based on current status and schema version.
 *
 * @param $extension
 *   Object of a single extension info.
 *
 * @return
 *   String describing extension status. Values: enabled|disabled|not installed
 */
function drush_get_extension_status($extension) {
  if ((drush_extension_get_type($extension) == 'module') && ($extension->schema_version == -1)) {
    $status = "not installed";
  }
  else {
    $status = ($extension->status == 1)?'enabled':'disabled';
  }

  return $status;
}

/**
 * Classify extensions as modules, themes or unknown.
 *
 * @param $extensions
 *   Array of extension names, by reference.
 * @param $modules
 *   Empty array to be filled with modules in the provided extension list.
 * @param $themes
 *   Empty array to be filled with themes in the provided extension list.
 */
function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) {
  _drush_pm_expand_extensions($extensions, $extension_info);
  foreach ($extensions as $extension) {
    if (!isset($extension_info[$extension])) {
      continue;
    }
    $type = drush_extension_get_type($extension_info[$extension]);
    if ($type == 'module') {
      $modules[$extension] = $extension;
    }
    else if ($type == 'theme') {
      $themes[$extension] = $extension;
    }
  }
}

/**
 * Obtain an array of installed projects off the extensions available.
 *
 * A project is considered to be 'enabled' when any of its extensions is
 * enabled.
 * If any extension lacks project information and it is found that the
 * extension was obtained from drupal.org's cvs or git repositories, a new
 * 'vcs' attribute will be set on the extension. Example:
 *   $extensions[name]->vcs = 'cvs';
 *
 * @param array $extensions
 *   Array of extensions as returned by drush_get_extensions().
 *
 * @return
 *   Array of installed projects with info of version, status and provided
 * extensions.
 */
function drush_get_projects(&$extensions = NULL) {
  if (!isset($extensions)) {
    $extensions = drush_get_extensions();
  }
  $projects = array(
    'drupal' => array(
      'label'      => 'Drupal',
      'version'    => drush_drupal_version(),
      'type'       => 'core',
      'extensions' => array(),
    )
  );
  if (isset($extensions['system']->info['datestamp'])) {
    $projects['drupal']['datestamp'] = $extensions['system']->info['datestamp'];
  }
  foreach ($extensions as $extension) {
    $extension_name = drush_extension_get_name($extension);
    $extension_path = drush_extension_get_path($extension);

    // Obtain the project name. It is not available in this cases:
    //   1. the extension is part of drupal core.
    //   2. the project was checked out from CVS/git and cvs_deploy/git_deploy
    //      is not installed.
    //   3. it is not a project hosted in drupal.org.
    if (empty($extension->info['project'])) {
      if (isset($extension->info['version']) && ($extension->info['version'] == drush_drupal_version())) {
        $project = 'drupal';
      }
      else {
        if (is_dir($extension_path . '/CVS') && (!drush_module_exists('cvs_deploy'))) {
          $extension->vcs = 'cvs';
          drush_log(dt('Extension !extension is fetched from cvs. Ignoring.', array('!extension' => $extension_name)), LogLevel::DEBUG);
        }
        elseif (is_dir($extension_path . '/.git') && (!drush_module_exists('git_deploy'))) {
          $extension->vcs = 'git';
          drush_log(dt('Extension !extension is fetched from git. Ignoring.', array('!extension' => $extension_name)), LogLevel::DEBUG);
        }
        continue;
      }
    }
    else {
      $project = $extension->info['project'];
    }

    // Create/update the project in $projects with the project data.
    if (!isset($projects[$project])) {
      $projects[$project] = array(
        // If there's an extension with matching name, pick its label.
        // Otherwise use just the project name. We avoid $extension->label
        // for the project label because the extension's label may have
        // no direct relation with the project name. For example,
        // "Text (text)" or "Number (number)" for the CCK project.
        'label'      => isset($extensions[$project]) ? $extensions[$project]->label : $project,
        'type'       => drush_extension_get_type($extension),
        'version'    => $extension->info['version'],
        'status'     => $extension->status,
        'extensions' => array(),
      );
      if (isset($extension->info['datestamp'])) {
        $projects[$project]['datestamp'] = $extension->info['datestamp'];
      }
      if (isset($extension->info['project status url'])) {
        $projects[$project]['status url'] = $extension->info['project status url'];
      }
    }
    else {
      // If any of the extensions is enabled, consider the project is enabled.
      if ($extension->status != 0) {
        $projects[$project]['status'] = $extension->status;
      }
    }
    $projects[$project]['extensions'][] = drush_extension_get_name($extension);
  }

  // Obtain each project's path and try to provide a better label for ones
  // with machine name.
  $reserved = array('modules', 'sites', 'themes');
  foreach ($projects as $name => $project) {
    if ($name == 'drupal') {
      continue;
    }

    // If this project has no human label, see if we can find
    // one "main" extension whose label we could use.
    if ($project['label'] == $name)  {
      // If there is only one extension, construct a label based on
      // the extension name.
      if (count($project['extensions']) == 1) {
        $extension = $extensions[$project['extensions'][0]];
        $projects[$name]['label'] = $extension->info['name'] .  ' (' . $name . ')';
      }
      else {
        // Make a list of all of the extensions in this project
        // that do not depend on any other extension in this
        // project.
        $candidates = array();
        foreach ($project['extensions'] as $e) {
          $has_project_dependency = FALSE;
          if (isset($extensions[$e]->info['dependencies']) && is_array($extensions[$e]->info['dependencies'])) {
            foreach ($extensions[$e]->info['dependencies'] as $dependent) {
              if (in_array($dependent, $project['extensions'])) {
                $has_project_dependency = TRUE;
              }
            }
          }
          if ($has_project_dependency === FALSE) {
            $candidates[] = $extensions[$e]->info['name'];
          }
        }
        // If only one of the modules is a candidate, use its name in the label
        if (count($candidates) == 1) {
          $projects[$name]['label'] = reset($candidates) .  ' (' . $name . ')';
        }
      }
    }

    drush_log(dt('Obtaining !project project path.', array('!project' => $name)), LogLevel::DEBUG);
    $path = _drush_pm_find_common_path($project['type'], $project['extensions']);
    // Prevent from setting a reserved path. For example it may happen in a case
    // where a module and a theme are declared as part of a same project.
    // There's a special case, a project called "sites", this is the reason for
    // the second condition here.
    if ($path == '.' || (in_array(basename($path), $reserved) && !in_array($name, $reserved))) {
      drush_log(dt('Error while trying to find the common path for enabled extensions of project !project. Extensions are: !extensions.', array('!project' => $name, '!extensions' => implode(', ', $project['extensions']))), LogLevel::ERROR);
    }
    else {
      $projects[$name]['path'] = $path;
    }
  }

  return $projects;
}

/**
 * Helper function to find the common path for a list of extensions in the aim to obtain the project name.
 *
 * @param $project_type
 *  Type of project we're trying to find. Valid values: module, theme.
 * @param $extensions
 *  Array of extension names.
 */
function _drush_pm_find_common_path($project_type, $extensions) {
  // Select the first path as the candidate to be the common prefix.
  $extension = array_pop($extensions);
  while (!($path = drupal_get_path($project_type, $extension))) {
    drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), LogLevel::WARNING);
    $extension = array_pop($extensions);
  }

  // If there's only one extension we are done. Otherwise, we need to find
  // the common prefix for all of them.
  if (count($extensions) > 0) {
    // Iterate over the other projects.
    while($extension = array_pop($extensions)) {
      $path2 = drupal_get_path($project_type, $extension);
      if (!$path2) {
        drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), LogLevel::DEBUG);
        continue;
      }
      // Option 1: same path.
      if ($path == $path2) {
        continue;
      }
      // Option 2: $path is a prefix of $path2.
      if (strpos($path2, $path) === 0) {
        continue;
      }
      // Option 3: $path2 is a prefix of $path.
      if (strpos($path, $path2) === 0) {
        $path = $path2;
        continue;
      }
      // Option 4: no one is a prefix of the other. Find the common
      // prefix by iteratively strip the rigthtmost piece of $path.
      // We will iterate until a prefix is found or path = '.', that on the
      // other hand is a condition theorically impossible to reach.
      do {
        $path = dirname($path);
        if (strpos($path2, $path) === 0) {
          break;
        }
      } while ($path != '.');
    }
  }

  return $path;
}

/**
 * @} End of "defgroup extensions".
 */

/**
 * Command callback. Show a list of extensions with type and status.
 */
function drush_pm_list() {
  //--package
  $package_filter = array();
  $package = strtolower(drush_get_option('package'));
  if (!empty($package)) {
    $package_filter = explode(',', $package);
  }
  if (!empty($package_filter) && (count($package_filter) == 1)) {
    drush_hide_output_fields('package');
  }

  //--type
  $all_types = array('module', 'theme');
  $type_filter = strtolower(drush_get_option('type'));
  if (!empty($type_filter)) {
    $type_filter = explode(',', $type_filter);
  }
  else {
    $type_filter = $all_types;
  }

  if (count($type_filter) == 1) {
    drush_hide_output_fields('type');
  }
  foreach ($type_filter as $type) {
    if (!in_array($type, $all_types)) { //TODO: this kind of check can be implemented drush-wide
      return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type)));
    }
  }

  //--status
  $all_status = array('enabled', 'disabled', 'not installed');
  $status_filter = strtolower(drush_get_option('status'));
  if (!empty($status_filter)) {
    $status_filter = explode(',', $status_filter);
  }
  else {
    $status_filter = $all_status;
  }
  if (count($status_filter) == 1) {
    drush_hide_output_fields('status');
  }

  foreach ($status_filter as $status) {
    if (!in_array($status, $all_status)) { //TODO: this kind of check can be implemented drush-wide
      return drush_set_error('DRUSH_PM_INVALID_PROJECT_STATUS', dt('!status is not a valid project status.', array('!status' => $status)));
  }
  }

  $result = array();
  $extension_info = drush_get_extensions(FALSE);
  uasort($extension_info, '_drush_pm_sort_extensions');

  $major_version = drush_drupal_major_version();
  foreach ($extension_info as $key => $extension) {
    if (!in_array(drush_extension_get_type($extension), $type_filter)) {
      unset($extension_info[$key]);
      continue;
    }
    $status = drush_get_extension_status($extension);
    if (!in_array($status, $status_filter)) {
      unset($extension_info[$key]);
      continue;
    }

    // Filter out core if --no-core specified.
    if (drush_get_option('no-core', FALSE)) {
      if ((($major_version >= 8) && ($extension->origin == 'core')) || (($major_version <= 7) && (strpos($extension->info['package'], 'Core') === 0))) {
        unset($extension_info[$key]);
        continue;
      }
    }

    // Filter out non-core if --core specified.
    if (drush_get_option('core', FALSE)) {
      if ((($major_version >= 8) && ($extension->origin != 'core')) || (($major_version <= 7) && (strpos($extension->info['package'], 'Core') !== 0))) {
        unset($extension_info[$key]);
        continue;
      }
    }

    // Filter by package.
    if (!empty($package_filter)) {
      if (!in_array(strtolower($extension->info['package']), $package_filter)) {
        unset($extension_info[$key]);
        continue;
      }
    }

    $row['package'] = $extension->info['package'];
    $row['name'] = $extension->label;
    $row['type'] = ucfirst(drush_extension_get_type($extension));
    $row['status'] = ucfirst($status);
    // Suppress notice when version is not present.
    $row['version'] = @$extension->info['version'];

    $result[$key] = $row;
    unset($row);
  }
  // In Drush-5, we used to return $extension_info here.
  return $result;
}

/**
 * Helper function for pm-enable.
 */
function drush_pm_enable_find_project_from_extension($extension) {
  $result =  drush_pm_lookup_extension_in_cache($extension);

  if (!isset($result)) {
    $release_info = drush_get_engine('release_info');

    // If we can find info on a project that has the same name
    // as the requested extension, then we'll call that a match.
    $request = pm_parse_request($extension);
    if ($release_info->checkProject($request)) {
      $result = $extension;
    }
  }

  return $result;
}

/**
 * Validate callback. Determine the modules and themes that the user would like enabled.
 */
function drush_pm_enable_validate() {
  $args = pm_parse_arguments(func_get_args());

  $extension_info = drush_get_extensions();

  $recheck = TRUE;
  $last_download = NULL;
  while ($recheck) {
    $recheck = FALSE;

    // Classify $args in themes, modules or unknown.
    $modules = array();
    $themes = array();
    $download = array();
    drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
    $extensions = array_merge($modules, $themes);
    $unknown = array_diff($args, $extensions);

    // If there're unknown extensions, try and download projects
    // with matching names.
    if (!empty($unknown)) {
      $found = array();
      foreach ($unknown as $name) {
        drush_log(dt('!extension was not found.', array('!extension' => $name)), LogLevel::WARNING);
        $project = drush_pm_enable_find_project_from_extension($name);
        if (!empty($project)) {
          $found[] = $project;
        }
      }
      if (!empty($found)) {
        // Prevent from looping if last download failed.
        if ($found === $last_download) {
          drush_log(dt("Unable to download some or all of the extensions."), LogLevel::WARNING);
          break;
        }
        drush_log(dt("The following projects provide some or all of the extensions not found:\n@list", array('@list' => implode("\n", $found))), LogLevel::OK);
        if (drush_get_option('resolve-dependencies')) {
          drush_log(dt("They are being downloaded."), LogLevel::OK);
        }
        if ((drush_get_option('resolve-dependencies')) || (drush_confirm("Would you like to download them?"))) {
          $download = $found;
        }
      }
    }

    // Discard already enabled and incompatible extensions.
    foreach ($extensions as $name) {
      if ($extension_info[$name]->status) {
        drush_log(dt('!extension is already enabled.', array('!extension' => $name)), LogLevel::OK);
      }
      // Check if the extension is compatible with Drupal core and php version.
      if ($component = drush_extension_check_incompatibility($extension_info[$name])) {
        drush_set_error('DRUSH_PM_ENABLE_MODULE_INCOMPATIBLE', dt('!name is incompatible with the !component version.', array('!name' => $name, '!component' => $component)));
        if (drush_extension_get_type($extension_info[$name]) == 'module') {
          unset($modules[$name]);
        }
        else {
          unset($themes[$name]);
        }
      }
    }

    if (!empty($modules)) {
      // Check module dependencies.
      $dependencies = drush_check_module_dependencies($modules, $extension_info);
      $unmet_dependencies = array();
      foreach ($dependencies as $module => $info) {
        if (!empty($info['unmet-dependencies'])) {
          foreach ($info['unmet-dependencies'] as $unmet) {
            $unmet_project = (!empty($info['dependencies'][$unmet]['project'])) ? $info['dependencies'][$unmet]['project'] : drush_pm_enable_find_project_from_extension($unmet);
            if (!empty($unmet_project)) {
              $unmet_dependencies[$module][$unmet_project] = $unmet_project;
            }
          }
        }
      }
      if (!empty($unmet_dependencies)) {
        $msgs = array();
        $unmet_project_list = array();
        foreach ($unmet_dependencies as $module => $unmet_projects) {
          $unmet_project_list = array_merge($unmet_project_list, $unmet_projects);
          $msgs[] = dt("!module requires !unmet-projects", array('!unmet-projects' => implode(', ', $unmet_projects), '!module' => $module));
        }
        $found = array_merge($download, $unmet_project_list);
        // Prevent from looping if last download failed.
        if ($found === $last_download) {
          drush_log(dt("Unable to download some or all of the extensions."), LogLevel::WARNING);
          break;
        }
        drush_log(dt("The following projects have unmet dependencies:\n!list", array('!list' => implode("\n", $msgs))), LogLevel::OK);
        if (drush_get_option('resolve-dependencies')) {
          drush_log(dt("They are being downloaded."), LogLevel::OK);
        }
        if (drush_get_option('resolve-dependencies') || drush_confirm(dt("Would you like to download them?"))) {
          $download = $found;
        }
      }
    }

    if (!empty($download)) {
      // Disable DRUSH_AFFIRMATIVE context temporarily.
      $drush_affirmative = drush_get_context('DRUSH_AFFIRMATIVE');
      drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
      // Invoke a new process to download dependencies.
      $result = drush_invoke_process('@self', 'pm-download', $download, array(), array('interactive' => TRUE));
      // Restore DRUSH_AFFIRMATIVE context.
      drush_set_context('DRUSH_AFFIRMATIVE', $drush_affirmative);
      // Refresh module cache after downloading the new modules.
      if (drush_drupal_major_version() >= 8) {
        \Drush\Drupal\ExtensionDiscovery::reset();
        system_list_reset();
      }
      $extension_info = drush_get_extensions();
      $last_download = $download;
      $recheck = TRUE;
    }
  }

  if (!empty($modules)) {
    $all_dependencies = array();
    $dependencies_ok = TRUE;
    foreach ($dependencies as $key => $info) {
      if (isset($info['error'])) {
        unset($modules[$key]);
        $dependencies_ok = drush_set_error($info['error']['code'], $info['error']['message']);
      }
      elseif (!empty($info['dependencies'])) {
        // Make sure we have an assoc array.
        $dependencies_list = array_keys($info['dependencies']);
        $assoc = array_combine($dependencies_list, $dependencies_list);
        $all_dependencies = array_merge($all_dependencies, $assoc);
      }
    }
    if (!$dependencies_ok) {
      return FALSE;
    }
    $modules = array_diff(array_merge($modules, $all_dependencies), drush_module_list());
    // Discard modules which doesn't meet requirements.
    require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
    foreach ($modules as $key => $module) {
      // Check to see if the module can be installed/enabled (hook_requirements).
      // See @system_modules_submit
      if (!drupal_check_module($module)) {
        unset($modules[$key]);
        drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module)));
        _drush_log_drupal_messages();
        return FALSE;
      }
    }
  }

  $searchpath = array();
  foreach (array_merge($modules, $themes) as $name) {
    $searchpath[] = drush_extension_get_path($extension_info[$name]);
  }
  // Add all modules that passed validation to the drush
  // list of commandfiles (if they have any).  This
  // will allow these newly-enabled modules to participate
  // in the pre-pm_enable and post-pm_enable hooks.
  if (!empty($searchpath)) {
    _drush_add_commandfiles($searchpath);
  }

  drush_set_context('PM_ENABLE_EXTENSION_INFO', $extension_info);
  drush_set_context('PM_ENABLE_MODULES', $modules);
  drush_set_context('PM_ENABLE_THEMES', $themes);

  return TRUE;
}

/**
 * Command callback. Enable one or more extensions from downloaded projects.
 * Note that the modules and themes to be enabled were evaluated during the
 * pm-enable validate hook, above.
 */
function drush_pm_enable() {
  // Get the data built during the validate phase
  $extension_info = drush_get_context('PM_ENABLE_EXTENSION_INFO');
  $modules = drush_get_context('PM_ENABLE_MODULES');
  $themes = drush_get_context('PM_ENABLE_THEMES');

  // Inform the user which extensions will finally be enabled.
  $extensions = array_merge($modules, $themes);
  if (empty($extensions)) {
    return drush_log(dt('There were no extensions that could be enabled.'), LogLevel::OK);
  }
  else {
    drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions))));
    if(!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  // Enable themes.
  if (!empty($themes)) {
    drush_theme_enable($themes);
  }

  // Enable modules and pass dependency validation in form submit.
  if (!empty($modules)) {
    drush_include_engine('drupal', 'environment');
    drush_module_enable($modules);
  }

  // Inform the user of final status.
  $result_extensions = drush_get_named_extensions_list($extensions);
  $problem_extensions = array();
  $role = drush_role_get_class();
  foreach ($result_extensions as $name => $extension) {
    if ($extension->status) {
      drush_log(dt('!extension was enabled successfully.', array('!extension' => $name)), LogLevel::OK);
      $perms = $role->getModulePerms($name);
      if (!empty($perms)) {
        drush_print(dt('!extension defines the following permissions: !perms', array('!extension' => $name, '!perms' => implode(', ', $perms))));
      }
    }
    else {
      $problem_extensions[] = $name;
    }
  }
  if (!empty($problem_extensions)) {
    return drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => implode(',', $problem_extensions))));
  }
  // Return the list of extensions enabled
  return $extensions;
}

/**
 * Command callback. Disable one or more extensions.
 */
function drush_pm_disable() {
  $args = pm_parse_arguments(func_get_args());
  drush_include_engine('drupal', 'pm');
  _drush_pm_disable($args);
}

/**
 * Add extensions that match extension_name*.
 *
 * A helper function for commands that take a space separated list of extension
 * names. It will identify extensions that have been passed in with a
 * trailing * and add all matching extensions to the array that is returned.
 *
 * @param $extensions
 *   An array of extensions, by reference.
 * @param $extension_info
 *   Optional. An array of extension info as returned by drush_get_extensions().
 */
function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) {
  if (empty($extension_info)) {
    $extension_info = drush_get_extensions();
  }
  foreach ($extensions as $key => $extension) {
    if (($wildcard = rtrim($extension, '*')) !== $extension) {
      foreach (array_keys($extension_info) as $extension_name) {
        if (substr($extension_name, 0, strlen($wildcard)) == $wildcard) {
          $extensions[] = $extension_name;
        }
      }
      unset($extensions[$key]);
      continue;
    }
  }
}

/**
 * Command callback. Uninstall one or more modules.
 */
function drush_pm_uninstall() {
  $args = pm_parse_arguments(func_get_args());
  drush_include_engine('drupal', 'pm');
  _drush_pm_uninstall($args);
}

/**
 * Command callback. Show available releases for given project(s).
 */
function drush_pm_releases() {
  $release_info = drush_get_engine('release_info');

  // Obtain requests.
  $requests = pm_parse_arguments(func_get_args(), FALSE);
  if (!$requests) {
    $requests = array('drupal');
  }

  // Get installed projects.
  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
    $projects = drush_get_projects();
  }
  else {
    $projects = array();
  }

  // Select the filter to apply based on cli options.
  if (drush_get_option('dev', FALSE)) {
    $filter = 'dev';
  }
  elseif (drush_get_option('all', FALSE)) {
    $filter = 'all';
  }
  else {
    $filter = '';
  }

  $status_url = drush_get_option('source');

  $output = array();
  foreach ($requests as $request) {
    $request = pm_parse_request($request, $status_url, $projects);
    $project_name = $request['name'];
    $project_release_info = $release_info->get($request);
    if ($project_release_info) {
      $version = isset($projects[$project_name]) ? $projects[$project_name]['version'] : NULL;
      $releases = $project_release_info->filterReleases($filter, $version);
      foreach ($releases as $key => $release) {
        $output["${project_name}-${key}"] = array(
          'project' => $project_name,
          'version' => $release['version'],
          'date' => gmdate('Y-M-d', $release['date']),
          'status' => implode(', ', $release['release_status']),
        ) + $release;
      }
    }
  }
  if (empty($output)) {
    return drush_log(dt('No valid projects given.'), LogLevel::OK);
  }

  return $output;
}

/**
 * Command callback. Show release notes for given project(s).
 */
function drush_pm_releasenotes() {
  $release_info = drush_get_engine('release_info');

  // Obtain requests.
  if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) {
    $requests = array('drupal');
  }

  // Get installed projects.
  if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
    $projects = drush_get_projects();
  }
  else {
    $projects = array();
  }

  $status_url = drush_get_option('source');

  $output = '';
  foreach($requests as $request) {
    $request = pm_parse_request($request, $status_url, $projects);
    $project_release_info = $release_info->get($request);
    if ($project_release_info) {
      $version = empty($request['version']) ? NULL : $request['version'];
      $output .= $project_release_info->getReleaseNotes($version);
    }
  }
  return $output;
}

/**
 * Command callback. Refresh update status information.
 */
function drush_pm_refresh() {
  $update_status = drush_get_engine('update_status');
  drush_print(dt("Refreshing update status information ..."));
  $update_status->refresh();
  drush_print(dt("Done."));
}

/**
 * Command callback. Execute pm-update.
 */
function drush_pm_update() {
  // Call pm-updatecode.  updatedb will be called in the post-update process.
  $args = pm_parse_arguments(func_get_args(), FALSE);
  drush_set_option('check-updatedb', FALSE);
  return drush_invoke('pm-updatecode', $args);
}

/**
 * Post-command callback.
 * Execute updatedb command after an updatecode - user requested `update`.
 */
function drush_pm_post_pm_update() {
  // Use drush_invoke_process to start a subprocess. Cleaner that way.
  if (drush_get_context('DRUSH_PM_UPDATED', FALSE) !== FALSE) {
    drush_invoke_process('@self', 'updatedb');
  }
}

/**
 * Validate callback for updatecode command. Abort if 'backup' directory exists.
 */
function drush_pm_updatecode_validate() {
  $path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/backup';
  if (is_dir($path) && (realpath(drush_get_option('backup-dir', FALSE)) != $path)) {
    return drush_set_error('', dt('Backup directory !path found. It\'s a security risk to store backups inside the Drupal tree. Drush now uses by default ~/drush-backups. You need to move !path out of the Drupal tree to proceed. Note: if you know what you\'re doing you can explicitly set --backup-dir to !path and continue.', array('!path' => $path)));
  }
}

/**
 * Post-command callback for updatecode.
 *
 * Execute pm-updatecode-postupdate in a backend process to not conflict with
 * old code already in memory.
 */
function drush_pm_post_pm_updatecode() {
  // Skip if updatecode was invoked by pm-update.
  // This way we avoid being noisy, as updatedb is to be executed.
  if (drush_get_option('check-updatedb', TRUE)) {
    if (drush_get_context('DRUSH_PM_UPDATED', FALSE)) {
      drush_invoke_process('@self', 'pm-updatecode-postupdate');
    }
  }
}

/**
 * Command callback. Execute updatecode-postupdate.
 */
function drush_pm_updatecode_postupdate() {
  // Clear the cache, since some projects could have moved around.
  drush_drupal_cache_clear_all();

  // Notify of pending database updates.
  // Make sure the installation API is available
  require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';

  // Load all .install files.
  drupal_load_updates();

  // @see system_requirements().
  foreach (drush_module_list() as $module) {
    $updates = drupal_get_schema_versions($module);
    if ($updates !== FALSE) {
      $default = drupal_get_installed_schema_version($module);
      if (max($updates) > $default) {
        drush_log(dt("You have pending database updates. Run `drush updatedb` or visit update.php in your browser."), LogLevel::WARNING);
        break;
      }
    }
  }
}

/**
 * Sanitize user provided arguments to several pm commands.
 *
 * Return an array of arguments off a space and/or comma separated values.
 */
function pm_parse_arguments($args, $dashes_to_underscores = TRUE) {
  $arguments = _convert_csv_to_array($args);
  foreach ($arguments as $key => $argument) {
    $argument = ($dashes_to_underscores) ? strtr($argument, '-', '_') : $argument;
  }
  return $arguments;
}

/**
 * Decompound a version string and returns major, minor, patch and extra parts.
 *
 * @see _pm_parse_version_compound()
 * @see pm_parse_version()
 *
 * @param string $version
 *    A version string like X.Y-Z, X.Y.Z-W or a subset.
 *
 * @return array
 *   Array with major, patch and extra keys.
 */
function _pm_parse_version_decompound($version) {
  $pattern = '/^(\d+)(?:.(\d+))?(?:\.(x|\d+))?(?:-([a-z0-9\.-]*))?(?:\+(\d+)-dev)?$/';

  $matches = array();
  preg_match($pattern, $version, $matches);

  $parts = array(
    'major'  => '',
    'minor'  => '',
    'patch'  => '',
    'extra'  => '',
    'offset' => '',
  );
  if (isset($matches[1])) {
    $parts['major'] = $matches[1];
    if (isset($matches[2])) {
      if (isset($matches[3]) && $matches[3] != '') {
        $parts['minor'] = $matches[2];
        $parts['patch'] = $matches[3];
      }
      else {
        $parts['patch'] = $matches[2];
      }
    }
    if (!empty($matches[4])) {
      $parts['extra'] = $matches[4];
    }
    if (!empty($matches[5])) {
      $parts['offset'] = $matches[5];
    }
  }

  return $parts;
}

/**
 * Build a version string from an array of major, minor and extra parts.
 *
 * @see _pm_parse_version_decompound()
 * @see pm_parse_version()
 *
 * @param array $parts
 *    Array of parts.
 *
 * @return string
 *   A Version string.
 */
function _pm_parse_version_compound($parts) {
  $project_version = '';
  if ($parts['patch'] != '') {
    $project_version = $parts['major'];
    if ($parts['minor'] != '') {
      $project_version = $project_version . '.' . $parts['minor'];
    }
    if ($parts['patch'] == 'x') {
      $project_version = $project_version . '.x-dev';
    }
    else {
      $project_version = $project_version . '.' . $parts['patch'];
      if ($parts['extra'] != '') {
        $project_version = $project_version . '-' . $parts['extra'];
      }
    }
    if ($parts['offset'] != '') {
      $project_version = $project_version . '+' . $parts['offset'] . '-dev';
    }
  }

  return $project_version;
}

/**
 * Parses a version string and returns its components.
 *
 * It parses both core and contrib version strings.
 *
 * Core (semantic versioning):
 *   - 8.0.0-beta3+252-dev
 *   - 8.0.0-beta2
 *   - 8.0.x-dev
 *   - 8.1.x
 *   - 8.0.1
 *   - 8
 *
 * Core (classic drupal scheme):
 *   - 7.x-dev
 *   - 7.x
 *   - 7.33
 *   - 7.34+3-dev
 *   - 7
 *
 * Contrib:
 *   - 7.x-1.0-beta1+30-dev
 *   - 7.x-1.0-beta1
 *   - 7.x-1.0+30-dev
 *   - 7.x-1.0
 *   - 1.0-beta1
 *   - 1.0
 *   - 7.x-1.x
 *   - 7.x-1.x-dev
 *   - 1.x
 *
 * @see pm_parse_request()
 *
 * @param string $version
 *   A core or project version string.
 *
 * @param bool $is_core
 *   Whether this is a core version or a project version.
 *
 * @return array
 *   Version string in parts.
 *   Example for a contrib version (ex: 7.x-3.2-beta1):
 *     - version         : Fully qualified version string.
 *     - drupal_version  : Core compatibility version (ex: 7.x).
 *     - version_major   : Major version (ex: 3).
 *     - version_minor   : Minor version. Not applicable. Always empty.
 *     - version_patch   : Patch version (ex: 2).
 *     - version_extra   : Extra version (ex: beta1).
 *     - project_version : Project specific part of the version (ex: 3.2-beta1).
 *
 *   Example for a core version (ex: 8.1.2-beta2 or 7.0-beta2):
 *     - version         : Fully qualified version string.
 *     - drupal_version  : Core compatibility version (ex: 8.x).
 *     - version_major   : Major version (ex: 8).
 *     - version_minor   : Minor version (ex: 1). Empty if not a semver.
 *     - version_patch   : Patch version (ex: 2).
 *     - version_extra   : Extra version (ex: beta2).
 *     - project_version : Same as 'version'.
 */
function pm_parse_version($version, $is_core = FALSE) {
  $core_parts = _pm_parse_version_decompound($version);

  // If no major version, we have no version at all. Pick a default.
  $drupal_version_default = drush_drupal_major_version();
  if ($core_parts['major'] == '') {
    $core_parts['major'] = ($drupal_version_default) ? $drupal_version_default : drush_get_option('default-major', 8);
  }

  if ($is_core) {
    $project_version = _pm_parse_version_compound($core_parts);
    $version_parts = array(
      'version' => $project_version,
      'drupal_version'  => $core_parts['major'] . '.x',
      'project_version' => $project_version,
      'version_major'   => $core_parts['major'],
      'version_minor'   => $core_parts['minor'],
      'version_patch'   => ($core_parts['patch'] == 'x') ? '' : $core_parts['patch'],
      'version_extra'   => ($core_parts['patch'] == 'x') ? 'dev' : $core_parts['extra'],
      'version_offset'  => $core_parts['offset'],
    );
  }
  else {
    // If something as 7.x-1.0-beta1, the project specific version is
    // in $version['extra'] and we need to parse it.
    if (strpbrk($core_parts['extra'], '.-')) {
      $nocore_parts = _pm_parse_version_decompound($core_parts['extra']);
      $nocore_parts['offset'] = $core_parts['offset'];
      $project_version = _pm_parse_version_compound($nocore_parts);
      $version_parts = array(
        'version' => $core_parts['major'] . '.x-' . $project_version,
        'drupal_version'  => $core_parts['major'] . '.x',
        'project_version' => $project_version,
        'version_major'   => $nocore_parts['major'],
        'version_minor'   => $core_parts['minor'],
        'version_patch'   => ($nocore_parts['patch'] == 'x') ? '' : $nocore_parts['patch'],
        'version_extra'   => ($nocore_parts['patch'] == 'x') ? 'dev' : $nocore_parts['extra'],
        'version_offset'  => $core_parts['offset'],
      );
    }
    // At this point we have half a version and must decide if this is a drupal major or a project.
    else {
      // If working on a bootstrapped site, core_parts has the project version.
      if ($drupal_version_default) {
        $project_version = _pm_parse_version_compound($core_parts);
        $version = ($project_version) ? $drupal_version_default . '.x-' . $project_version : '';
        $version_parts = array(
          'version'         => $version,
          'drupal_version'  => $drupal_version_default . '.x',
          'project_version' => $project_version,
          'version_major'   => $core_parts['major'],
          'version_minor'   => $core_parts['minor'],
          'version_patch'   => ($core_parts['patch'] == 'x') ? '' : $core_parts['patch'],
          'version_extra'   => ($core_parts['patch'] == 'x') ? 'dev' : $core_parts['extra'],
          'version_offset'  => $core_parts['offset'],
        );
      }
      // Not working on a bootstrapped site, core_parts is core version.
      else {
        $version_parts = array(
          'version' => '',
          'drupal_version'  => $core_parts['major'] . '.x',
          'project_version' => '',
          'version_major'   => '',
          'version_minor'   => '',
          'version_patch'   => '',
          'version_extra'   => '',
          'version_offset'  => '',
        );
      }
    }
  }

  return $version_parts;
}

/**
 * Parse out the project name and version and return as a structured array.
 *
 * @see pm_parse_version()
 *
 * @param string $request_string
 *   Project name with optional version. Examples: 'ctools-7.x-1.0-beta1'
 *
 * @return array
 *   Array with all parts of the request info.
 */
function pm_parse_request($request_string, $status_url = NULL, &$projects = array()) {
  // Split $request_string in project name and version. Note that hyphens (-)
  // are permitted in project names (ex: field-conditional-state).
  // We use a regex to split the string. The pattern used matches a string
  // starting with hyphen, followed by one or more numbers, any of the valid
  // symbols in version strings (.x-) and a catchall for the rest of the
  // version string.
  $parts = preg_split('/-(?:([\d+\.x].*))?$/', $request_string, NULL, PREG_SPLIT_DELIM_CAPTURE);

  if (count($parts) == 1) {
    // No version in the request string.
    $project = $request_string;
    $version = '';
  }
  else {
    $project = $parts[0];
    $version = $parts[1];
  }

  $is_core = ($project == 'drupal');
  $request = array(
    'name' => $project,
  ) + pm_parse_version($version, $is_core);

  // Set the status url if provided or available in project's info file.
  if ($status_url) {
    $request['status url'] = $status_url;
  }
  elseif (!empty($projects[$project]['status url'])) {
    $request['status url'] = $projects[$project]['status url'];
  }

  return $request;
}

/**
 * @defgroup engines Engine types
 * @{
 */

/**
 * Implementation of hook_drush_engine_type_info().
 */
function pm_drush_engine_type_info() {
  return array(
    'package_handler' => array(
      'option' => 'package-handler',
      'description' => 'Determine how to fetch projects from update service.',
      'default' => 'wget',
      'options' => array(
        'cache' => 'Cache release XML and tarballs or git clones. Git clones use git\'s --reference option. Defaults to 1 for downloads, and 0 for git.',
      ),
    ),
    'release_info' => array(
      'add-options-to-command' => TRUE,
    ),
    'update_status' => array(
      'option' => 'update-backend',
      'description' => 'Determine how to fetch update status information.',
      'default' => 'drush',
      'add-options-to-command' => TRUE,
      'options' => array(
        'update-backend' => 'Backend to obtain available updates.',
        'check-disabled' => 'Check for updates of disabled modules and themes.',
        'security-only'  => 'Only update modules that have security updates available.',
      ),
      'combine-help' => TRUE,
    ),
    'version_control' => array(
      'option' => 'version-control',
      'default' => 'backup',
      'description' => 'Integrate with version control systems.',
    ),
  );
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 *
 * Package handler engine is used by pm-download and
 * pm-updatecode commands to determine how to download/checkout
 * new projects and acquire updates to projects.
 */
function pm_drush_engine_package_handler() {
  return array(
    'wget' => array(
      'description' => 'Download project packages using wget or curl.',
      'options' => array(
        'no-md5' => 'Skip md5 validation of downloads.',
      ),
    ),
    'git_drupalorg' => array(
      'description' => 'Use git.drupal.org to checkout and update projects.',
      'options' => array(
        'gitusername' => 'Your git username as shown on user/[uid]/edit/git. Typically, this is set this in drushrc.php. Omitting this prevents users from pushing changes back to git.drupal.org.',
        'gitsubmodule' => 'Use git submodules for checking out new projects. Existing git checkouts are unaffected, and will continue to (not) use submodules regardless of this setting.',
        'gitcheckoutparams' => 'Add options to the `git checkout` command.',
        'gitcloneparams' => 'Add options to the `git clone` command.',
        'gitfetchparams' => 'Add options to the `git fetch` command.',
        'gitpullparams' => 'Add options to the `git pull` command.',
        'gitinfofile' => 'Inject version info into each .info file.',
      ),
      'sub-options' => array(
        'gitsubmodule' => array(
          'gitsubmoduleaddparams' => 'Add options to the `git submodule add` command.',
        ),
      ),
    ),
  );
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 *
 * Release info engine is used by several pm commands to obtain
 * releases info from Drupal's update service or external sources.
 */
function pm_drush_engine_release_info() {
  return array(
    'updatexml' => array(
      'description' => 'Drush release info engine for update.drupal.org and compatible services.',
      'options' => array(
        'source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.',
        'dev' => 'Work with development releases solely.',
      ),
      'sub-options' => array(
        'cache' => array(
          'cache-duration-releasexml' => 'Expire duration (in seconds) for release XML. Defaults to 86400 (24 hours).',
        ),
        'select' => array(
          'all' => 'Shows all available releases instead of a short list of recent releases.',
        ),
      ),
      'class' => 'Drush\UpdateService\ReleaseInfo',
    ),
  );
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 *
 * Update status engine is used to check available updates for
 * the projects in a Drupal site.
 */
function pm_drush_engine_update_status() {
  return array(
    'drupal' => array(
      'description' => 'Check available updates with update.module.',
      'drupal dependencies' => array('update'),
      'class' => 'Drush\UpdateService\StatusInfoDrupal',
    ),
    'drush' => array(
      'description' => 'Check available updates without update.module.',
      'class' => 'Drush\UpdateService\StatusInfoDrush',
    ),
  );
}

/**
 * Implements hook_drush_engine_ENGINE_TYPE().
 *
 * Integration with VCS in order to easily commit your changes to projects.
 */
function pm_drush_engine_version_control() {
  return array(
    'backup' => array(
      'description' => 'Backup all project files before updates.',
      'options' => array(
        'no-backup' => 'Do not perform backups. WARNING: Will result in non-core files/dirs being deleted (e.g. .git)',
        'backup-dir' => 'Specify a directory to backup projects into. Defaults to drush-backups within the home directory of the user running the command. It is forbidden to specify a directory inside your drupal root.',
      ),
    ),
    'bzr' => array(
      'signature' => 'bzr root %s',
      'description' => 'Quickly add/remove/commit your project changes to Bazaar.',
      'options' => array(
        'bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.',
        'bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also use the --bzrsync option.',
      ),
      'sub-options' => array(
        'bzrcommit' => array(
          'bzrmessage' => 'Override default commit message which is: Drush automatic commit. Project <name> <type> Command: <the drush command line used>',
        ),
      ),
      'examples' => array(
        'drush dl cck --version-control=bzr --bzrsync --bzrcommit' =>  'Download the cck project and then add it and commit it to Bazaar.'
      ),
    ),
    'svn' => array(
      'signature' => 'svn info %s',
      'description' => 'Quickly add/remove/commit your project changes to Subversion.',
      'options' => array(
        'svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.',
        'svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.',
        'svnstatusparams' => "Add options to the 'svn status' command",
        'svnaddparams' => 'Add options to the `svn add` command',
        'svnremoveparams' => 'Add options to the `svn remove` command',
        'svnrevertparams' => 'Add options to the `svn revert` command',
        'svncommitparams' => 'Add options to the `svn commit` command',
      ),
      'sub-options' => array(
        'svncommit' => array(
         'svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>',
        ),
      ),
      'examples' => array(
        'drush [command] cck --svncommitparams=\"--username joe\"' =>  'Commit changes as the user \'joe\' (Quotes are required).'
      ),
    ),
  );
}

/**
 * @} End of "Engine types".
 */

/**
 * Interface for version control systems.
 * We use a simple object layer because we conceivably need more than one
 * loaded at a time.
 */
interface drush_version_control {
  function pre_update(&$project);
  function rollback($project);
  function post_update($project);
  function post_download($project);
  static function reserved_files();
}

/**
 * A simple factory function that tests for version control systems, in a user
 * specified order, and returns the one that appears to be appropriate for a
 * specific directory.
 */
function drush_pm_include_version_control($directory = '.') {
  $engine_info = drush_get_engines('version_control');
  $version_controls = drush_get_option('version-control', FALSE);
  // If no version control was given, use a list of defaults.
  if (!$version_controls) {
    // Backup engine is the last option.
    $version_controls = array_reverse(array_keys($engine_info['engines']));
  }
  else {
    $version_controls = array($version_controls);
  }

  // Find the first valid engine in the list, checking signatures if needed.
  $engine = FALSE;
  while (!$engine && count($version_controls)) {
    $version_control = array_shift($version_controls);
    if (isset($engine_info['engines'][$version_control])) {
      if (!empty($engine_info['engines'][$version_control]['signature'])) {
        drush_log(dt('Verifying signature for !vcs version control engine.', array('!vcs' => $version_control)), LogLevel::DEBUG);
        if (drush_shell_exec($engine_info['engines'][$version_control]['signature'], $directory)) {
          $engine = $version_control;
        }
      }
      else {
        $engine = $version_control;
      }
    }
  }
  if (!$engine) {
    return drush_set_error('DRUSH_PM_NO_VERSION_CONTROL', dt('No valid version control or backup engine found (the --version-control option was set to "!version-control").', array('!version-control' => $version_control)));
  }

  $instance = drush_include_engine('version_control', $engine);
  return $instance;
}

/**
 * Update the locked status of all of the candidate projects
 * to be updated.
 *
 * @param array &$projects
 *   The projects array from pm_updatecode.  $project['locked'] will
 *   be set for every file where a persistent lockfile can be found.
 *   The 'lock' and 'unlock' operations are processed first.
 * @param array $projects_to_lock
 *   A list of projects to create peristent lock files for
 * @param array $projects_to_unlock
 *   A list of projects to clear the persistent lock on
 * @param string $lock_message
 *   The reason the project is being locked; stored in the lockfile.
 *
 * @return array
 *   A list of projects that are locked.
 */
function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) {
  $locked_result = array();

  // Warn about ambiguous lock / unlock values
  if ($projects_to_lock == array('1')) {
    $projects_to_lock = array();
    drush_log(dt('Ignoring --lock with no value.'), LogLevel::WARNING);
  }
  if ($projects_to_unlock == array('1')) {
    $projects_to_unlock = array();
    drush_log(dt('Ignoring --unlock with no value.'), LogLevel::WARNING);
  }

  // Log if we are going to lock or unlock anything
  if (!empty($projects_to_unlock)) {
    drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), LogLevel::OK);
  }
  if (!empty($projects_to_lock)) {
    drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), LogLevel::OK);
  }

  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  foreach ($projects as $name => $project) {
    $message = NULL;
    if (isset($project['path'])) {
      if ($name == 'drupal') {
        $lockfile = $drupal_root . '/.drush-lock-update';
      }
      else {
        $lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update';
      }

      // Remove the lock file if the --unlock option was specified
      if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) {
        drush_op('unlink', $lockfile);
      }

      // Create the lock file if the --lock option was specified
      if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) {
        drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush.");
        // Note that the project is locked.  This will work even if we are simulated,
        // or if we get permission denied from the file_put_contents.
        // If the lock is -not- simulated or transient, then the lock message will be
        // read from the lock file below.
        $message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.';
      }

      // If the persistent lock file exists, then mark the project as locked.
      if (file_exists($lockfile)) {
        $message = trim(file_get_contents($lockfile));
      }
    }

    // If there is a message set, then mark the project as locked.
    if (isset($message)) {
      $projects[$name]['locked'] = !empty($message) ? $message : "Locked.";
      $locked_result[$name] = $project;
    }
  }

  return $locked_result;
}

/**
 * Returns the path to the extensions cache file.
 */
function _drush_pm_extension_cache_file() {
  return drush_get_context('DRUSH_PER_USER_CONFIGURATION') . "/drush-extension-cache.inc";
}

/**
 * Load the extensions cache.
 */
function _drush_pm_get_extension_cache() {
  $extension_cache = array();
  $cache_file = _drush_pm_extension_cache_file();

  if (file_exists($cache_file)) {
    include $cache_file;
  }
  if (!array_key_exists('extension-map', $extension_cache)) {
    $extension_cache['extension-map'] = array();
  }
  return $extension_cache;
}

/**
 * Lookup an extension in the extensions cache.
 */
function drush_pm_lookup_extension_in_cache($extension) {
  $result = NULL;
  $extension_cache = _drush_pm_get_extension_cache();
  if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) {
    $result = $extension_cache[$extension];
  }
  return $result;
}

/**
 * Persists extensions cache.
 *
 * #TODO# not implemented.
 */
function drush_pm_put_extension_cache($extension_cache) {
}

/**
 * Store extensions founds within a project in extensions cache.
 */
function drush_pm_cache_project_extensions($project, $found) {
  $extension_cache = _drush_pm_get_extension_cache();
  foreach($found as $extension) {
    // Simple cache does not handle conflicts
    // We could keep an array of projects, and count
    // how many times each one has been seen...
    $extension_cache[$extension] = $project['name'];
  }
  drush_pm_put_extension_cache($extension_cache);
}

/**
 * Print out all extensions (modules/themes/profiles) found in specified project.
 *
 * Find .info.yml files in the project path and identify modules, themes and
 * profiles. It handles two kind of projects: drupal core/profiles and
 * modules/themes.
 * It does nothing with theme engine projects.
 */
function drush_pm_extensions_in_project($project) {
  // Mask for drush_scan_directory, to match .info.yml files.
  $mask = $project['drupal_version'][0] >= 8 ? '/(.*)\.info\.yml$/' : '/(.*)\.info$/';

  // Mask for drush_scan_directory, to avoid tests directories.
  $nomask = array('.', '..', 'CVS', 'tests');

  // Drupal core and profiles can contain modules, themes and profiles.
  if (in_array($project['project_type'], array('core', 'profile'))) {
    $found = array('profile' => array(), 'theme' => array(), 'module' => array());
    // Find all of the .info files
    foreach (drush_scan_directory($project['full_project_path'], $mask, $nomask) as $filename => $info) {
      // Extract extension name from filename.
      $matches = array();
      preg_match($mask, $info->basename, $matches);
      $name = $matches[1];

      // Find the project type corresponding the .info file.
     // (Only drupal >=7.x has .info for .profile)
      $base = dirname($filename) . '/' . $name;
      if (is_file($base . '.module')) {
        $found['module'][] = $name;
      }
      else if (is_file($base . '.profile')) {
        $found['profile'][] = $name;
      }
      else {
        $found['theme'][] = $name;
      }
    }
    // Special case: find profiles for drupal < 7.x (no .info)
    if ($project['drupal_version'][0] < 7) {
      foreach (drush_find_profiles($project['full_project_path']) as $filename => $info) {
        $found['profile'][] = $info->name;
      }
    }
    // Log results.
    $msg = "Project !project contains:\n";
    $args = array('!project' => $project['name']);
    foreach (array_keys($found) as $type) {
      if ($count = count($found[$type])) {
        $msg .= " - !count_$type !type_$type: !found_$type\n";
        $args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type]));
        if ($count > 1) {
          $args["!type_$type"] = $type.'s';
        }
      }
    }
    drush_log(dt($msg, $args), LogLevel::SUCCESS);
    drush_print_pipe(call_user_func_array('array_merge', array_values($found)));
  }
  // Modules and themes can only contain other extensions of the same type.
  elseif (in_array($project['project_type'], array('module', 'theme'))) {
    $found = array();
    foreach (drush_scan_directory($project['full_project_path'], $mask, $nomask) as $filename => $info) {
      // Extract extension name from filename.
      $matches = array();
      preg_match($mask, $info->basename, $matches);
      $found[] = $matches[1];
    }
    // If there is only one module / theme in the project, only print out
    // the message if is different than the project name.
    if (count($found) == 1) {
      if ($found[0] != $project['name']) {
        $msg = "Project !project contains a !type named !found.";
      }
    }
    // If there are multiple modules or themes in the project, list them all.
    else {
      $msg = "Project !project contains !count !types: !found.";
    }
    if (isset($msg)) {
      drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found))));
    }
    drush_print_pipe($found);
    // Cache results.
    drush_pm_cache_project_extensions($project, $found);
  }
}

/**
 * Return an array of empty directories.
 *
 * Walk a directory and return an array of subdirectories that are empty. Will
 * return the given directory if it's empty.
 * If a list of items to exclude is provided, subdirectories will be condidered
 * empty even if they include any of the items in the list.
 *
 * @param string $dir
 *   Path to the directory to work in.
 * @param array $exclude
 *   Array of files or directory to exclude in the check.
 *
 * @return array
 *   A list of directory paths that are empty. A directory is deemed to be empty
 *   if it only contains excluded files or directories.
 */
function drush_find_empty_directories($dir, $exclude = array()) {
  // Skip files.
  if (!is_dir($dir)) {
    return array();
  }
  $to_exclude = array_merge(array('.', '..'), $exclude);
  $empty_dirs = array();
  $dir_is_empty = TRUE;
  foreach (scandir($dir) as $file) {
    // Skip excluded directories.
    if (in_array($file, $to_exclude)) {
      continue;
    }
    // Recurse into sub-directories to find potentially empty ones.
    $subdir = $dir . '/' . $file;
    $empty_dirs += drush_find_empty_directories($subdir, $exclude);
    // $empty_dir will not contain $subdir, if it is a file or if the
    // sub-directory is not empty. $subdir is only set if it is empty.
    if (!isset($empty_dirs[$subdir])) {
      $dir_is_empty = FALSE;
    }
  }

  if ($dir_is_empty) {
    $empty_dirs[$dir] = $dir;
  }
  return $empty_dirs;
}

/**
 * Inject metadata into all .info files for a given project.
 *
 * @param string $project_dir
 *   The full path to the root directory of the project to operate on.
 * @param string $project_name
 *   The project machine name (AKA shortname).
 * @param string $version
 *   The version string to inject into the .info file(s).
 * @param int $datestamp
 *   The datestamp of the last commit.
 *
 * @return boolean
 *   TRUE on success, FALSE on any failures appending data to .info files.
 */
function drush_pm_inject_info_file_metadata($project_dir, $project_name, $version, $datestamp) {
  // `drush_drupal_major_version()` cannot be used here because this may be running
  // outside of a Drupal context.
  $yaml_format = substr($version, 0, 1) >= 8;
  $pattern = preg_quote($yaml_format ? '.info.yml' : '.info');
  $info_files = drush_scan_directory($project_dir, '/.*' . $pattern . '$/');
  if (!empty($info_files)) {
    // Construct the string of metadata to append to all the .info files.
    if ($yaml_format) {
      $info = _drush_pm_generate_info_yaml_metadata($version, $project_name, $datestamp);
    }
    else {
      $info = _drush_pm_generate_info_ini_metadata($version, $project_name, $datestamp);
    }
    foreach ($info_files as $info_file) {
      if (!drush_file_append_data($info_file->filename, $info)) {
        return FALSE;
      }
    }
  }
  return TRUE;
}

/**
 * Generate version information for `.info` files in ini format.
 *
 * Taken with some modifications from:
 * http://drupalcode.org/project/drupalorg.git/blob/refs/heads/6.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php#l192
 */
function _drush_pm_generate_info_ini_metadata($version, $project_name, $datestamp) {
  $matches = array();
  $extra = '';
  if (preg_match('/^((\d+)\.x)-.*/', $version, $matches) && $matches[2] >= 6) {
    $extra .= "\ncore = \"$matches[1]\"";
  }
  if (!drush_get_option('no-gitprojectinfo', FALSE)) {
    $extra = "\nproject = \"$project_name\"";
  }
  $date = date('Y-m-d', $datestamp);
  $info = <<<METADATA

; Information added by drush on {$date}
version = "{$version}"{$extra}
datestamp = "{$datestamp}"
METADATA;
  return $info;
}

/**
 * Generate version information for `.info` files in YAML format.
 */
function _drush_pm_generate_info_yaml_metadata($version, $project_name, $datestamp) {
  $matches = array();
  $extra = '';
  if (preg_match('/^((\d+)\.x)-.*/', $version, $matches) && $matches[2] >= 6) {
    $extra .= "\ncore: '$matches[1]'";
  }
  if (!drush_get_option('no-gitprojectinfo', FALSE)) {
    $extra = "\nproject: '$project_name'";
  }
  $date = date('Y-m-d', $datestamp);
  $info = <<<METADATA

# Information added by drush on {$date}
version: '{$version}'{$extra}
datestamp: {$datestamp}
METADATA;
  return $info;
}
<?php

use Drush\Log\LogLevel;

/**
 * Implementation of drush_hook_COMMAND_validate().
 */
function drush_pm_projectinfo_validate() {
  $status = drush_get_option('status');
  if (!empty($status)) {
    if (!in_array($status, array('enabled', 'disabled'), TRUE)) {
      return drush_set_error('DRUSH_PM_INVALID_PROJECT_STATUS', dt('!status is not a valid project status.', array('!status' => $status)));
    }
  }
}

/**
 * Implementation of drush_hook_COMMAND().
 */
function drush_pm_projectinfo() {
  // Get specific requests.
  $requests = pm_parse_arguments(func_get_args(), FALSE);

  // Get installed extensions and projects.
  $extensions = drush_get_extensions();
  $projects = drush_get_projects($extensions);

  // If user did not specify any projects, return them all
  if (empty($requests)) {
    $result = $projects;
  }
  else {
    $result = array();
    foreach ($requests as $name) {
      if (array_key_exists($name, $projects)) {
        $result[$name] = $projects[$name];
      }
      else {
        drush_log(dt('!project was not found.', array('!project' => $name)), LogLevel::WARNING);
        continue;
      }
    }
  }

  // Find the Drush commands that belong with each project.
  foreach ($result as $name => $project) {
    $drush_commands = pm_projectinfo_commands_in_project($project);
    if (!empty($drush_commands)) {
      $result[$name]['drush'] = $drush_commands;
    }
  }

  // If user specified --drush, remove projects with no drush extensions
  if (drush_get_option('drush')) {
    foreach ($result as $name => $project) {
      if (!array_key_exists('drush', $project)) {
        unset($result[$name]);
      }
    }
  }

  // If user specified --status=1|0, remove projects with a distinct status.
  if (($status = drush_get_option('status', FALSE)) !== FALSE) {
    $status_code = ($status == 'enabled') ? 1 : 0;
    foreach ($result as $name => $project) {
      if ($project['status'] != $status_code) {
        unset($result[$name]);
      }
    }
  }

  return $result;
}

function pm_projectinfo_commands_in_project($project) {
  $drush_commands = array();
  if (array_key_exists('path', $project)) {
    $commands = drush_get_commands();
    foreach ($commands as $commandname => $command) {
      if (!array_key_exists("is_alias", $command) && ($command['path'] == $project['path'])) {
        $drush_commands[] = $commandname;
      }
    }
  }
  return $drush_commands;
}

<?php

/**
 * @file
 * pm-updatecode command implementation.
 */

use Drush\Log\LogLevel;

/**
 * Command callback. Displays update status info and allows to update installed projects.
 *
 * Pass specific projects as arguments, otherwise we update all that have
 * candidate releases.
 *
 * This command prompts for confirmation before updating, so it is safe to run
 * just to check on. In this case, say at the confirmation prompt.
 */
function drush_pm_updatecode() {
  // In --pipe mode, just run pm-updatestatus and exit.
  if (drush_get_context('DRUSH_PIPE')) {
    drush_set_option('strict', 0);
    return drush_invoke('pm-updatestatus');
  }

  $update_status = drush_get_engine('update_status');

  // Get specific requests.
  $requests = pm_parse_arguments(func_get_args(), FALSE);

  // Print report of modules to update, and record
  // result of that function in $update_info.
  $updatestatus_options = array();
  foreach (array('lock', 'unlock', 'lock-message', 'update-backend', 'check-disabled', 'security-only') as $option) {
    $value = drush_get_option($option, FALSE);
    if ($value) {
      $updatestatus_options[$option] = $value;
    }
  }
  $backend_options = array(
    'integrate' => FALSE,
  );
  $values = drush_invoke_process("@self", 'pm-updatestatus', func_get_args(), $updatestatus_options, $backend_options);
  if (!is_array($values) || $values['error_status']) {
    return drush_set_error('pm-updatestatus failed.');
  }
  $last = $update_status->lastCheck();
  drush_print(dt('Update information last refreshed: ') . ($last  ? format_date($last) : dt('Never')));
  drush_print($values['output']);

  $update_info = $values['object'];

  // Prevent update of core if --no-core was specified.
  if (isset($update_info['drupal']) && drush_get_option('no-core', FALSE)) {
    unset($update_info['drupal']);
    drush_print(dt('Skipping core update (--no-core specified).'));
  }

  // Remove locked and non-updateable projects.
  foreach ($update_info as $name => $project) {
    if ((isset($project['locked']) && !isset($requests[$name])) || (!isset($project['updateable']) || !$project['updateable'])) {
      unset($update_info[$name]);
    }
  }

  // Do no updates in simulated mode.
  if (drush_get_context('DRUSH_SIMULATE')) {
    return drush_log(dt('No action taken in simulated mode.'), LogLevel::OK);
    return TRUE;
  }

  $tmpfile = drush_tempnam('pm-updatecode.');

  $core_update_available = FALSE;
  if (isset($update_info['drupal'])) {
    $drupal_project = $update_info['drupal'];
    unset($update_info['drupal']);

    // At present we need to update drupal core after non-core projects
    // are updated.
    if (empty($update_info)) {
      return _pm_update_core($drupal_project, $tmpfile);
    }
    // If there are modules other than drupal core enabled, then update them
    // first.
    else {
      $core_update_available = TRUE;
      if ($drupal_project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) {
        drush_print(dt("NOTE: A security update for the Drupal core is available."));
      }
      else {
        drush_print(dt("NOTE: A code update for the Drupal core is available."));
      }
      drush_print(dt("Drupal core will be updated after all of the non-core projects are updated.\n"));
    }
  }

  // If there are no releases to update, then print a final
  // exit message.
  if (empty($update_info)) {
    if (drush_get_option('security-only')) {
      return drush_log(dt('No security updates available.'), LogLevel::OK);
    }
    else {
      return drush_log(dt('No code updates available.'), LogLevel::OK);
    }
  }

  // Offer to update to the identified releases.
  if (!pm_update_packages($update_info, $tmpfile)) {
    return FALSE;
  }

  // After projects are updated we can update core.
  if ($core_update_available) {
    drush_print();
    return _pm_update_core($drupal_project, $tmpfile);
  }
}

/**
 * Update drupal core, following interactive confirmation from the user.
 *
 * @param $project
 *   The drupal project information from the drupal.org update service,
 *   copied from $update_info['drupal'].  @see drush_pm_updatecode.
 *
 * @return bool
 *   Success or failure. An error message will be logged.
 */
function _pm_update_core(&$project, $tmpfile) {
  $release_info = drush_get_engine('release_info');

  drush_print(dt('Code updates will be made to drupal core.'));
  drush_print(dt("WARNING:  Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt.  If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file."));
  drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing."));
  drush_print();
  if (drush_get_option('notes', FALSE)) {
    drush_print('Obtaining release notes for above projects...');
    #TODO# Build the $request array from info in $project.
    $request = pm_parse_request('drupal');
    $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile);
  }
  if(!drush_confirm(dt('Do you really want to continue?'))) {
    drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.'));
    return drush_user_abort();
  }

  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');

  // We need write permission on $drupal_root.
  if (!is_writable($drupal_root)) {
    return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.'));
  }

  // Create a directory 'core' if it does not already exist.
  $project['path'] = 'drupal-' . $project['candidate_version'];
  $project['full_project_path'] = $drupal_root . '/' . $project['path'];
  if (!is_dir($project['full_project_path'])) {
    drush_mkdir($project['full_project_path']);
  }

  // Create a list of directories to exclude from the update process.
  // On Drupal >=8 skip also directories in the document root.
  if (drush_drupal_major_version() >= 8) {
    $skip_list = array('sites', $project['path'], 'modules', 'profiles', 'themes');
  }
  else {
    $skip_list = array('sites', $project['path']);
  }
  // Add non-writable directories: we can't move them around.
  // We will also use $items_to_test later for $version_control check.
  $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE);
  foreach (array_keys($items_to_test) as $item) {
    if (is_dir($item) && !is_writable($item)) {
      $skip_list[] = $item;
      unset($items_to_test[$item]);
    }
    elseif (is_link($item)) {
      $skip_list[] = $item;
      unset($items_to_test[$item]);
    }
  }
  $project['skip_list'] = $skip_list;

  // Move all files and folders in $drupal_root to the new 'core' directory
  // except for the items in the skip list
  _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);

  // Set a context variable to indicate that rollback should reverse
  // the _pm_update_move_files above.
  drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);

  if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
    return FALSE;
  }

  // Check we have a version control system, and it clears its pre-flight.
  if (!$version_control->pre_update($project, $items_to_test)) {
    return FALSE;
  }

  // Update core.
  if (pm_update_project($project, $version_control) === FALSE) {
    return FALSE;
  }

  // Take the updated files in the 'core' directory that have been updated,
  // and move all except for the items in the skip list back to
  // the drupal root.
  _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
  drush_delete_dir($project['full_project_path']);
  $project['full_project_path'] = $drupal_root;

  // If there is a backup target, then find items
  // in the backup target that do not exist at the
  // drupal root.  These are to be moved back.
  if (array_key_exists('backup_target', $project)) {
    _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE);
    _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE);
  }

  pm_update_finish($project, $version_control);

  return TRUE;
}

/**
 * Move some files from one location to another.
 */
function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
  $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
  foreach ($items_to_move as $filename => $info) {
    if ($remove_conflicts) {
      drush_delete_dir($dest_dir . '/' . basename($filename));
    }
    if (!file_exists($dest_dir . '/' . basename($filename))) {
      $move_result = drush_move_dir($filename,  $dest_dir . '/' . basename($filename));
    }
  }
  return TRUE;
}

/**
 * Update projects according to an array of releases and print the release notes
 * for each project, following interactive confirmation from the user.
 *
 * @param $update_info
 *   An array of projects from the drupal.org update service, with an additional
 *   array key candidate_version that specifies the version to be installed.
 */
function pm_update_packages($update_info, $tmpfile) {
  $release_info = drush_get_engine('release_info');

  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');

  $print = '';
  $status = array();
  foreach($update_info as $project) {
    $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], ";
    $status[$project['status']] = $project['status'];
  }
  // We print the list of the projects that need to be updated.
  if (isset($status[DRUSH_UPDATESTATUS_NOT_SECURE])) {
    if (isset($status[DRUSH_UPDATESTATUS_NOT_CURRENT])) {
      $title = (dt('Security and code updates will be made to the following projects:'));
    }
    else {
      $title = (dt('Security updates will be made to the following projects:'));
    }
  }
  else {
    $title = (dt('Code updates will be made to the following projects:'));
  }
  $print = "$title " . (substr($print, 0, strlen($print)-2));
  drush_print($print);
  file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND);

  // Print the release notes for projects to be updated.
  if (drush_get_option('notes', FALSE)) {
    drush_print('Obtaining release notes for above projects...');
    #TODO# Build the $request array from info in $project.
    foreach (array_keys($update_info) as $project_name) {
      $request = pm_parse_request($project_name);
      $release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile);
    }
  }

  // We print some warnings before the user confirms the update.
  drush_print();
  if (drush_get_option('no-backup', FALSE)) {
    drush_print(dt("Note: You have selected to not store backups."));
  }
  else {
    drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system."));
    drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.'));
  }
  if(!drush_confirm(dt('Do you really want to continue with the update process?'))) {
    return drush_user_abort();
  }

  // Now we start the actual updating.
  foreach($update_info as $project) {
    if (empty($project['path'])) {
      return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
    }
    drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));

    // Define and check the full path to project directory and base (parent) directory.
    $project['full_project_path'] = $drupal_root . '/' . $project['path'];
    if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
      return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path'])));
    }
    if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
      return FALSE;
    }

    // Check we have a version control system, and it clears its pre-flight.
    if (!$version_control->pre_update($project)) {
      return FALSE;
    }

    // Run update on one project.
    if (pm_update_project($project, $version_control) === FALSE) {
      return FALSE;
    }
    pm_update_finish($project, $version_control);
  }

  return TRUE;
}

/**
 * Update one project -- a module, theme or Drupal core.
 *
 * @param $project
 *   The project to upgrade.  $project['full_project_path'] must be set
 *   to the location where this project is stored.
 * @return bool
 *   Success or failure. An error message will be logged.
 */
function pm_update_project($project, $version_control) {
  // 1. If the version control engine is a proper vcs we need to remove project
  // files in order to not have orphan files after update.
  // 2. If the package-handler is cvs or git, it will remove upstream removed
  // files and no orphans will exist after update.
  // So, we must remove all files previous update if the directory is not a
  // working copy of cvs or git but we don't need to remove them if the version
  // control engine is backup, as it did already move the project out to the
  // backup directory.
  if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
    // Find and unlink all files but the ones in the vcs control directories.
    $skip_list = array('.', '..');
    $skip_list = array_merge($skip_list, drush_version_control_reserved_files());
    drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
  }

  // Add the project to a context so we can roll back if needed.
  $updated = drush_get_context('DRUSH_PM_UPDATED');
  $updated[] = $project;
  drush_set_context('DRUSH_PM_UPDATED', $updated);

  if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
    return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name'])));
  }

  // If the version control engine is a proper vcs we also need to remove
  // orphan directories.
  if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
    $files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files());
    array_map('drush_delete_dir', $files);
  }

  return TRUE;
}

/**
 * Run the post-update hooks after updatecode is finished for one project.
 */
function pm_update_finish($project, $version_control) {
  drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
  drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']], $project);
  $version_control->post_update($project);
}

/**
 * Rollback the update process.
 */
function drush_pm_updatecode_rollback() {
  $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
  foreach($projects as $project) {
    drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));

    // Check we have a version control system, and it clears it's pre-flight.
    if (!$version_control = drush_pm_include_version_control($project['path'])) {
      return FALSE;
    }
    $version_control->rollback($project);
  }

  // Post rollback, we will do additional repair if the project is drupal core.
  $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE);
  if ($drupal_core) {
    $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
    _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
    drush_delete_dir($drupal_core['full_project_path']);
  }
}

<?php

/**
 * @file
 * pm-updatestatus command implementation.
 */

/**
 * Command callback. Displays update status info of installed projects.
 *
 * Pass specific projects as arguments, otherwise we show all that are
 * updateable.
 */
function drush_pm_updatestatus() {
  // Get specific requests.
  $args = pm_parse_arguments(func_get_args(), FALSE);

  // Get installed extensions and projects.
  $extensions = drush_get_extensions();
  $projects = drush_get_projects($extensions);

  // Parse out project name and version.
  $requests = array();
  foreach ($args as $request) {
    $request = pm_parse_request($request, NULL, $projects);
    $requests[$request['name']] = $request;
  }

  // Get the engine instance.
  $update_status = drush_get_engine('update_status');

  // If the user doesn't provide a value for check-disabled option,
  // and the update backend is 'drupal', use NULL, so the engine
  // will respect update.module defaults.
  $check_disabled_default = ($update_status->engine == 'drupal') ? NULL : FALSE;
  $check_disabled = drush_get_option('check-disabled', $check_disabled_default);

  $update_info = $update_status->getStatus($projects, $check_disabled);

  foreach ($extensions as $name => $extension) {
    // Add an item to $update_info for each enabled extension which was obtained
    // from cvs or git and its project is unknown (because of cvs_deploy or
    // git_deploy is not enabled).
    if (!isset($extension->info['project'])) {
      if ((isset($extension->vcs)) && ($extension->status)) {
        $update_info[$name] = array(
          'name' => $name,
          'label' => $extension->label,
          'existing_version' => 'Unknown',
          'status' => DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED,
          'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs)),
        );
        // The user may have requested to update a project matching this
        // extension. If it was by coincidence or error we don't mind as we've
        // already added an item to $update_info. Just clean up $requests.
        if (isset($requests[$name])) {
          unset($requests[$name]);
        }
      }
    }
    // Additionally if the extension name is distinct to the project name and
    // the user asked to update the extension, fix the request.
    elseif ((isset($requests[$name])) && ($name != $extension->info['project'])) {
      $requests[$extension->info['project']] = $requests[$name];
      unset($requests[$name]);
    }
  }
  // If specific project updates were requested then remove releases for all
  // others.
  $requested = func_get_args();
  if (!empty($requested)) {
    foreach ($update_info as $name => $project) {
      if (!isset($requests[$name])) {
        unset($update_info[$name]);
      }
    }
  }
  // Add an item to $update_info for each request not present in $update_info.
  foreach ($requests as $name => $request) {
    if (!isset($update_info[$name])) {
      // Disabled projects.
      if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) {
        $update_info[$name] = array(
          'name' => $name,
          'label' => $projects[$name]['label'],
          'existing_version' => $projects[$name]['version'],
          'status' => DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE,
        );
        unset($requests[$name]);
      }
      // At this point we are unable to find matching installed project.
      // It does not exist at all or it is misspelled,...
      else {
        $update_info[$name] = array(
          'name' => $name,
          'label' => $name,
          'existing_version' => 'Unknown',
          'status'=> DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND,
        );
      }
    }
  }

  // If specific versions were requested, match the requested release.
  foreach ($requests as $name => $request) {
    if (!empty($request['version'])) {
      if (empty($update_info[$name]['releases'][$request['version']])) {
        $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND;
      }
      elseif ($request['version'] == $update_info[$name]['existing_version']) {
        $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT;
      }
      // TODO: should we warn/reject if this is a downgrade?
      else {
        $update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT;
        $update_info[$name]['candidate_version'] = $request['version'];
      }
    }
  }
  // Process locks specified on the command line.
  $locked_list = drush_pm_update_lock($update_info, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message'));

  // Build project updatable messages, set candidate version and mark
  // 'updateable' in the project.
  foreach ($update_info as $key => $project) {
    switch($project['status']) {
      case DRUSH_UPDATESTATUS_NOT_SECURE:
        $status = dt('SECURITY UPDATE available');
        pm_release_recommended($project);
        break;
      case DRUSH_UPDATESTATUS_REVOKED:
        $status = dt('Installed version REVOKED');
        pm_release_recommended($project);
        break;
      case DRUSH_UPDATESTATUS_NOT_SUPPORTED:
        $status = dt('Installed version not supported');
        pm_release_recommended($project);
        break;
      case DRUSH_UPDATESTATUS_NOT_CURRENT:
        $status = dt('Update available');
        pm_release_recommended($project);
        break;
      case DRUSH_UPDATESTATUS_CURRENT:
        $status = dt('Up to date');
        pm_release_recommended($project);
        $project['updateable'] = FALSE;
        break;
      case DRUSH_UPDATESTATUS_NOT_CHECKED:
      case DRUSH_UPDATESTATUS_NOT_FETCHED:
      case DRUSH_UPDATESTATUS_FETCH_PENDING:
        $status = dt('Unable to check status');
        break;
      case DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED:
        $status = $project['status_msg'];
        break;
      case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE:
        $status = dt('Project has no enabled extensions and can\'t be updated');
        break;
      case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND:
        $status = dt('Specified project not found');
        break;
      case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND:
        $status = dt('Specified version not found');
        break;
      case DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT:
        $status = dt('Specified version already installed');
        break;
      case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT:
        $status = dt('Specified version available');
        $project['updateable'] = TRUE;
        break;
      default:
        $status = dt('Unknown');
        break;
    }

    if (isset($project['locked'])) {
      $status = $project['locked'] . " ($status)";
    }
    // Persist candidate_version in $update_info (plural).
    if (empty($project['candidate_version'])) {
      $update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change
    }
    else {
      $update_info[$key]['candidate_version'] = $project['candidate_version'];
    }
    $update_info[$key]['status_msg'] = $status;
    if (isset($project['updateable'])) {
      $update_info[$key]['updateable'] = $project['updateable'];
    }
  }

  // Filter projects to show.
  return pm_project_filter($update_info, drush_get_option('security-only'));
}

/**
 * Filter projects based on verbosity level and $security_only flag.
 *
 * @param array $update_info
 *   Update info for projects.
 * @param bool $security_only
 *   Whether to select only projects with security updates.
 *
 * @return
 *   Array of projects matching filter criteria.
 */
function pm_project_filter($update_info, $security_only) {
  $eligible = array();
  foreach ($update_info as $key => $project) {
    if ($security_only) {
      if ($project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) {
        $eligible[$key] = $project;
      }
    }
    elseif (drush_get_context('DRUSH_VERBOSE')) {
      $eligible[$key] = $project;
    }
    elseif ($project['status'] != DRUSH_UPDATESTATUS_CURRENT) {
      $eligible[$key] = $project;
    }
  }
  return $eligible;
}

/**
 * Set a release to a recommended version (if available), and set as updateable.
 */
function pm_release_recommended(&$project) {
  if (isset($project['recommended'])) {
    $project['candidate_version'] = $project['recommended'];
    $project['updateable'] = TRUE;
  }
  // If installed version is dev and the candidate version is older, choose
  // latest dev as candidate.
  if (($project['install_type'] == 'dev') && isset($project['candidate_version'])) {
    if ($project['releases'][$project['candidate_version']]['date'] < $project['datestamp']) {
      $project['candidate_version'] = $project['latest_dev'];
      if ($project['releases'][$project['candidate_version']]['date'] <= $project['datestamp']) {
        $project['candidate_version'] = $project['existing_version'];
        $project['updateable'] = FALSE;
      }
    }
  }
}

<?php

/**
 * @file
 * Drush pm directory copy backup extension
 */

use Drush\Log\LogLevel;

class drush_version_control_backup implements drush_version_control {

  /**
   * Implementation of pre_update().
   */
  public function pre_update(&$project, $items_to_test = array()) {
    if (drush_get_option('no-backup', FALSE)) {
      // Delete the project path to clean up files that should be removed
      if (!drush_delete_dir($project['full_project_path'])) {
        return FALSE;
      }
      return TRUE;
    }
    if ($backup_target = $this->prepare_backup_dir()) {
      if ($project['project_type'] != 'core') {
        $backup_target .= '/' . $project['project_type'] . 's';
        drush_mkdir($backup_target);
      }
      $backup_target .= '/'. $project['name'];
      // Save for rollback or notifications.
      $project['backup_target'] = $backup_target;

      // Move or copy to backup target based in package-handler.
      if (drush_get_option('package-handler', 'wget') == 'wget') {
        if (drush_move_dir($project['full_project_path'], $backup_target)) {
          return TRUE;
        }
      }
      // cvs or git.
      elseif (drush_copy_dir($project['full_project_path'], $backup_target)) {
        return TRUE;
      }
      return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target)));
    }
  }

  /**
   * Implementation of rollback().
   */
  public function rollback($project) {
    if (drush_get_option('no-backup', FALSE)) {
      return;
    }
    if (drush_move_dir($project['backup_target'], $project['full_project_path'], TRUE)) {
      return drush_log(dt("Backups were restored successfully."), LogLevel::OK);
    }
    return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.'));
  }

  /**
   * Implementation of post_update().
   */
  public function post_update($project) {
    if (drush_get_option('no-backup', FALSE)) {
      return;
    }
    if ($project['backup_target']) {
      drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $project['backup_target'])), LogLevel::OK);
    }
  }

  /**
   * Implementation of post_download().
   */
  public function post_download($project) {
   // NOOP
  }

  // Helper for pre_update.
  public function prepare_backup_dir($subdir = NULL) {
    return drush_prepare_backup_dir($subdir);
  }

  public static function reserved_files() {
    return array();
  }
}
<?php

/**
 * @file
 * Drush pm Bazaar extension
 */

use Drush\Log\LogLevel;

class drush_version_control_bzr implements drush_version_control {

  /**
   * Implementation of pre_update().
   *
   * Check that the project or drupal core directory looks clean
   */
  public function pre_update(&$project, $items_to_test = array()) {
    // Bazaar needs a list of items to test within the given project.
    // If $items_to_test is empty we need to force it to test the project
    // directory itself --once we've cd'ed to it.
    if (empty($items_to_test)) {
      $items_to_test = array('.' => '.');
    }
    $args = array_keys($items_to_test);
    array_unshift($args, 'bzr status --short' . str_repeat(' %s', count($args)));
    array_unshift($args, $project['full_project_path']);
    if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
      $output = preg_grep('/^[\sRCP][\sNDKM][\s\*]/', drush_shell_exec_output());
      if (!empty($output)) {
        return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
    }
    return TRUE;
  }

  /**
   * Implementation of rollback().
   */
  public function rollback($project) {
    if (drush_shell_exec('bzr revert %s', $project['full_project_path'])) {
      $output = drush_shell_exec_output();
      if (!empty($output)) {
        return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the Bazaar status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
    }
  }

  /**
   * Implementation of post_update().
   */
  public function post_update($project) {
    if ($this->sync($project)) {
      // Only attempt commit on a sucessful sync
      $this->commit($project);
    }
  }

  /**
   * Implementation of post_download().
   */
  public function post_download($project) {
    if ($this->sync($project)) {
      // Only attempt commit on a sucessful sync
      $this->commit($project);
    }
  }

  /**
   * Automatically add any unversioned files to Bazaar and remove any files
   * that have been deleted on the file system
   */
  private function sync($project) {
    if (drush_get_option('bzrsync')) {
      $errors = '';
      $root = array();
      if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) {
        $output = drush_shell_exec_output();
        // All paths returned by bzr status are relative to the repository root.
        if (drush_shell_exec('bzr root %s', $project['full_project_path'])) {
          $root = drush_shell_exec_output();
        }
        foreach ($output as $line) {
          if (preg_match('/^\?\s+(.*)/', $line, $matches)) {
            $path = $root[0] .'/'. $matches[1];
            if (!drush_shell_exec('bzr add --no-recurse %s', $path)) {
              $errors .= implode("\n", drush_shell_exec_output());
            }
          }
          else if (preg_match('/^\s+D\s+(.*)/', $line, $matches)) {
            $path = $root[0] .'/'. $matches[1];
            if (!drush_shell_exec('bzr remove %s', $path)) {
              $errors .= implode("\n", drush_shell_exec_output());
            }
          }
        }
        if (!empty($errors)) {
          return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
        }
      }
      else {
        return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
      }
      return TRUE;
    }
  }

  /**
   * Automatically commit changes to the repository
   */
  private function commit($project) {
    if (drush_get_option('bzrcommit')) {
      $message = drush_get_option('bzrmessage');
      if (empty($message)) {
        $message = dt("Drush automatic commit.\nProject: @name @type\nCommand: @arguments", array('@name' => $project['name'], '@type' => $project['project_type'], '@arguments' => implode(' ', $_SERVER['argv'])));
      }
      if (drush_shell_exec('bzr commit --message=%s %s', $message, $project['full_project_path'])) {
        drush_log(dt('Project committed to Bazaar successfully'), LogLevel::OK);
      }
      else {
        drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
      }
    }
    else {
      drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back."));
    }
  }

  public static function reserved_files() {
    return array('.bzr', '.bzrignore', '.bzrtags');
  }
}
<?php

/**
 * @file
 * Drush pm SVN extension
 */

use Drush\Log\LogLevel;

class drush_version_control_svn implements drush_version_control {

  /**
   * Implementation of pre_update().
   */
  public function pre_update(&$project, $items_to_test = array()) {
    // If items to test is empty, test everything; otherwise, pass just
    // the list of files to test to svn status.
    $args = array_keys($items_to_test);
    array_unshift($args, 'svn status '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
    array_unshift($args, $project['full_project_path']);
    if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
      $output = preg_grep('/^[ ACDMRX?!~][ CM][ L][ +][ SX][ K]/', drush_shell_exec_output());
      if (!empty($output)) {
        return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
    }

    // Check for incoming updates
    $args = array_keys($items_to_test);
    array_unshift($args, 'svn status -u '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
    array_unshift($args, $project['full_project_path']);
    if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
      $output = preg_grep('/\*/', drush_shell_exec_output());
      if (!empty($output)) {
        return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
    }
    return TRUE;
  }

  /**
   * Implementation of rollback().
   */
  public function rollback($project) {
    if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $project['full_project_path'])) {
      $output = drush_shell_exec_output();
      if (!empty($output)) {
        return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
      }
    }
    else {
      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
    }
  }

  /**
   * Implementation of post_update().
   */
  public function post_update($project) {
    if ($this->sync($project)) {
      // Only attempt commit on a sucessful sync
      $this->commit($project);
    }
  }

  /**
   * Implementation of post_download().
   */
  public function post_download($project) {
    if ($this->sync($project)) {
      // Only attempt commit on a sucessful sync
      $this->commit($project);
    }
  }

  /**
   * Automatically add any unversioned files to Subversion and remove any files
   * that have been deleted on the file system
   */
  private function sync($project) {
    if (drush_get_option('svnsync')) {
      $errors = '';
      if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $project['full_project_path'])) {
        $output = drush_shell_exec_output();
        foreach ($output as $line) {
          if (preg_match('/^\? *(.*)/', $line, $matches)) {
            if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) {
              $errors .= implode("\n", drush_shell_exec_output());
            }
          }
          if (preg_match('/^\! *(.*)/', $line, $matches)) {
            if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) {
              $errors .= implode("\n", drush_shell_exec_output());
            }
          }
        }
        if (!empty($errors)) {
          return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
        }
      }
      else {
        return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
      }
      return TRUE;
    }
  }

  /**
   * Automatically commit changes to the repository
   */
  private function commit($project) {
    if (drush_get_option('svncommit')) {
      $message = drush_get_option('svnmessage');
      if (empty($message)) {
        $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
      }
      if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $project['full_project_path'])) {
        drush_log(dt('Project committed to Subversion successfully'), LogLevel::OK);
      }
      else {
        drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
      }
    }
    else {
      drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back."));
    }
  }

  public static function reserved_files() {
    return array('.svn');
  }
}
<?php

// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
  // Check function_exists as a safety net in case it is added in future.
  function filter_init() {
    global $conf, $user;
    // Inject values into the $conf array - will apply to all sites.
    // This can be a useful place to apply generic development settings.
    $conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
    // Merge in the injected conf, overriding existing items.
    $conf = array_merge($conf, $conf_inject);
  }
}

// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
  // Check function_exists as a safety net in case it is added in future.
  function system_watchdog($log_entry = array()) {
    $uid = $log_entry['user']->uid;
    $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
      '!message'     => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
      '!severity'    => $log_entry['severity'],
      '!type'        => $log_entry['type'],
      '!ip'          => $log_entry['ip'],
      '!request_uri' => $log_entry['request_uri'],
      '!referer'     => $log_entry['referer'],
      '!uid'         => $uid,
      '!link'        => strip_tags($log_entry['link']),
    ));
    error_log($message);
  }
}

// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
  if (isset($_SERVER[$key])) {
    return $_SERVER[$key];
  }
  else {
    return getenv($key);
  }
}

$url = parse_url($_SERVER["REQUEST_URI"]);
if (file_exists('.' . urldecode($url['path']))) {
  // Serve the requested resource as-is.
  return FALSE;
}

// Populate the "q" query key with the path, skip the leading slash.
$_GET['q'] = $_REQUEST['q'] = substr($url['path'], 1);

// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');

// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs
// contain multiple dots (such as config entity IDs) in the path. Since this is
// a virtual resource, served by index.php set the script name explicitly.
// See https://github.com/drush-ops/drush/issues/2033 for more information.
$_SERVER['SCRIPT_NAME'] = '/index.php';

// Include the main index.php and let Drupal take over.
// n.b. Drush sets the cwd to the Drupal root during bootstrap.
include 'index.php';
<?php

// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
  // Check function_exists as a safety net in case it is added in future.
  function filter_init() {
    global $conf, $user;
    // Inject values into the $conf array - will apply to all sites.
    // This can be a useful place to apply generic development settings.
    $conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
    // Merge in the injected conf, overriding existing items.
    $conf = array_merge($conf, $conf_inject);
  }
}

// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
  // Check function_exists as a safety net in case it is added in future.
  function system_watchdog($log_entry = array()) {
    $uid = $log_entry['user']->id();
    $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
      '!message'     => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
      '!severity'    => $log_entry['severity'],
      '!type'        => $log_entry['type'],
      '!ip'          => $log_entry['ip'],
      '!request_uri' => $log_entry['request_uri'],
      '!referer'     => $log_entry['referer'],
      '!uid'         => $uid,
      '!link'        => strip_tags($log_entry['link']),
    ));
    error_log($message);
  }
}

// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
  if (isset($_SERVER[$key])) {
    return $_SERVER[$key];
  }
  else {
    return getenv($key);
  }
}

$url = parse_url($_SERVER["REQUEST_URI"]);
if (file_exists('.' . urldecode($url['path']))) {
  // Serve the requested resource as-is.
  return FALSE;
}

// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');

// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs
// contain multiple dots (such as config entity IDs) in the path. Since this is
// a virtual resource, served by index.php set the script name explicitly.
// See https://github.com/drush-ops/drush/issues/2033 for more information.
// Work around the PHP bug. Update $_SERVER variables to point to the correct
// index-file.
$path = $url['path'];
$script = 'index.php';
if (strpos($path, '.php') !== FALSE) {
  // Work backwards through the path to check if a script exists. Otherwise
  // fallback to index.php.
  do {
    $path = dirname($path);
    if (preg_match('/\.php$/', $path) && is_file('.' . $path)) {
      // Discovered that the path contains an existing PHP file. Use that as the
      // script to include.
      $script = ltrim($path, '/');
      break;
    }
  } while ($path !== '/' && $path !== '.');
}

// Update $_SERVER variables to point to the correct index-file.
$index_file_absolute = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $script;
$index_file_relative = DIRECTORY_SEPARATOR . $script;

// SCRIPT_FILENAME will point to the router script itself, it should point to
// the full path of index.php.
$_SERVER['SCRIPT_FILENAME'] = $index_file_absolute;

// SCRIPT_NAME and PHP_SELF will either point to index.php or contain the full
// virtual path being requested depending on the URL being requested. They
// should always point to index.php relative to document root.
$_SERVER['SCRIPT_NAME'] = $index_file_relative;
$_SERVER['PHP_SELF'] = $index_file_relative;

// Require the script and let core take over.
require $_SERVER['SCRIPT_FILENAME'];<?php

// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');

// Complete $_GET['q'] for Drupal 6 with built in server
// - this uses the Drupal 7 method.
if (!isset($_GET['q']) && isset($_SERVER['REQUEST_URI'])) {
    // This request is either a clean URL, or 'index.php', or nonsense.
    // Extract the path from REQUEST_URI.
    $request_path = strtok($_SERVER['REQUEST_URI'], '?');
    $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/'));
    // Unescape and strip $base_path prefix, leaving q without a leading slash.
  $_GET['q'] = substr(urldecode($request_path), $base_path_len + 1);
}

// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
  // Check function_exists as a safety net in case it is added in future.
  function filter_init() {
    global $conf, $user;
    // Inject values into the $conf array - will apply to all sites.
    // This can be a useful place to apply generic development settings.
    $conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
    // Merge in the injected conf, overriding existing items.
    $conf = array_merge($conf, $conf_inject);
  }
}

// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
  // Check function_exists as a safety net in case it is added in future.
  function system_watchdog($log_entry = array()) {
    // Drupal <= 7.x defines VERSION. Drupal 8 defines \Drupal::VERSION instead.
    if (defined('VERSION')) {
      $uid = $log_entry['user']->uid;
    }
    else {
      $uid = $log_entry['user']->id();
    }
    $message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
      '!message'     => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
      '!severity'    => $log_entry['severity'],
      '!type'        => $log_entry['type'],
      '!ip'          => $log_entry['ip'],
      '!request_uri' => $log_entry['request_uri'],
      '!referer'     => $log_entry['referer'],
      '!uid'         => $uid,
      '!link'        => strip_tags($log_entry['link']),
    ));
    error_log($message);
  }
}

// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
  if (isset($_SERVER[$key])) {
    return $_SERVER[$key];
  }
  else {
    return getenv($key);
  }
}
<?php

/**
 * @file
 * Built in http server commands.
 */

/**
 * Implements hook_drush_help().
 */
function runserver_drush_help($section) {
  switch ($section) {
    case 'meta:runserver:title':
      return dt("Runserver commands");
    case 'meta:runserver:summary':
      return dt('Launch the built-in PHP webserver.');
    case 'drush:runserver':
      return dt("Runs a lightweight built in http server for development.
 - Don't use this for production, it is neither scalable nor secure for this use.
 - If you run multiple servers simultaneously, you will need to assign each a unique port.
 - Use Ctrl-C or equivalent to stop the server when complete.");
  }
}

/**
 * Implements hook_drush_command().
 */
function runserver_drush_command() {
  $items = array();

  $items['runserver'] = array(
    'description' => 'Runs PHP\'s built-in http server for development.',
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
    'arguments' => array(
      'addr:port/path' => 'Host IP address and port number to bind to and path to open in web browser. Format is addr:port/path, default 127.0.0.1:8888, all elements optional. See examples for shorthand. Only opens a browser if a path is specified.',
    ),
    'options' => array(
      'variables' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Comma delimited list of name=value pairs (or array in drushrc).',
      'default-server' => 'A default addr:port/path to use for any values not specified as an argument.',
      'user' => 'If opening a web browser, automatically log in as this user (user ID or username). Default is to log in as uid 1.',
      'browser' => 'If opening a web browser, which browser to user (defaults to operating system default). Use --no-browser to avoid opening a browser.',
      'dns' => 'Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.',
    ),
    'aliases' => array('rs'),
    'examples' => array(
      'drush rs 8080' => 'Start runserver on 127.0.0.1, port 8080.',
      'drush rs 10.0.0.28:80' => 'Start runserver on 10.0.0.28, port 80.',
      'drush rs [::1]:80' => 'Start runserver on IPv6 localhost ::1, port 80.',
      'drush rs --dns localhost:8888/user' => 'Start runserver on localhost (using rDNS to determine binding IP), port 8888, and open /user in browser.',
      'drush rs /' => 'Start runserver on default IP/port (127.0.0.1, port 8888), and open / in browser.',
      'drush rs --default-server=127.0.0.1:8080/ -' => 'Use a default (would be specified in your drushrc) that starts runserver on port 8080, and opens a browser to the front page. Set path to a single hyphen path in argument to prevent opening browser for this session.',
      'drush rs :9000/admin' => 'Start runserver on 127.0.0.1, port 9000, and open /admin in browser. Note that you need a colon when you specify port and path, but no IP.',
    ),
  );
  return $items;
}

/**
 * Callback for runserver command.
 */
function drush_core_runserver($uri = NULL) {
  global $user, $base_url;

  // Determine active configuration.
  $uri = runserver_uri($uri);
  if (!$uri) {
    return FALSE;
  }

  // Remove any leading slashes from the path, since that is what url() expects.
  $path = ltrim($uri['path'], '/');

  // $uri['addr'] is a special field set by runserver_uri()
  $hostname = $uri['host'];
  $addr = $uri['addr'];

  drush_set_context('DRUSH_URI', 'http://' . $hostname . ':' . $uri['port']);

  // We pass in the currently logged in user (if set via the --user option),
  // which will automatically log this user in the browser during the first
  // request.
  if (drush_get_option('user', FALSE) === FALSE) {
    drush_set_option('user', 1);
  }
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);

  // We delete any registered files here, since they are not caught by Ctrl-C.
  _drush_delete_registered_files();

  // We set the effective base_url, since we have now detected the current site,
  // and need to ensure generated URLs point to our runserver host.
  // We also pass in the effective base_url to our auto_prepend_script via the
  // CGI environment. This allows Drupal to generate working URLs to this http
  // server, whilst finding the correct multisite from the HTTP_HOST header.
  $base_url = 'http://' . $addr . ':' . $uri['port'];
  $env['RUNSERVER_BASE_URL'] = $base_url;

  // We pass in an array of $conf overrides using the same approach.
  // This is available as an option for developers to pass in their own
  // favorite $conf overrides (e.g. disabling css aggregation).
  $current_override = drush_get_option_list('variables', array());
  $override = array();
  foreach ($current_override as $name => $value) {
    if (is_numeric($name) && (strpos($value, '=') !== FALSE)) {
      list($name, $value) = explode('=', $value, 2);
    }
    $override[$name] = $value;
  }
  $env['RUNSERVER_CONF'] = urlencode(serialize($override));

  // We log in with the specified user ID (if set) via the password reset URL.
  $user_message = '';
  $usersingle = drush_user_get_class()->getCurrentUserAsSingle();
  if ($usersingle->id()) {
    $browse = $usersingle->passResetUrl($path);
    $user_message = ', logged in as ' . $usersingle->getUsername();
  }
  else {
    $browse = drush_url($path);
  }

  drush_print(dt('HTTP server listening on !addr, port !port (see http://!hostname:!port/!path), serving site !site!user...', array('!addr' => $addr, '!hostname' => $hostname, '!port' => $uri['port'], '!path' => $path, '!site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), '!user' => $user_message)));
  // Start php 5.4 builtin server.
  // Store data used by runserver-prepend.php in the shell environment.
  foreach ($env as $key => $value) {
    putenv($key . '=' . $value);
  }
  if (!empty($uri['path'])) {
    // Start a browser if desired. Include a 2 second delay to allow the
    // server to come up.
    drush_start_browser($browse, 2);
  }
  // Start the server using 'php -S'.
  if (drush_drupal_major_version() >= 8) {
    $extra = ' "' . __DIR__ . '/d8-rs-router.php"';
  }
  elseif (drush_drupal_major_version() == 7) {
    $extra = ' "' . __DIR__ . '/d7-rs-router.php"';
  }
  else {
    $extra = ' --define auto_prepend_file="' . __DIR__ . '/runserver-prepend.php"';
  }
  $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  drush_shell_exec_interactive('cd %s && %s -S ' . $addr . ':' . $uri['port']. $extra, $root, drush_get_option('php', 'php'));
}

/**
 * Determine the URI to use for this server.
 */
function runserver_uri($uri) {
  $drush_default = array(
    'host' => '127.0.0.1',
    'port' => '8888',
    'path' => '',
  );
  $user_default = runserver_parse_uri(drush_get_option('default-server', ''));
  $site_default = runserver_parse_uri(drush_get_option('uri', ''));
  $uri = runserver_parse_uri($uri);
  if (is_array($uri)) {
    // Populate defaults.
    $uri = $uri + $user_default + $site_default + $drush_default;
    if (ltrim($uri['path'], '/') == '-') {
      // Allow a path of a single hyphen to clear a default path.
      $uri['path'] = '';
    }
    // Determine and set the new URI.
    $uri['addr'] = $uri['host'];
    if (drush_get_option('dns', FALSE)) {
      if (ip2long($uri['host'])) {
        $uri['host'] = gethostbyaddr($uri['host']);
      }
      else {
        $uri['addr'] = gethostbyname($uri['host']);
      }
    }
  }
  return $uri;
}

/**
 * Parse a URI or partial URI (including just a port, host IP or path).
 *
 * @param string $uri
 *   String that can contain partial URI.
 *
 * @return array
 *   URI array as returned by parse_url.
 */
function runserver_parse_uri($uri) {
  if (empty($uri)) {
    return array();
  }
  if ($uri[0] == ':') {
    // ':port/path' shorthand, insert a placeholder hostname to allow parsing.
    $uri = 'placeholder-hostname' . $uri;
  }
  // FILTER_VALIDATE_IP expects '[' and ']' to be removed from IPv6 addresses.
  // We check for colon from the right, since IPv6 addresses contain colons.
  $to_path = trim(substr($uri, 0, strpos($uri, '/')), '[]');
  $to_port = trim(substr($uri, 0, strrpos($uri, ':')), '[]');
  if (filter_var(trim($uri, '[]'), FILTER_VALIDATE_IP) || filter_var($to_path, FILTER_VALIDATE_IP) || filter_var($to_port, FILTER_VALIDATE_IP)) {
    // 'IP', 'IP/path' or 'IP:port' shorthand, insert a schema to allow parsing.
    $uri = 'http://' . $uri;
  }
  $uri = parse_url($uri);
  if (empty($uri)) {
    return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid argument - should be in the "host:port/path" format, numeric (port only) or non-numeric (path only).'));
  }
  if (count($uri) == 1 && isset($uri['path'])) {
    if (is_numeric($uri['path'])) {
      // Port only shorthand.
      $uri['port'] = $uri['path'];
      unset($uri['path']);
    }
  }
  if (isset($uri['host']) && $uri['host'] == 'placeholder-hostname') {
    unset($uri['host']);
  }
  return $uri;
}
<?php

/**
 * @file
 * Drush sql commands
 */

/**
 * Implementation of hook_drush_help().
 */
function sql_drush_help($section) {
  switch ($section) {
    case 'meta:sql:title':
      return dt('SQL commands');
    case 'meta:sql:summary':
      return dt('Examine and modify your Drupal database.');
    case 'drush:sql-sanitize':
      return dt('Run sanitization operations on the current database. You can add more sanitization to this command by implementing hook_drush_sql_sync_sanitize().');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function sql_drush_command() {
  $options['database'] = array(
    'description' => 'The DB connection key if using multiple connections in settings.php.',
    'example-value' => 'key',
  );
  $db_url['db-url'] = array(
    'description' => 'A Drupal 6 style database URL.',
    'example-value' => 'mysql://root:pass@127.0.0.1/db',
  );
  $options['target'] = array(
    'description' => 'The name of a target within the specified database connection. Defaults to \'default\'.',
    'example-value' => 'key',
    // Gets unhidden in help_alter(). We only want to show this to D7 users but have to
    // declare it here since some commands do not bootstrap fully.
    'hidden' => TRUE,
  );

  $items['sql-drop'] = array(
    'description' => 'Drop all tables in a given database.',
    'arguments' => array(
    ),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'options' => array(
      'yes' => 'Skip confirmation and proceed.',
      'result-file' => array(
        'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
        'example-value' => '/path/to/file',
      ),
    ) + $options + $db_url,
    'topics' => array('docs-policy'),
  );
  $items['sql-conf'] = array(
    'description' => 'Print database connection details using print_r().',
    'hidden' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'options' => array(
      'all' => 'Show all database connections, instead of just one.',
      'show-passwords' => 'Show database password.',
    ) + $options,
    'outputformat' => array(
      'default' => 'print-r',
      'pipe-format' => 'var_export',
      'private-fields' => 'password',
    ),
  );
  $items['sql-connect'] = array(
    'description' => 'A string for connecting to the DB.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'options' => $options + $db_url + array(
      'extra' => array(
        'description' => 'Add custom options to the connect string.',
        'example-value' => '--skip-column-names',
      ),
    ),
    'examples' => array(
      '`drush sql-connect` < example.sql' => 'Bash: Import SQL statements from a file into the current database.',
      'eval (drush sql-connect) < example.sql' => 'Fish: Import SQL statements from a file into the current database.',
    ),
  );
  $items['sql-create'] = array(
    'description' => 'Create a database.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'examples' => array(
      'drush sql-create' => 'Create the database for the current site.',
      'drush @site.test sql-create' => 'Create the database as specified for @site.test.',
      'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' =>
        'Create the database as specified in the db-url option.'
    ),
    'options' => array(
      'db-su' => 'Account to use when creating a new database. Optional.',
      'db-su-pw' => 'Password for the "db-su" account. Optional.',
    ) + $options + $db_url,
  );
  $items['sql-dump'] = array(
    'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'examples' => array(
      'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.',
      'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.php',
      'drush sql-dump --extra=--no-data' => 'Pass extra option to dump command.',
    ),
    'options' => drush_sql_get_table_selection_options() + array(
      'result-file' => array(
        'description' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.',
        'example-value' => '/path/to/file',
        'value' => 'optional',
      ),
      'create-db' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only.  Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'),
      'data-only' => 'Dump data without statements to create any of the schema.',
      'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Slows down the dump. Mysql only.',
      'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.',
      'extra' => 'Add custom options to the dump command.',
    ) + $options + $db_url,
  );
  $items['sql-query'] = array(
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'description' => 'Execute a query against a database.',
    'examples' => array(
      'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.',
      'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored.  Caution: curly-braces will be stripped from all portions of the query.',
      '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
      'drush sql-query --file=example.sql' => 'Alternate way to import sql statements from a file.',
    ),
    'arguments' => array(
       'query' => 'An SQL query. Ignored if \'file\' is provided.',
    ),
    'options' => array(
      'result-file' => array(
        'description' => 'Save to a file. The file should be relative to Drupal root. Optional.',
        'example-value' => '/path/to/file',
      ),
      'file' => 'Path to a file containing the SQL to be run. Gzip files are accepted.',
      'extra' => array(
        'description' => 'Add custom options to the database connection command.',
        'example-value' => '--skip-column-names',
      ),
      'db-prefix' => 'Enable replacement of braces in your query.',
      'db-spec' => array(
        'description' => 'A database specification',
        'hidden' => TRUE, // Hide since this is only used with --backend calls.
      )
    ) + $options + $db_url,
    'aliases' => array('sqlq'),
  );
  $items['sql-cli'] = array(
    'description' => "Open a SQL command-line interface using Drupal's credentials.",
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    // 'options' => $options + $db_url,
    'allow-additional-options' => array('sql-connect'),
    'aliases' => array('sqlc'),
    'examples' => array(
      'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.",
      'drush sql-cli --extra=-A' => "Open a SQL CLI and skip reading table information.",
    ),
    'remote-tty' => TRUE,
  );
  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function sql_drush_help_alter(&$command) {
  // Drupal 7+ only options.
  if (drush_drupal_major_version() >= 7) {
    if ($command['commandfile'] == 'sql') {
      unset($command['options']['target']['hidden']);
    }
  }
}

/**
 * Safely bootstrap Drupal to the point where we can
 * access the database configuration.
 */
function drush_sql_bootstrap_database_configuration() {
  // Under Drupal 7, if the database is configured but empty, then
  // DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception.
  // If this happens, we'll just catch it and continue.
  // TODO:  Fix this in the bootstrap, per http://drupal.org/node/1996004
  try {
    drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  }
  catch (Exception $e) {
  }
}

/**
 * Check whether further bootstrap is needed. If so, do it.
 */
function drush_sql_bootstrap_further() {
  if (!drush_get_option(array('db-url', 'db-spec'))) {
    drush_sql_bootstrap_database_configuration();
  }
}

/**
 * Command callback. Displays the Drupal site's database connection string.
 */
function drush_sql_conf() {
  drush_sql_bootstrap_database_configuration();
  if (drush_get_option('all')) {
    $sqlVersion = drush_sql_get_version();
    return $sqlVersion->getAll();
  }
  else {
    $sql = drush_sql_get_class();
    return $sql->db_spec();
  }
}

/**
 * Command callback. Emits a connect string.
 */
function drush_sql_connect() {
  drush_sql_bootstrap_further();
  $sql = drush_sql_get_class();
  return $sql->connect(FALSE);
}

/**
 * Command callback. Create a database.
 */
function drush_sql_create() {
  drush_sql_bootstrap_further();
  $sql = drush_sql_get_class();
  $db_spec = $sql->db_spec();
  // Prompt for confirmation.
  if (!drush_get_context('DRUSH_SIMULATE')) {
    // @todo odd - maybe for sql-sync.
    $txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database'];
    drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination)));

    if (!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }

  return $sql->createdb(TRUE);
}


/**
 * Command callback. Outputs the entire Drupal database in SQL format using mysqldump or equivalent.
 */
function drush_sql_dump() {
  drush_sql_bootstrap_further();
  $sql = drush_sql_get_class();
  return $sql->dump(drush_get_option('result-file', FALSE));
}

/**
 * Construct an array that places table names in appropriate
 * buckets based on whether the table is to be skipped, included
 * for structure only, or have structure and data dumped.
 * The keys of the array are:
 * - skip: tables to be skipped completed in the dump
 * - structure: tables to only have their structure i.e. DDL dumped
 * - tables: tables to have structure and data dumped
 *
 * @return array
 *   An array of table names with each table name in the appropriate
 *   element of the array.
 */
function drush_sql_get_table_selection() {
  // Skip large core tables if instructed.  Used by 'sql-drop/sql-dump/sql-sync' commands.
  $skip_tables = _drush_sql_get_raw_table_list('skip-tables');
  // Skip any structure-tables as well.
  $structure_tables = _drush_sql_get_raw_table_list('structure-tables');
  // Dump only the specified tables.  Takes precedence over skip-tables and structure-tables.
  $tables = _drush_sql_get_raw_table_list('tables');

  return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables);
}

function drush_sql_get_table_selection_options() {
  return array(
    'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.',
    'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.',
    'tables-key' => 'A key in the $tables array. Optional.',
    'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
    'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.',
    'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
  );
}

/**
 * Expand wildcard tables.
 *
 * @param array $tables
 *   An array of table names, some of which may contain wildcards (`*`).
 * @param array $db_tables
 *   An array with all the existing table names in the current database.
 * @return
 *   $tables array with wildcards resolved to real table names.
 */
function drush_sql_expand_wildcard_tables($tables, $db_tables) {
  // Table name expansion based on `*` wildcard.
  $expanded_db_tables = array();
  foreach ($tables as $k => $table) {
    // Only deal with table names containing a wildcard.
    if (strpos($table, '*') !== FALSE) {
      $pattern = '/^' . str_replace('*', '.*', $table) . '$/i';
      // Merge those existing tables which match the pattern with the rest of
      // the expanded table names.
      $expanded_db_tables += preg_grep($pattern, $db_tables);
    }
  }
  return $expanded_db_tables;
}

/**
 * Filters tables.
 *
 * @param array $tables
 *   An array of table names to filter.
 * @param array $db_tables
 *   An array with all the existing table names in the current database.
 * @return
 *   An array with only valid table names (i.e. all of which actually exist in
 *   the database).
 */
function drush_sql_filter_tables($tables, $db_tables) {
  // Ensure all the tables actually exist in the database.
  foreach ($tables as $k => $table) {
    if (!in_array($table, $db_tables)) {
      unset($tables[$k]);
    }
  }

  return $tables;
}

/**
 * Given the table names in the input array that may contain wildcards (`*`),
 * expand the table names so that the array returned only contains table names
 * that exist in the database.
 *
 * @param array $tables
 *   An array of table names where the table names may contain the
 *   `*` wildcard character.
 * @param array $db_tables
 *   The list of tables present in a database.
 * @return array
 *   An array of tables with non-existant tables removed.
 */
function _drush_sql_expand_and_filter_tables($tables, $db_tables) {
  $expanded_tables = drush_sql_expand_wildcard_tables($tables, $db_tables);
  $tables = drush_sql_filter_tables(array_merge($tables, $expanded_tables), $db_tables);
  $tables = array_unique($tables);
  sort($tables);
  return $tables;
}

/**
 * Consult the specified options and return the list of tables
 * specified.
 *
 * @param option_name
 *   The option name to check: skip-tables, structure-tables
 *   or tables.  This function will check both *-key and *-list,
 *   and, in the case of sql-sync, will also check target-*
 *   and source-*, to see if an alias set one of these options.
 * @returns array
 *   Returns an array of tables based on the first option
 *   found, or an empty array if there were no matches.
 */
function _drush_sql_get_raw_table_list($option_name) {
  foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) {
    foreach(explode(',',$prefix_list) as $prefix) {
      $key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context);
      foreach(explode(',', $key_list) as $key) {
        $all_tables = drush_get_option($option_name, array());
        if (array_key_exists($key, $all_tables)) {
          return $all_tables[$key];
        }
        if ($option_name != 'tables') {
          $all_tables = drush_get_option('tables', array());
          if (array_key_exists($key, $all_tables)) {
            return $all_tables[$key];
          }
        }
      }
      $table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context);
      if (isset($table_list)) {
        return empty($table_list) ? array() : explode(',', $table_list);
      }
    }
  }

  return array();
}

/**
 * Command callback. Executes the given SQL query on the Drupal database.
 */
function drush_sql_query($query = NULL) {
  drush_sql_bootstrap_further();
  $filename = drush_get_option('file', NULL);
  // Enable prefix processing when db-prefix option is used.
  if (drush_get_option('db-prefix')) {
    drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
  }
  if (drush_get_context('DRUSH_SIMULATE')) {
    if ($query) {
      drush_print(dt('Simulating sql-query: !q', array('!q' => $query)));
    }
    else {
      drush_print(dt('Simulating sql-import from !f', array('!f' => drush_get_option('file'))));
    }
  }
  else {
    $sql = drush_sql_get_class(drush_get_option('db-spec'));
    $result = $sql->query($query, $filename, drush_get_option('result-file'));
    if (!$result) {
      return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.'));
    }
    drush_print(implode("\n", drush_shell_exec_output()));
  }
  return TRUE;
}

/**
 * Command callback. Drops all tables in the database.
 */
function drush_sql_drop() {
  drush_sql_bootstrap_further();
  $sql = drush_sql_get_class();
  $db_spec = $sql->db_spec();
  if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) {
    return drush_user_abort();
  }
  $tables = $sql->listTables();
  return $sql->drop($tables);
}

/**
 * Command callback. Launches console a DB backend.
 */
function drush_sql_cli() {
  drush_sql_bootstrap_further();
  $sql = drush_sql_get_class();
  $result = !drush_shell_proc_open($sql->connect());
  if (!$result) {
    drush_set_error('DRUSH_SQL_CLI_ERROR', dt('SQL client error occurred.'));
  }
  return $result;
}

/**
 * Call from a pre-sql-sync hook to register an sql
 * query to be executed in the post-sql-sync hook.
 * @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync().
 *
 * @param $id
 *   String containing an identifier representing this
 *   operation.  This id is not actually used at the
 *   moment, it is just used to fufill the contract
 *   of drush contexts.
 * @param $message
 *   String with the confirmation message that describes
 *   to the user what the post-sync operation is going
 *   to do.  This confirmation message is printed out
 *   just before the user is asked whether or not the
 *   sql-sync operation should be continued.
 * @param $query
 *   String containing the sql query to execute.  If no
 *   query is provided, then the confirmation message will
 *   be displayed to the user, but no action will be taken
 *   in the post-sync hook.  This is useful for drush modules
 *   that wish to provide their own post-sync hooks to fix
 *   up the target database in other ways (e.g. through
 *   Drupal APIs).
 */
function drush_sql_register_post_sync_op($id, $message, $query = NULL) {
  $options = drush_get_context('post-sync-ops');

  $options[$id] = array('message' => $message, 'query' => $query);

  drush_set_context('post-sync-ops', $options);
}

/**
 * Builds a confirmation message for all post-sync operations.
 *
 * @return string
 *   All post-sync operation messages concatenated together.
 */
function _drush_sql_get_post_sync_messages() {
  $messages = '';
  $operations = drush_get_context('post-sync-ops');
  if (!empty($operations)) {
    $messages = dt('The following operations will be done on the target database:') . "\n";

    $bullets = array_column($operations, 'message');
    $messages .= " * " . implode("\n * ", $bullets) . "\n";
  }
  return $messages;
}

/**
 * Wrapper for drush_get_class; instantiates an driver-specific instance
 * of SqlBase class.
 *
 * @param array $db_spec
 *   If known, specify a $db_spec that the class can operate with.
 *
 * @throws \Drush\Sql\SqlException
 *
 * @return Drush\Sql\SqlBase
 */
function drush_sql_get_class($db_spec = NULL) {
  $database = drush_get_option('database', 'default');
  $target = drush_get_option('target', 'default');

  // Try a few times to quickly get $db_spec.
  if (!empty($db_spec)) {
    if (!empty($db_spec['driver'])) {
      return drush_get_class(array('Drush\Sql\Sql', 'Drupal\Driver\Database\\' .  $db_spec['driver'] . '\Drush'), array($db_spec), array($db_spec['driver']));
    }
  }
  elseif ($url = drush_get_option('db-url')) {
    $url =  is_array($url) ? $url[$database] : $url;
    $db_spec = drush_convert_db_from_db_url($url);
    $db_spec['db_prefix'] = drush_get_option('db-prefix');
    return drush_sql_get_class($db_spec);
  }
  elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) {
    $db_spec = $databases[$database][$target];
    return drush_sql_get_class($db_spec);
  }
  else {
    // No parameter or options provided. Determine $db_spec ourselves.
    if ($sqlVersion = drush_sql_get_version()) {
      if ($db_spec = $sqlVersion->get_db_spec()) {
        return drush_sql_get_class($db_spec);
      }
    }
  }

  throw new \Drush\Sql\SqlException('Unable to find a matching SQL Class. Drush cannot find your database connection details.');
}

/**
 * Wrapper for drush_get_class; instantiates a Drupal version-specific instance
 * of SqlVersion class.
 *
 * @return Drush\Sql\SqlVersion
 */
function drush_sql_get_version() {
  return drush_get_class('Drush\Sql\Sql', array(), array(drush_drupal_major_version())) ?: NULL;
}

/**
 * Implements hook_drush_sql_sync_sanitize().
 *
 * Sanitize usernames, passwords, and sessions when the --sanitize option is used.
 * It is also an example of how to write a database sanitizer for sql sync.
 *
 * To write your own sync hook function, define mymodule_drush_sql_sync_sanitize()
 * in mymodule.drush.inc and follow the form of this function to add your own
 * database sanitization operations via the register post-sync op function;
 * @see drush_sql_register_post_sync_op().  This is the only thing that the
 * sync hook function needs to do; sql-sync takes care of the rest.
 *
 * The function below has a lot of logic to process user preferences and
 * generate the correct SQL regardless of whether Postgres, Mysql,
 * Drupal 6/7/8 is in use.  A simpler sanitize function that
 * always used default values and only worked with Drupal 6 + mysql
 * appears in the drush.api.php.  @see hook_drush_sql_sync_sanitize().
 */
function sql_drush_sql_sync_sanitize($site) {
  $site_settings = drush_sitealias_get_record($site);
  $databases = sitealias_get_databases_from_record($site_settings);
  $major_version = drush_drupal_major_version();
  $wrap_table_name = (bool) drush_get_option('db-prefix');
  $user_table_updates = array();
  $message_list = array();

  // Sanitize passwords.
  $newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), drush_generate_password());
  if ($newpassword != 'no' && $newpassword !== 0) {
    $pw_op = "";

    // In Drupal 6, passwords are hashed via the MD5 algorithm.
    if ($major_version == 6) {
      $pw_op = "MD5('$newpassword')";
    }
    // In Drupal 7, passwords are hashed via a more complex algorithm,
    // available via the user_hash_password function.
    elseif ($major_version == 7) {
      $core = DRUSH_DRUPAL_CORE;
      include_once $core . '/includes/password.inc';
      include_once $core . '/includes/bootstrap.inc';
      $hash = user_hash_password($newpassword);
      $pw_op = "'$hash'";
    }
    else {
      // D8+. Mimic Drupal's /scripts/password-hash.sh
      drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
      $password_hasher = \Drupal::service('password');
      $hash = $password_hasher->hash($newpassword);
      $pw_op = "'$hash'";
    }
    if (!empty($pw_op)) {
      $user_table_updates[] = "pass = $pw_op";
      $message_list[] =  "passwords";
    }
  }

  // Sanitize email addresses.
  $newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost.localdomain');
  if ($newemail != 'no' && $newemail !== 0) {
    if (strpos($newemail, '%') !== FALSE) {
      // We need a different sanitization query for Postgres and Mysql.

      $db_driver = $databases['default']['default']['driver'];
      if ($db_driver == 'pgsql') {
        $email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '");
        $newmail =  "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
      }
      elseif ($db_driver == 'mssql') {
        $email_map = array('%uid' => "' + uid + '", '%mail' => "' + replace(mail, '@', '_') + '", '%name' => "' + replace(name, ' ', '_') + '");
        $newmail =  "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
      }
      else {
        $email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '");
        $newmail =  "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')";
      }
      $user_table_updates[] = "mail = $newmail, init = $newmail";
    }
    else {
      $user_table_updates[] = "mail = '$newemail', init = '$newemail'";
    }
    $message_list[] = 'email addresses';
  }

  if (!empty($user_table_updates)) {
    $table = $major_version >= 8 ? 'users_field_data' : 'users';
    if ($wrap_table_name) {
      $table = "{{$table}}";
    }
    $sanitize_query = "UPDATE {$table} SET " . implode(', ', $user_table_updates) . " WHERE uid > 0;";
    drush_sql_register_post_sync_op('user-email', dt('Reset !message in !table table', array('!message' => implode(' and ', $message_list), '!table' => $table)), $sanitize_query);
  }

  $sanitizer = new \Drush\Commands\core\SanitizeCommands();
  $sanitizer->doSanitize($major_version);
}
<?php

use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;

/**
 * Implementation of hook_drush_command().
 */
function sqlsync_drush_command() {
  $items['sql-sync'] = array(
    'description' => 'Copies the database contents from a source site to a target site. Transfers the database dump via rsync.',
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'drush dependencies' => array('sql', 'core'), // core-rsync.
    'package' => 'sql',
    'examples' => array(
      'drush sql-sync @source @target' => 'Copy the database from the site with the alias "source" to the site with the alias "target".',
      'drush sql-sync prod dev' => 'Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).',
    ),
    'arguments' => array(
      'source' => 'A site-alias or the name of a subdirectory within /sites whose database you want to copy from.',
      'target' => 'A site-alias or the name of a subdirectory within /sites whose database you want to replace.',
    ),
    'required-arguments' => TRUE,
    'options' => drush_sql_get_table_selection_options() + array(
      // 'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.',
      // 'no-cache' => 'Do not cache the sql-dump file.',
      'no-dump' => 'Do not dump the sql database; always use an existing dump file.',
      'no-sync' => 'Do not rsync the database dump file from source to target.',
      'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".',
      'source-db-url' => 'Database specification for source system to dump from.',
      'source-remote-port' => 'Override sql database port number in source-db-url. Optional.',
      'source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.',
      'source-dump' => array(
        'description' => 'The destination for the dump file, or the path to the dump file when --no-dump is specified.',
        'example-value' => '/dumpdir/db.sql',
      ),
      'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
      'source-target' => array(
        'description' => 'A key within the SOURCE database identifying a particular server in the database group.',
        'example-value' => 'key',
        // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
        // declare it here since this command does not bootstrap fully.
        'hidden' => TRUE,
      ),
      'target-db-url' => '',
      'target-remote-port' => '',
      'target-remote-host' => '',
      'target-dump' => array(
        'description' => 'A path for saving the dump file on target. Mandatory when using --no-sync.',
        'example-value' => '/dumpdir/db.sql.gz',
      ),
      'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.',
      'target-target' => array(
        'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.',
        'example-value' => 'key',
        // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
        // declare it here since this command does not bootstrap fully.
        'hidden' => TRUE,
      ),
      'create-db' => 'Create a new database before importing the database dump on the target machine.',
      'db-su' => array(
        'description' => 'Account to use when creating a new database. Optional.',
        'example-value' => 'root',
      ),
      'db-su-pw' => array(
        'description' => 'Password for the "db-su" account. Optional.',
        'example-value' => 'pass',
      ),
      // 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump.  sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
      'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync.',
    ),
    'sub-options' => array(
      'sanitize' => drupal_sanitize_options() + array(
          'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations',
        ),
    ),
    'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'),
  );
  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function sqlsync_drush_help_alter(&$command) {
  // Drupal 7+ only options.
  if (drush_drupal_major_version() >= 7) {
    if ($command['command'] == 'sql-sync') {
      unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']);
    }
  }
}

/**
 * Command argument complete callback.
 *
 * @return
 *  Array of available site aliases.
 */
function sql_sql_sync_complete() {
  return array('values' => array_keys(_drush_sitealias_all_list()));
}

/*
 * Implements COMMAND hook init.
 */
function drush_sql_sync_init($source, $destination) {
  // Try to get @self defined when --uri was not provided.
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);

  // Preflight destination in case it defines the alias used by the source
  _drush_sitealias_get_record($destination);

  // After preflight, get source and destination settings
  $source_settings = drush_sitealias_get_record($source);
  $destination_settings = drush_sitealias_get_record($destination);

  // Apply command-specific options.
  drush_sitealias_command_default_options($source_settings, 'source-');
  drush_sitealias_command_default_options($destination_settings, 'target-');
}

/*
 * A command validate callback.
 */
function drush_sqlsync_sql_sync_validate($source, $destination) {
  // Get destination info for confirmation prompt.
  $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
  $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
  $source_db_spec = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-');
  $target_db_spec = drush_sitealias_get_db_spec($destination_settings, FALSE, 'target-');
  $txt_source = (isset($source_db_spec['remote-host']) ? $source_db_spec['remote-host'] . '/' : '') . $source_db_spec['database'];
  $txt_destination = (isset($target_db_spec['remote-host']) ? $target_db_spec['remote-host'] . '/' : '') . $target_db_spec['database'];

  // Validate.
  if (empty($source_db_spec)) {
    if (empty($source_settings)) {
      return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for source !source', array('!source' => $source)));
    }
    return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for source !source', array('!source' => $source)));
  }
  if (empty($target_db_spec)) {
    if (empty($destination_settings)) {
      return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for target !destination', array('!destination' => $destination)));
    }
    return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for target !destination', array('!destination' => $destination)));
  }

  if (drush_get_option('no-dump') && !drush_get_option('source-dump')) {
    return drush_set_error('DRUSH_SOURCE_DUMP_MISSING', dt('The --source-dump option must be supplied when --no-dump is specified.'));
  }

  if (drush_get_option('no-sync') && !drush_get_option('target-dump')) {
    return drush_set_error('DRUSH_TARGET_DUMP_MISSING', dt('The --target-dump option must be supplied when --no-sync is specified.'));
  }

  if (!drush_get_context('DRUSH_SIMULATE')) {
    drush_print(dt("You will destroy data in !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
    // @todo Move sanitization prompts to here. They currently show much later.
    if (!drush_confirm(dt('Do you really want to continue?'))) {
      return drush_user_abort();
    }
  }
}

/*
 * A command callback.
 */
function drush_sqlsync_sql_sync($source, $destination) {
  $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
  $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
  $source_is_local = !array_key_exists('remote-host', $source_settings) || drush_is_local_host($source_settings);
  $destination_is_local = !array_key_exists('remote-host', $destination_settings) || drush_is_local_host($destination_settings);

  // These options are passed along to subcommands like sql-create, sql-dump, sql-query, sql-sanitize, ...
  $source_options = drush_get_merged_prefixed_options('source-');
  $target_options = drush_get_merged_prefixed_options('target-');

  $backend_options = array();
  // @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
  $global_options = drush_redispatch_get_options() + array(
     'strict' => 0,
  );
  // We do not want to include root or uri here.  If the user
  // provided -r or -l, their key has already been remapped to
  // 'root' or 'uri' by the time we get here.
  unset($global_options['root']);
  unset($global_options['uri']);

  if (drush_get_context('DRUSH_SIMULATE')) {
    $backend_options['backend-simulate'] = TRUE;
  }

  // Create destination DB if needed.
  if (drush_get_option('create-db')) {
    drush_log(dt('Starting to create database on Destination.'), LogLevel::OK);
    $return = drush_invoke_process($destination, 'sql-create', array(), $global_options + $target_options, $backend_options);
    if ($return['error_status']) {
      return drush_set_error('DRUSH_SQL_CREATE_FAILED', dt('sql-create failed.'));
    }
  }

  // Perform sql-dump on source unless told otherwise.
  $options = $global_options + $source_options + array(
    'gzip' => TRUE,
    'result-file' => drush_get_option('source-dump', TRUE),
    // 'structure-tables-list' => 'cache*', // Do we want to default to this?
  );
  if (!drush_get_option('no-dump')) {
    drush_log(dt('Starting to dump database on Source.'), LogLevel::OK);
    $return = drush_invoke_process($source, 'sql-dump', array(), $options, $backend_options);
    if ($return['error_status']) {
      return drush_set_error('DRUSH_SQL_DUMP_FAILED', dt('sql-dump failed.'));
    }
    else {
      $source_dump_path = $return['object'];
      if (!is_string($source_dump_path)) {
        return drush_set_error('DRUSH_SQL_DUMP_FILE_NOT_REPORTED', dt('The Drush sql-dump command did not report the path to the dump file produced.  Try upgrading the version of Drush you are using on the source machine.'));
      }
    }
  }
  else {
    $source_dump_path = drush_get_option('source-dump');
  }

  $do_rsync = !drush_get_option('no-sync');
  // Determine path/to/dump on destination.
  if (drush_get_option('target-dump')) {
    $destination_dump_path = drush_get_option('target-dump');
    $rsync_options['yes'] = TRUE;  // @temporary: See https://github.com/drush-ops/drush/pull/555
  }
  elseif ($source_is_local && $destination_is_local) {
    $destination_dump_path = $source_dump_path;
    $do_rsync = FALSE;
  }
  else {
    $tmp = '/tmp'; // Our fallback plan.
    drush_log(dt('Starting to discover temporary files directory on Destination.'), LogLevel::OK);
    $return = drush_invoke_process($destination, 'core-status', array(), array(), array('integrate' => FALSE, 'override-simulated' => TRUE));
    if (!$return['error_status'] && isset($return['object']['drush-temp'])) {
      $tmp = $return['object']['drush-temp'];
    }
    $destination_dump_path = Path::join($tmp, basename($source_dump_path));
    $rsync_options['yes'] = TRUE;  // No need to prompt as destination is a tmp file.
  }

  if ($do_rsync) {
    if (!drush_get_option('no-dump')) {
      // Cleanup if this command created the dump file.
      $rsync_options['remove-source-files'] = TRUE;
    }
    $runner = drush_get_runner($source_settings, $destination_settings, drush_get_option('runner', FALSE));
    // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
    // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
    $return = drush_invoke_process($runner, 'core-rsync', array("$source:$source_dump_path", "$destination:$destination_dump_path"), $rsync_options);
    drush_log(dt('Copying dump file from Source to Destination.'), LogLevel::OK);
    if ($return['error_status']) {
      return drush_set_error('DRUSH_RSYNC_FAILED', dt('core-rsync failed.'));
    }
  }

  // Import file into destination.
  drush_log(dt('Starting to import dump file onto Destination database.'), LogLevel::OK);
  $options = $global_options + $target_options + array(
    'file' => $destination_dump_path,
    'file-delete' => TRUE,
  );
  $return = drush_invoke_process($destination, 'sql-query', array(), $options, $backend_options);
  if ($return['error_status']) {
    // An error was already logged.
    return FALSE;
  }

  // Run Sanitize if needed.
  $options = $global_options + $target_options;
  if (drush_get_option('sanitize')) {
    drush_log(dt('Starting to sanitize target database on Destination.'), LogLevel::OK);
    $return = drush_invoke_process($destination, 'sql-sanitize', array(), $options, $backend_options);
    if ($return['error_status']) {
      return drush_set_error('DRUSH_SQL_SANITIZE_FAILED', dt('sql-sanitize failed.'));
    }
  }
}
<?php

use Drush\Log\LogLevel;
use Drush\User\UserList;
use Drush\User\UserListException;

/**
 * @file
 * Drush User Management commands
 */

function user_drush_help($section) {
  switch ($section) {
    case 'meta:user:title':
      return dt('User commands');
    case 'meta:user:summary':
      return dt('Add, modify and delete users.');
  }
}

/**
 * Implementation of hook_drush_command().
 */
function user_drush_command() {
  $options_common = array(
    'uid' => array(
      'description' => 'A comma delimited list of uids of users to operate on.',
      'example-value' => '3,5',
      'value' => 'required',
    ),
    'name' => array(
      'description' => 'A comma delimited list of user names of users to operate on.',
      'example-value' => 'foo',
      'value' => 'required',
    ),
    'mail' => array(
      'description' => 'A comma delimited list of user mail addresses of users to operate on.',
      'example-value' => 'me@example.com',
      'value' => 'required',
    )
  );

  $items['user-information'] = array(
    'description' => 'Print information about the specified user(s).',
    'aliases' => array('uinf'),
    'examples' => array(
      'drush user-information 2,3,someguy,somegal,billgates@microsoft.com' =>
        'Display information about the listed users.',
    ),
    'arguments' => array(
      'users' => 'A comma delimited list of uids, user names, or email addresses.',
    ),
    'required-arguments' => TRUE,
    'outputformat' => array(
      'default' => 'key-value-list',
      'pipe-format' => 'csv',
      'field-labels' => array(
        'uid' => 'User ID',
        'name' => 'User name',
        'pass' => 'Password',
        'mail' => 'User mail',
        'theme' => 'User theme',
        'signature' => 'Signature',
        'signature_format' => 'Signature format',
        'user_created' => 'User created',
        'created' => 'Created',
        'user_access' => 'User last access',
        'access' => 'Last access',
        'user_login' => 'User last login',
        'login' => 'Last login',
        'user_status' => 'User status',
        'status' => 'Status',
        'timezone' => 'Time zone',
        'picture' => 'User picture',
        'init' => 'Initial user mail',
        'roles' => 'User roles',
        'group_audience' => 'Group Audience',
        'langcode' => 'Language code',
        'uuid' => 'Uuid',
      ),
      'format-cell' => 'csv',
      'fields-default' => array('uid', 'name', 'mail', 'roles', 'user_status'),
      'fields-pipe' => array('name', 'uid', 'mail', 'status', 'roles'),
      'fields-full' => array('uid', 'name', 'pass', 'mail', 'theme', 'signature', 'user_created', 'user_access', 'user_login', 'user_status', 'timezone', 'roles', 'group_audience', 'langcode', 'uuid'),
      'output-data-type' => 'format-table',
    ),
  );
  $items['user-block'] = array(
    'description' => 'Block the specified user(s).',
    'aliases' => array('ublk'),
    'arguments' => array(
      'users' => 'A comma delimited list of uids, user names, or email addresses.',
    ),
    'examples' => array(
      'drush user-block 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
        'Block the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
    ),
    'options' => $options_common,
  );
  $items['user-unblock'] = array(
    'description' => 'Unblock the specified user(s).',
    'aliases' => array('uublk'),
    'arguments' => array(
      'users' => 'A comma delimited list of uids, user names, or email addresses.',
    ),
    'examples' => array(
      'drush user-unblock 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
        'Unblock the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
    ),
    'options' => $options_common,
  );
  $items['user-add-role'] = array(
    'description' => 'Add a role to the specified user accounts.',
    'aliases' => array('urol'),
    'arguments' => array(
      'role' => 'The name of the role to add',
      'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
    ),
    'required-arguments' => 1,
    'examples' => array(
      'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
        'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
    ),
    'options' => $options_common,
  );
  $items['user-remove-role'] = array(
    'description' => 'Remove a role from the specified user accounts.',
    'aliases' => array('urrol'),
    'arguments' => array(
      'role' => 'The name of the role to remove',
      'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
    ),
    'required-arguments' => 1,
    'examples' => array(
      'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
        'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
    ),
    'options' => $options_common,
  );
  $items['user-create'] = array(
    'description' => 'Create a user account with the specified name.',
    'aliases' => array('ucrt'),
    'arguments' => array(
      'name' => 'The name of the account to add'
    ),
    'required-arguments' => TRUE,
    'examples' => array(
      'drush user-create newuser --mail="person@example.com" --password="letmein"' =>
        'Create a new user account with the name newuser, the email address person@example.com, and the password letmein',
    ),
    'options' => array(
      'password' => 'The password for the new account',
      'mail' => 'The email address for the new account',
    ),
    'outputformat' => $items['user-information']['outputformat'],
  );
  $items['user-cancel'] = array(
    'description' => 'Cancel a user account with the specified name.',
    'aliases' => array('ucan'),
    'arguments' => array(
      'name' => 'The name of the account to cancel',
    ),
    // The `_user_cancel` method still references global $user.
    // @todo remove once https://www.drupal.org/node/2163205 is in.
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
    'required-arguments' => TRUE,
    'examples' => array(
      'drush user-cancel username' =>
        'Cancel the user account with the name username and anonymize all content created by that user.',
    ),
  );
  $items['user-password'] = array(
    'description' => '(Re)Set the password for the user account with the specified name.',
    'aliases' => array('upwd'),
    'arguments' => array(
      'name' => 'The name of the account to modify.'
    ),
    'required-arguments' => TRUE,
    'options' => array(
      'password' => array(
        'description' => 'The new password for the account.',
        'required' => TRUE,
        'example-value' => 'foo',
      ),
    ),
    'examples' => array(
      'drush user-password someuser --password="correct horse battery staple"' =>
        'Set the password for the username someuser. @see xkcd.com/936',
    ),
  );
  $items['user-login'] = array(
    'description' => 'Display a one time login link for the given user account (defaults to uid 1).',
    'aliases' => array('uli'),
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'handle-remote-commands' => TRUE,
    'arguments' => array(
      'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.',
      'path' => 'Optional path to redirect to after logging in.',
    ),
    'options' => array(
      'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Use --no-browser to suppress opening a browser.',
      'uid' => 'A uid to log in as.',
      'redirect-port' => 'A custom port for redirecting to (e.g. when running within a Vagrant environment)',
      'name' => 'A user name to log in as.',
      'mail' => 'A user mail address to log in as.',
    ),
    'examples' => array(
      'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.',
      'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.',
    ),
  );
  return $items;
}

/**
 * Implements hook_drush_help_alter().
 */
function user_drush_help_alter(&$command) {
  // Drupal 7+ only options.
  if ($command['command'] == 'user-cancel' && drush_drupal_major_version() >= 7) {
    $command['options']['delete-content'] = 'Delete all content created by the user';
    $command['examples']['drush user-cancel --delete-content username'] =
      'Cancel the user account with the name username and delete all content created by that user.';
  }
}

/**
 * Get a version specific UserSingle class.
 *
 * @param $account
 * @return \Drush\User\UserSingleBase
 *
 * @see drush_get_class().
 */
function drush_usersingle_get_class($account) {
  return drush_get_class('Drush\User\UserSingle', array($account));
}

/**
 * Get a version specific User class.
 *
 * @return \Drush\User\UserVersion
 *
 * @see drush_get_class().
 */
function drush_user_get_class() {
  return drush_get_class('Drush\User\User');
}

/**
 * Command callback. Prints information about the specified user(s).
 */
function drush_user_information($users) {
  $userlist = new UserList($users);
  $info = $userlist->each('info');
  return $info;
}

/**
 * Block the specified user(s).
 */
function drush_user_block($users = '') {
  $userlist = new UserList($users);
  $userlist->each('block');
  drush_log(dt('Blocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
}

/**
 * Unblock the specified user(s).
 */
function drush_user_unblock($users = '') {
  $userlist = new UserList($users);
  $userlist->each('unblock');
  drush_log(dt('Unblocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
}

/**
 * Add a role to the specified user accounts.
 */
function drush_user_add_role($role, $users = '') {
  // If role is not found, an exception gets thrown and handled by command invoke.
  $role_object = drush_role_get_class($role);
  $userlist = new UserList($users);
  $userlist->each('addRole', array($role_object->rid));
  drush_log(dt('Added role !role role to !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS);
}

/**
 * Remove a role from the specified user accounts.
 */
function drush_user_remove_role($role, $users = '') {
  // If role is not found, an exception gets thrown and handled by command invoke.
  $role_object = drush_role_get_class($role);
  $userlist = new UserList($users);
  $userlist->each('removeRole', array($role_object->rid));
  drush_log(dt('Removed !role role from !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS);
}

/**
 * Creates a new user account.
 */
function drush_user_create($name) {
  $userversion = drush_user_get_class();
  $mail = drush_get_option('mail');
  $pass = drush_get_option('password');
  $new_user = array(
    'name' => $name,
    'pass' => $pass,
    'mail' => $mail,
    'access' => '0',
    'status' => 1,
  );
  if (!drush_get_context('DRUSH_SIMULATE')) {
    if ($account = $userversion->create($new_user)) {
      return array($account->id() => $account->info());
    }
    else {
      return drush_set_error("Could not create a new user account with the name " . $name . ".");
    }
  }
}

function drush_user_create_validate($name) {
  $userversion = drush_user_get_class();
  if ($mail = drush_get_option('mail')) {
    if ($userversion->load_by_mail($mail)) {
      return drush_set_error(dt('There is already a user account with the email !mail', array('!mail' => $mail)));
    }
  }
  if ($userversion->load_by_name($name)) {
    return drush_set_error(dt('There is already a user account with the name !name', array('!name' => $name)));
  }
}

/**
 * Cancels a user account.
 */
function drush_user_cancel($inputs) {
  $userlist = new UserList($inputs);
  foreach ($userlist->accounts as $account) {
    if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) {
      drush_print(dt('All content created by !name will be deleted.', array('!name' => $account->getUsername())));
    }
    if (drush_confirm('Cancel user account?: ')) {
      $account->cancel();
    }
  }
  drush_log(dt('Cancelled user(s): !users', array('!users' => $userlist->names())),LogLevel::SUCCESS);
}

/**
 * Sets the password for the account with the given username
 */
function drush_user_password($inputs) {
  $userlist = new UserList($inputs);
  if (!drush_get_context('DRUSH_SIMULATE')) {
    $pass = drush_get_option('password');
    // If no password has been provided, prompt for one.
    if (empty($pass)) {
      $pass = drush_prompt(dt('Password'), NULL, TRUE, TRUE);
    }
    foreach ($userlist->accounts as $account) {
      $userlist->each('password', array($pass));
    }
    drush_log(dt('Changed password for !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
  }
}

/**
 * Displays a one time login link for the given user.
 */
function drush_user_login($inputs = '', $path = NULL) {
  $args = func_get_args();

  // Redispatch if called against a remote-host so a browser is started on the
  // the *local* machine.
  $alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
  if (drush_sitealias_is_remote_site($alias)) {
    $return = drush_invoke_process($alias, 'user-login', $args, drush_redispatch_get_options(), array('integrate' => FALSE));
    if ($return['error_status']) {
      return drush_set_error('Unable to execute user login.');
    }
    else {
      // Prior versions of Drupal returned a string so cast to an array if needed.
      $links = is_string($return['object']) ? array($return['object']) : $return['object'];
    }
  }
  else {
    if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      // Fail gracefully if unable to bootstrap Drupal.
      // drush_bootstrap() has already logged an error.
      return FALSE;
    }

    if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) {
      // If we only have a single argument and one of the user options is passed,
      // then we assume the argument is the path to open.
      if (count($args) == 1) {
        $path = $args[0];
      }
    }

    // Try to load a user from provided options and arguments.
    try {
      $userlist = new UserList($inputs);
    }
    catch (UserListException $e) {
      // No user option or argument was passed, so we default to uid 1.
        $userlist = new UserList(1);
    }
    $links = $userlist->each('passResetUrl', array($path));
  }
  $port = drush_get_option('redirect-port', FALSE);
  // There is almost always only one link so pick the first one for display and browser.
  // The full array is sent on backend calls.
  $first = current($links);
  drush_start_browser($first, FALSE, $port);
  drush_backend_set_result($links);
  return $first;
}
<?php

/**
 * @file
 */

use Drush\Log\LogLevel;

define('XH_PROFILE_MEMORY', FALSE);

define('XH_PROFILE_CPU', FALSE);

define('XH_PROFILE_BUILTINS', TRUE);

/**
 * Implements hook_drush_help_alter().
 */
function xh_drush_help_alter(&$command) {
  if ($command['command'] == 'global-options') {
    // Do not include these in options in standard help.
    if ($command['#brief'] === FALSE) {
      $command['options']['xh'] = array(
        'description' => 'Enable profiling via XHProf',
      );
      $command['sub-options']['xh']['xh-link'] = 'URL to your XHProf report site.';
      $command['sub-options']['xh']['xh-path'] = 'Absolute path to the xhprof project.';
      $command['sub-options']['xh']['xh-profile-builtins'] = 'Profile built-in PHP functions (defaults to TRUE).';
      $command['sub-options']['xh']['xh-profile-cpu'] = 'Profile CPU (defaults to FALSE).';
      $command['sub-options']['xh']['xh-profile-memory'] = 'Profile Memory (defaults to FALSE).';
    }
  }
}

function xh_enabled() {
  if (drush_get_option('xh')) {
    if (!extension_loaded('xhprof')) {
      return drush_set_error('You must enable the xhprof PHP extension in your CLI PHP in order to profile.');
    }
    if (!drush_get_option('xh-path', '')) {
      return drush_set_error('You must provide the xh-path option in your drushrc or on the CLI in order to profile.');
    }
    return TRUE;
  }
}

/**
 * Determines flags for xhprof_enable based on drush options.
 */
function xh_flags() {
  $flags = 0;
  if (!drush_get_option('xh-profile-builtins', XH_PROFILE_BUILTINS)) {
    $flags |= XHPROF_FLAGS_NO_BUILTINS;
  }
  if (drush_get_option('xh-profile-cpu', XH_PROFILE_CPU)) {
    $flags |= XHPROF_FLAGS_CPU;
  }
  if (drush_get_option('xh-profile-memory', XH_PROFILE_MEMORY)) {
    $flags |= XHPROF_FLAGS_MEMORY;
  }
  return $flags;
}

/**
 * Implements hook_drush_init().
 */
function xh_drush_init() {
  if (xh_enabled()) {
    if ($path = drush_get_option('xh-path', '')) {
      include_once $path . '/xhprof_lib/utils/xhprof_lib.php';
      include_once $path . '/xhprof_lib/utils/xhprof_runs.php';
      xhprof_enable(xh_flags());
    }
  }
}

/**
 * Implements hook_drush_exit().
 */
function xh_drush_exit() {
  if (xh_enabled()) {
    $args = func_get_args();
    $namespace = 'Drush'; // variable_get('site_name', '');
    $xhprof_data = xhprof_disable();
    $xhprof_runs = new XHProfRuns_Default();
    $run_id =  $xhprof_runs->save_run($xhprof_data, $namespace);
    if ($url = xh_link($run_id)) {
      drush_log(dt('XHProf run saved. View report at !url', array('!url' => $url)), LogLevel::OK);
    }
  }
}

/**
 * Returns the XHProf link.
 */
function xh_link($run_id) {
  if ($xhprof_url = trim(drush_get_option('xh-link'))) {
    $namespace = 'Drush'; //variable_get('site_name', '');
    return $xhprof_url . '/index.php?run=' . urlencode($run_id) . '&source=' . urlencode($namespace);
  }
  else {
    drush_log('Configure xh_link in order to see a link to the XHProf report for this request instead of this message.');
  }
}
# Remote Operations on Drupal Sites via a Bastion Server

Wikipedia defines a [bastion server](http://en.wikipedia.org/wiki/Bastion_host) as "a special purpose computer on a network specifically designed and configured to withstand attacks." For the purposes of this documentation, though, any server that you can ssh through to reach other servers will do. Using standard ssh and Drush techniques, it is possible to make a two-hop remote command look and act as if the destination machine is on the same network as the source machine.

## Recap of Remote Site Aliases

Site aliases can refer to Drupal sites that are running on remote machines simply including 'remote-host' and 'remote-user' attributes:

    $aliases['internal'] = array(
        'remote-host' => 'internal.company.com',
        'remote-user' => 'wwwadmin',
        'uri' => 'http://internal.company.com',
        'root' => '/path/to/remote/drupal/root',
    );

With this alias defintion, you may use commands such as `drush @internal status`, `drush ssh @internal` and `drush rsync @internal @dev` to operate remotely on the internal machine. What if you cannot reach the server that site is on from your current network? Enter the bastion server.

## Setting up a Bastion server in .ssh/config

If you have access to a server, bastion.company.com, which you can ssh to from the open internet, and if the bastion server can in turn reach the internal server, then it is possible to configure ssh to route all traffic to the internal server through the bastion. The .ssh configuration file would look something like this:

In **.ssh/config:**

    Host internal.company.com
        ProxyCommand ssh user@bastion.company.com nc %h %p

That is all that is necessary; however, if the dev machine you are configuring is a laptop that might sometimes be inside the company intranet, you might want to optimize this setup so that the bastion is not used when the internal server can be reached directly. You could do this by changing the contents of your .ssh/config file when your network settings change -- or you could use Drush.

# Setting up a Bastion server via Drush configuration

First, make sure that you do not have any configuration options for the internal machine in your .ssh/config file. The next step after that is to identify when you are connected to your company intranet. I like to determine this by using the `route` command to find my network gateway address, since this is always the same on my intranet, and unlikely to be encountered in other places.

In **drushrc.php:**

    # Figure out if we are inside our company intranet by testing our gateway address against a known value
    exec("route -n | grep '^0\.0\.0\.0' | awk '{ print $2; }' 2> /dev/null", $output);
    if ($output[0] == '172.30.10.1') {
      drush_set_context('MY_INTRANET', TRUE);
    }

After this code runs, the 'MY\_INTRANET' context will be set if our gateway IP address matches the expected value, and unset otherwise. We can make use of this in our alias files.

In **aliases.drushrc.php:**

    if (drush_get_context('MY_INTRANET', FALSE) === FALSE) {
      $aliases['intranet-proxy'] = array(
        'ssh-options' => ' -o "ProxyCommand ssh user@bastion.company.com nc %h %p"',
      );
    }
    $aliases['internal-server'] = array(
      'parent' => '@intranet-proxy',
      'remote-host' => 'internal.company.com',
      'remote-user' => 'wwwadmin',
    );
    $aliases['internal'] = array(
      'parent' => '@internal-server',
      'uri' => 'http://internal.company.com',
      'root' => '/path/to/remote/drupal/root',
    );

The 'parent' term of the internal-server alias record is ignored if the alias it references ('@intranet-proxy') is not defined; the result is that 'ssh-options' will only be defined when outside of the intranet, and the ssh ProxyCommand to the bastion server will only be included when it is needed.

With this setup, you will be able to use your site alias '@internal' to remotely operate on your internal intranet Drupal site seemlessly, regardless of your location -- a handy trick indeed.

The Drush Bootstrap Process
===========================
When preparing to run a command, Drush works by "bootstrapping" the Drupal environment in very much the same way that is done during a normal page request from the web server, so most Drush commands run in the context of a fully-initialized website.

For efficiency and convenience, some Drush commands can work without first bootstrapping a Drupal site, or by only partially bootstrapping a site. This is faster than a full bootstrap. It is also a matter of convenience, because some commands are useful even when you don't have a working Drupal site. For example, you can use Drush to download Drupal with `drush dl drupal`. This obviously does not require any bootstrapping to work.

DRUSH\_BOOTSTRAP\_NONE
-----------------------
Only run Drush _preflight_, without considering Drupal at all. Any code that operates on the Drush installation, and not specifically any Drupal directory, should bootstrap to this phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT
------------------------------
Set up and test for a valid Drupal root, either through the --root options, or evaluated based on the current working directory. Any code that interacts with an entire Drupal installation, and not a specific site on the Drupal installation should use this bootstrap phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_SITE
------------------------------
Set up a Drupal site directory and the correct environment variables to allow Drupal to find the configuration file. If no site is specified with the --uri options, Drush will assume the site is 'default', which mimics Drupal's behaviour.  Note that it is necessary to specify a full URI, e.g. --uri=http://example.com, in order for certain Drush commands and Drupal modules to behave correctly. See the [example Config file](../examples/example.drushrc.php) for more information. Any code that needs to modify or interact with a specific Drupal site's settings.php file should bootstrap to this phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION
---------------------------------------
Load the settings from the Drupal sites directory. This phase is analagous to the DRUPAL\_BOOTSTRAP\_CONFIGURATION bootstrap phase in Drupal itself, and this is also the first step where Drupal specific code is included. This phase is commonly used for code that interacts with the Drupal install API, as both install.php and update.php start at this phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE
----------------------------------
Connect to the Drupal database using the database credentials loaded during the previous bootstrap phase. This phase is analogous to the DRUPAL\_BOOTSTRAP\_DATABASE bootstrap phase in Drupal. Any code that needs to interact with the Drupal database API needs to be bootstrapped to at least this phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_FULL
------------------------------
Fully initialize Drupal. This is analogous to the DRUPAL\_BOOTSTRAP\_FULL bootstrap phase in Drupal. Any code that interacts with the general Drupal API should be bootstrapped to this phase.

DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN
-------------------------------
Log in to the initialiazed Drupal site. This bootstrap phase is used after the site has been fully bootstrapped. This is the default bootstrap phase all commands will try to reach, unless otherwise specified. This phase will log you in to the drupal site with the username or user ID specified by the --user/ -u option(defaults to 0, anonymous). Use this bootstrap phase for your command if you need to have access to information for a specific user, such as listing nodes that might be different based on who is logged in.

DRUSH\_BOOTSTRAP\_MAX
---------------------
This is not an actual bootstrap phase. Commands that use DRUSH\_BOOTSTRAP\_MAX will cause Drush to bootstrap as far as possible, and then run the command regardless of the bootstrap phase that was reached. This is useful for Drush commands that work without a bootstrapped site, but that provide additional information or capabilities in the presence of a bootstrapped site. For example, `drush pm-releases modulename` works without a bootstrapped Drupal site, but will include the version number for the installed module if a Drupal site has been bootstrapped.

Creating Custom Drush Commands
==============================

Creating a new Drush command is very easy. Follow these simple steps:

1.  Create a command file called COMMANDFILE.drush.inc
1.  Implement the function COMMANDFILE\_drush\_command()
1.  Implement the functions that your commands will call. These will usually be named drush\_COMMANDFILE\_COMMANDNAME().

For an example Drush command, see examples/sandwich.drush.inc. The steps for implementing your command are explained in more detail below.

Create COMMANDFILE.drush.inc
----------------------------

The name of your Drush command is very important. It must end in ".drush.inc" to be recognized as a Drush command. The part of the filename that comes before the ".drush.inc" becomes the name of the commandfile. Optionally, the commandfile may be restricted to a particular version of Drupal by adding a ".dVERSION" after the name of the commandfile (e.g. ".d8.drush.inc") Your commandfile name is used by Drush to compose the names of the functions it will call, so choose wisely.

The example Drush command, 'make-me-a-sandwich', is stored in the 'sandwich' commandfile, 'sandwich.Drush.inc'. You can find this file in the 'examples' directory in the Drush distribution.

Drush searches for commandfiles in the following locations:

-   Folders listed in the 'include' option (see `drush topic docs-configuration`).
-   The system-wide Drush commands folder, e.g. /usr/share/drush/commands
-   The ".drush" folder in the user's HOME folder.
-   /drush and /sites/all/drush in the current Drupal installation
-   All enabled modules in the current Drupal installation
-   Folders and files containing other versions of Drush in their names will be \*skipped\* (e.g. devel.drush4.inc or drush4/devel.drush.inc). Names containing the current version of Drush (e.g. devel.drush5.inc) will be loaded.

Note that modules in the current Drupal installation will only be considered if Drush has bootstrapped to at least the DRUSH\_BOOSTRAP\_SITE level. Usually, when working with a Drupal site, Drush will bootstrap to DRUSH\_BOOTSTRAP\_FULL; in this case, only the Drush commandfiles in enabled modules will be considered eligible for loading. If Drush only bootstraps to DRUSH\_BOOTSTRAP\_SITE, though, then all Drush commandfiles will be considered, whether the module is enabled or not. See `drush topic docs-bootstrap` for more information on bootstrapping.

Implement COMMANDFILE\_drush\_command()
---------------------------------------

The drush\_command hook is the most important part of the commandfile. It returns an array of items that define how your commands should be called, and how they work. Drush commands are very similar to the Drupal menu system. The elements that can appear in a Drush command definition are shown below.

-   **aliases**: Provides a list of shorter names for the command. For example, pm-download may also be called via `drush dl`. If the alias is used, Drush will substitute back in the primary command name, so pm-download will still be used to generate the command hook, etc.
-   **command-hook**: Change the name of the function Drush will call to execute the command from drush\_COMMANDFILE\_COMMANDNAME() to drush\_COMMANDFILE\_COMMANDHOOK(), where COMMANDNAME is the original name of the command, and COMMANDHOOK is the value of the 'command-hook' item.
-   **callback**: Name of function to invoke for this command. The callback function name \_must\_ begin with "drush\_commandfile\_", where commandfile is from the file "commandfile.drush.inc", which contains the commandfile\_drush\_command() function that returned this command. Note that the callback entry is optional; it is preferable to omit it, in which case drush\_invoke() will generate the hook function name.
-   **callback arguments**: An array of arguments to pass to the callback. The command line arguments, if any, will appear after the callback arguments in the function parameters.
-   **description**: Description of the command.
-   **arguments**: An array of arguments that are understood by the command. Used by `drush help` only.
-   **required-arguments**: Defaults to FALSE; TRUE if all of the arguments are required. Set to an integer count of required arguments if only some are required.
-   **options**: An array of options that are understood by the command. Any option that the command expects to be able to query via drush\_get\_option \_must\_ be listed in the options array. If it is not, users will get an error about an "Unknown option" when they try to specify the option on the command line.

    The value of each option may be either a simple string containing the option description, or an array containing the following information:

    -   **description**: A description of the option.
    -   **example-value**: An example value to show in help.
    -   **value**: optional|required.
    -   **required**: Indicates that an option must be provided.
    -   **hidden**: The option is not shown in the help output (rare).

-   **allow-additional-options**: If TRUE, then the strict validation to see if options exist is skipped. Examples of where this is done includes the core-rsync command, which passes options along to the rsync shell command. This item may also contain a list of other commands that are invoked as subcommands (e.g. the pm-update command calls pm-updatecode and updatedb commands). When this is done, the options from the subcommand may be used on the commandline, and are also listed in the command's `help` output. Defaults to FALSE.
-   **examples**: An array of examples that are understood by the command. Used by `drush help` only.
-   **scope**: One of 'system', 'project', 'site'. Not currently used.
-   **bootstrap**: Drupal bootstrap level. More info at `drush topic docs-bootstrap`. Valid values are:
    -   DRUSH\_BOOTSTRAP\_NONE
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_SITE
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_FULL
    -   DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN (default)
    -   DRUSH\_BOOTSTRAP\_MAX
-   **core**: Drupal major version required. Append a '+' to indicate 'and later versions.'
-   **drupal dependencies**: Drupal modules required for this command.
-   **drush dependencies**: Other Drush commandfiles required for this command.
-   **engines**: Provides a list of Drush engines to load with this command. The set of appropriate engines varies by command.
    -   **outputformat**: One important engine is the 'outputformat' engine. This engine is responsible for formatting the structured data (usually an associative array) that a Drush command returns as its function result into a human-readable or machine-parsable string. Some of the options that may be used with output format engines are listed below; however, each specific output format type can take additional option items that control the way that the output is rendered. See the comment in the output format's implementation for information. The Drush core output format engines can be found in commands/core/outputformat.
        -   **default**: The default type to render output as. If declared, the command should not print any output on its own, but instead should return a data structure (usually an associative array) that can be rendered by the output type selected.
        -   **pipe-format**: When the command is executed in --pipe mode, the command output will be rendered by the format specified by the pipe-format item instead of the default format. Note that in either event, the user may specify the format to use via the --format command-line option.
        -   **formatted-filter** and **pipe-filter**: Specifies a function callback that will be used to filter the command result. The filter is selected based on the type of output format object selected. Most output formatters are 'pipe' formatters, that produce machine-parsable output. A few formatters, such as 'table' and 'key-value' are 'formatted' filter types, that produce human-readable output.
-   **topics**: Provides a list of topic commands that are related in some way to this command. Used by `drush help`.
-   **topic**: Set to TRUE if this command is a topic, callable from the `drush docs-topics` command.
-   **category**: Set this to override the category in which your command is listed in help.

The 'sandwich' drush\_command hook looks like this:

            function sandwich_drush_command() {
              $items = array();

              $items['make-me-a-sandwich'] = array(
                'description' => "Makes a delicious sandwich.",
                'arguments' => array(
                  'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
                ),
                'options' => array(
                  'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
                ),
                'examples' => array(
                  'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
                ),
                'aliases' => array('mmas'),
                'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
              );

              return $items;
            }

Most of the items in the 'make-me-a-sandwich' command definition have no effect on execution, and are used only by `drush help`. The exceptions are 'aliases' (described above) and 'bootstrap'. As previously mentioned, `drush topic docs-bootstrap` explains the Drush bootstrapping process in detail.

Implement drush\_COMMANDFILE\_COMMANDNAME()
-------------------------------------------

The 'make-me-a-sandwich' command in sandwich.drush.inc is defined as follows:

        function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
          // implementation here ...
        }

If a user runs `drush make-me-a-sandwich` with no command line arguments, then Drush will call drush\_sandwich\_make\_me\_a\_sandwich() with no function parameters; in this case, $filling will take on the provided default value, 'ascii'. (If there is no default value provided, then the variable will be NULL, and a warning will be printed.) Running `drush make-me-a-sandwich ham` will cause Drush to call drush\_sandwich\_make\_me\_a\_sandwich('ham'). In the same way, commands that take two command line arguments can simply define two functional parameters, and a command that takes a variable number of command line arguments can use the standard php function func\_get\_args() to get them all in an array for easy processing.

It is also very easy to query the command options using the function drush\_get\_option(). For example, in the drush\_sandwich\_make\_me\_a\_sandwich() function, the --spreads option is retrieved as follows:

            $str_spreads = '';
            if ($spreads = drush_get_option('spreads')) {
              $list = implode(' and ', explode(',', $spreads));
              $str_spreads = ' with just a dash of ' . $list;
            }

Note that Drush will actually call a sequence of functions before and after your Drush command function. One of these hooks is the "validate" hook. The 'sandwich' commandfile provides a validate hook for the 'make-me-a-sandwich' command:

            function drush_sandwich_make_me_a_sandwich_validate() {
              $name = posix_getpwuid(posix_geteuid());
              if ($name['name'] !== 'root') {
                return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
              }
            }

The validate function should call drush\_set\_error() and return its result if the command cannot be validated for some reason. See `drush topic docs-policy` for more information on defining policy functions with validate hooks, and `drush topic docs-api` for information on how the command hook process works. Also, the list of defined drush error codes can be found in `drush topic docs-errorcodes`.

To see the full implementation of the sample 'make-me-a-sandwich' command, see `drush topic docs-examplecommand`.

# Exporting and Importing Configuration

Drush provides commands to export, transfer, and import configuration files
to and from a Drupal 8 site.  Configuration can be altered by different
methods in order to provide different behaviors in different environments;
for example, a development server might be configured slightly differently
than the production server.

This document describes how to make simple value changes to configuration
based on the environment, how to have a different set of enabled modules
in different configurations without affecting your exported configuration
values, and how to make more complex changes.

## Simple Value Changes

It is not necessary to alter the configuration system values to 
make simple value changes to configuration variables, as this may be
done by the [configuration override system](https://www.drupal.org/node/1928898).

The configuration override system allows you to change configuration
values for a given instance of a site (e.g. the development server) by
setting configuration variables in the site's settings.php file.
For example, to change the name of a local development site:
```
$config['system.site']['name'] = 'Local Install of Awesome Widgets, Inc.';
```
Note that the configuration override system is a Drupal feature, not
a Drush feature. It should be the preferred method for changing
configuration values on a per-environment basis; however, it does not
work for some things, such as enabling and disabling modules.  For
configuration changes not handled by the configuration override system,
you can use configuration filters of the Config Filter module.

## Ignoring Development Modules

Use the [Config Split](https://www.drupal.org/project/config_split) module to
split off development configuration in a dedicated config directory.Drush Contexts
==============

The drush contexts API acts as a storage mechanism for all options, arguments and configuration settings that are loaded into drush.

This API also acts as an IPC mechanism between the different drush commands, and provides protection from accidentally overriding settings that are needed by other parts of the system.

It also avoids the necessity to pass references through the command chain and allows the scripts to keep track of whether any settings have changed since the previous execution.

This API defines several contexts that are used by default.

Argument contexts
-----------------

These contexts are used by Drush to store information on the command. They have their own access functions in the forms of drush\_set\_arguments(), drush\_get\_arguments(), drush\_set\_command(), drush\_get\_command().

-   command : The drush command being executed.
-   arguments : Any additional arguments that were specified.

Setting contexts
----------------

These contexts store options that have been passed to the drush.php script, either through the use of any of the config files, directly from the command line through --option='value' or through a JSON encoded string passed through the STDIN pipe.

These contexts are accessible through the drush\_get\_option() and drush\_set\_option() functions. See drush\_context\_names() for a description of all of the contexts.

Drush commands may also choose to save settings for a specific context to the matching configuration file through the drush\_save\_config() function.

Available Setting contexts
--------------------------

These contexts are evaluated in a certain order, and the highest priority value is returned by default from drush\_get\_option. This allows scripts to check whether an option was different before the current execution.

Specified by the script itself :

-   process : Generated in the current process.
-   cli : Passed as --option=value to the command line.
-   stdin : Passed as a JSON encoded string through stdin.
-   alias : Defined in an alias record, and set in the alias context whenever that alias is used.
-   specific : Defined in a command-specific option record, and set in the command context whenever that command is used.

Specified by config files :

-   custom : Loaded from the config file specified by --config or -c
-   site : Loaded from the drushrc.php file in the Drupal site directory.
-   drupal : Loaded from the drushrc.php file in the Drupal root directory.
-   user : Loaded from the drushrc.php file in the user's home directory.
-   drush : Loaded from the drushrc.php file in the $HOME/.drush directory.
-   system : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory.
-   drush : Loaded from the drushrc.php file in the same directory as drush.php.

Specified by the script, but has the lowest priority :

-   default : The script might provide some sensible defaults during init.

Running Drupal cron tasks from Drush
====================================

Drupal cron tasks are often set up to be run via a wget call to cron.php; this same task can also be accomplished via the `drush cron` command, which circumvents the need to provide a webserver interface to cron.

Quick start
----------

If you just want to get started quickly, here is a crontab entry that will run cron once every hour at ten minutes after the hour:

    10 * * * * /usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin COLUMNS=72 /usr/local/bin/drush --root=/path/to/your/drupalroot --uri=your.drupalsite.org --quiet cron

You should set up crontab to run your cron tasks as the same user that runs the web server; for example, if you run your webserver as the user www-data:

    sudo crontab -u www-data -e

You might need to edit the crontab entry shown above slightly for your particular setup; for example, if you have installed Drush to some directory other than /usr/local/drush, then you will need to adjust the path to drush appropriately. We'll break down the meaning of each section of the crontab entry in the documentation that continues below.

Setting the schedule
--------------------

See `man 5 crontab` for information on how to format the information in a crontab entry. In the example above, the schedule for the crontab is set by the string `10 * * * *`. These fields are the minute, hour, day of month, month and day of week; `*` means essentially 'all values', so `10 * * * *` will run any time the minute == 10 (once every hour).

Setting the PATH
----------------

We use /usr/bin/env to run Drush so that we can set up some necessary environment variables that Drush needs to execute. By default, cron will run each command with an empty PATH, which would not work well with Drush. To find out what your PATH needs to be, just type:

    echo $PATH

Take the value that is output and place it into your crontab entry in the place of the one shown above. You can remove any entry that is known to not be of interest to Drush (e.g. /usr/games), or is only useful in a graphic environment (e.g. /usr/X11/bin).

Setting COLUMNS
---------------

When running Drush in a terminal, the number of columns will be automatically determined by Drush by way of the tput command, which queries the active terminal to determine what the width of the screen is. When running Drush from cron, there will not be any terminal set, and the call to tput will produce an error message. Spurrious error messages are undesirable, as cron is often configured to send email whenever any output is produced, so it is important to make an effort to insure that successful runs of cron complete with no output.

In some cases, Drush is smart enough to recognize that there is no terminal -- if the terminal value is empty or "dumb", for example. However, there are some "non-terminal" values that Drush does not recognize, such as "unknown." If you manually set `COLUMNS`, then Drush will repect your setting and will not attempt to call tput.

Using --quiet
-------------

By default, Drush will print a success message when the run of cron is completed. The --quiet flag will suppress these and other progress messages, again avoiding an unnecessary email message.

Specifying the Drupal site to run
---------------------------------

There are many ways to tell Drush which Drupal site to select for the active command, and any may be used here. The example uses the --root and --uri flags, but you could also use an alias record.

The _examples_ folder contains example files which you may copy and edit as needed. Read the documentation right in the file. If you see an opportunity to improve the file, please submit a pull request.

* [drush.wrapper](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/drush.wrapper). A handy launcher script which calls the Drush located in vendor/bin/drush and can add options like --local, --root, etc.
* [example.aliases.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.aliases.drushrc.php). Example site alias definitions.
* [example.bashrc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.bashrc). Enhance your shell with lots of Drush niceties including bash completion.
* [example.drush.ini](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drush.ini). Configure your PHP just for Drush requests.
* [example.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drushrc.php). A Drush configuration file.
* [example.make](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.make). An ini style make file.
* [example.make.yml](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.make.yml). A YML make file. 
* [example.prompt.sh](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.prompt.sh). Displays Git repository and Drush alias status in your prompt.
* [git-bisect.example.sh](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/git-bisect.example.sh). Spelunking through Drush's git history with bisect.
* [helloworld.script](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/helloworld.script). An example Drush script. 
* [pm_update.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/pm_update.drush.inc). Restore sqlsrv driver after core update.
* [policy.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/policy.drush.inc). A policy file can disallow prohibited commands/options etc.
* [sandwich.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sandwich.drush.inc). A fun example command inspired by a famous XKCD comic.
* [sync_via_http.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sync_via_http.drush.inc). sql-sync modification that transfers via http instead of rsync. 
* [sync_enable.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/sync_enable.drush.inc). Automatically enable modules after a sql-sync.
* [xkcd.drush.inc](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/xkcd.drush.inc). A fun example command that browses XKCD comics.  
Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654).

[![Latest Stable Version](https://poser.pugx.org/drush/drush/v/stable.png)](https://packagist.org/packages/drush/drush) [![Total Downloads](https://poser.pugx.org/drush/drush/downloads.png)](https://packagist.org/packages/drush/drush) [![Latest Unstable Version](https://poser.pugx.org/drush/drush/v/unstable.png)](https://packagist.org/packages/drush/drush) [![License](https://poser.pugx.org/drush/drush/license.png)](https://packagist.org/packages/drush/drush)

Resources
-----------
* [Install documentation](http://docs.drush.org/en/8.x/install/)
* [General documentation](http://docs.drush.org)
* [API Documentation](http://api.drush.org)
* [Drush Commands](http://drushcommands.com)
* Subscribe [this atom feed](https://github.com/drush-ops/drush/releases.atom) to receive notification on new releases. Also, [Version eye](https://www.versioneye.com/).
* [Drush packages available via Composer](http://packages.drush.org)
* [A list of modules that include Drush integration](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654&solrsort=ds_project_latest_release+desc)
* Drush comes with a [full test suite](https://github.com/drush-ops/drush/blob/8.x/tests/README.md) powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush).

Support
-----------

Please take a moment to review the rest of the information in this file before
pursuing one of the support options below.

* Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush).
* Bug reports and feature requests should be reported in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues).
* Use pull requests (PRs) to contribute to Drush.
* It is still possible to search the old issue queue on Drupal.org for [fixed bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=7&categories%5B%5D=bug), [unmigrated issues](https://drupal.org/project/issues/search/drush?status%5B%5D=5&issue_tags=needs+migration), [unmigrated bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=5&categories%5B%5D=bug&issue_tags=needs+migration), and so on.

FAQ
------

##### What does the name Drush mean?
The Drupal Shell.

##### How do I pronounce Drush?
  
Some people pronounce the dru with a long u like Drupal. Fidelity points go to them, but they are in the minority. Most pronounce Drush so that it rhymes with hush, rush, flush, etc. This is the preferred pronunciation.

##### Does Drush have unit tests?
  
Drush has an excellent suite of unit tests. See [tests/README.md](https://github.com/drush-ops/drush/blob/8.x/tests/README.md) for more information.


Credits
-----------

* Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7.
* Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5.
* Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from
  Owen Barton, greg.1.anderson, jonhattan, Mark Sonnabaum, Jonathan Hedstrom and
  [Christopher Gervais](http://drupal.org/u/ergonlogic).
Install a global Drush via Composer
------------------
Follow the instructions below, or [watch a video by Drupalize.me](https://youtu.be/eAtDaD8xz0Q).

1. [Install Composer globally](https://getcomposer.org/doc/00-intro.md#globally).
1. Add composer's `bin` directory to the system path by placing `export PATH="$HOME/.composer/vendor/bin:$PATH"` into your ~/.bash_profile (Mac OS users) or into your ~/.bashrc (Linux users).		
1. Install latest stable Drush: `composer global require drush/drush`.
1. Verify that Drush works: `drush status`

#### Notes
* Update to latest release (per your specification in ~/.composer/composer.json): `composer global update`
* Install a specific version of Drush:

        # Install a specific version of Drush, e.g. Drush 7.1.0
        composer global require drush/drush:7.1.0

        # Install 8.x branch as a git clone. Great for contributing back to Drush project.
        composer global require drush/drush:8.x-dev --prefer-source

* Alternate way to install for all users via Composer:

        COMPOSER_HOME=/opt/drush COMPOSER_BIN_DIR=/usr/local/bin COMPOSER_VENDOR_DIR=/opt/drush/7 composer require drush/drush:7

* [Documentation for composer's require command.](http://getcomposer.org/doc/03-cli.md#require)
* Uninstall with : `composer global remove drush/drush`

Windows
------------
Drush on Windows is experimental, since Drush's test suite is not running there ([help wanted](https://github.com/drush-ops/drush/issues/1612)).

* [Acquia Dev Desktop](https://www.acquia.com/downloads) is excellent, and includes Drush. See the terminal icon after setting up a web site.
* Or consider running Linux/OSX via Virtualbox. [Drupal VM](http://www.drupalvm.com/) and [Vlad](https://github.com/hashbangcode/vlad) are popular.* These Windows packages include Drush and its dependencies (including MSys).     * [7.0.0 (stable)](https://github.com/drush-ops/drush/releases/download/7.0.0/windows-7.0.0.zip).    * [6.6.0](https://github.com/drush-ops/drush/releases/download/6.6.0/windows-6.6.0.zip).    * [6.0](https://github.com/drush-ops/drush/releases/download/6.0.0/Drush-6.0-2013-08-28-Installer-v1.0.21.msi).
* Or install LAMP on your own, and run Drush via [Git's shell](https://git-for-windows.github.io/), in order to insure that [all depedencies](https://github.com/acquia/DevDesktopCommon/tree/8.x/bintools-win/msys/bin) are available.   
* When creating site aliases for Windows remote machines, pay particular attention to information presented in the example.aliases.drushrc.php file, especially when setting values for 'remote-host' and 'os', as these are very important when running Drush rsync and Drush sql-sync commands.
Install/Upgrade a global Drush
---------------
```bash    
# Browse to https://github.com/drush-ops/drush/releases and download the drush.phar attached to the latest 8.x release.

# Test your install.
php drush.phar core-status

# Rename to `drush` instead of `php drush.phar`. Destination can be anywhere on $PATH. 
chmod +x drush.phar
sudo mv drush.phar /usr/local/bin/drush

# Optional. Enrich the bash startup file with completion and aliases.
drush init
```
    
* MAMP users, and anyone wishing to launch a non-default PHP, needs to [edit ~/.bashrc so that the right PHP is in your $PATH](http://stackoverflow.com/questions/4145667/how-to-override-the-path-of-php-to-use-the-mamp-path/10653443#10653443).
* We have documented [alternative ways to install](http://docs.drush.org/en/8.x/install-alternative/), including [Windows](http://docs.drush.org/en/8.x/install-alternative/#windows).
* If you need to pass custom php.ini values, run `php -d foo=bar drush.phar --php-options=foo=bar`
* Your shell now has [useful bash aliases and tab completion for command names, site aliases, options, and arguments](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.bashrc).
* A [drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.drushrc.php) has been copied to ~/.drush above. Customize it to save typing and standardize options for commands.
* Upgrade using this same procedure.

Install a site-local Drush
-----------------
In addition to the global Drush, it is recommended that Drupal 8 sites be [built using Composer, with Drush listed as a dependency](https://github.com/drupal-composer/drupal-project).

1. When you run `drush`, the global Drush is called first and then hands execution to the site-local Drush. This gives you the convenience of running `drush` without specifying the full path to the executable, without sacrificing the safety provided by a site-local Drush.
2. Optional: Copy the [examples/drush.wrapper](https://github.com/drush-ops/drush/blob/8.x/examples/drush.wrapper) file to your project root and modify to taste. This is a handy launcher script; add --local here to turn off all global configuration locations, and maintain consistency over configuration/aliases/commandfiles for your team.
3. Note that if you have multiple Drupal sites on your system, it is possible to use a different version of Drush with each one.

Drupal Compatibility
-----------------
!!! note

    Drush 9 only supports one install method. It requires that your Drupal 8 site be built with Composer and Drush be listed as a dependency. 
    
    See the [Drush 8 docs](http://docs.drush.org/en/8.x) for installing prior versions of Drush.

Install a site-local Drush and Drush Launcher.
-----------------
1. It is recommended that Drupal 8 sites be [built using Composer, with Drush listed as a dependency](https://github.com/drupal-composer/drupal-project). That project already includes Drush in its composer.json. If your Composer project doesn't yet depend on Drush, run `composer require drush/drush` to add it. 
1. To be able to call `drush` from anywhere, install the [Drush Launcher](https://github.com/drush-ops/drush-launcher). That is a small program which listens on your $PATH and hands control to a site-local Drush that is in the /vendor directory of your Composer project. If you skip this step, run Drush from Drupal root via `../vendor/bin/drush`. In that case Drush's bash integration and custom prompt won't work.
1. Run `drush init`. This edits ~/.bashrc so that Drush's custom prompt and bash integration are active.
1. See [Usage](http://docs.drush.org/en/master/usage/) for details on using Drush.
1. To use a non-default PHP, [edit ~/.bashrc so that the desired PHP is in front of your $PATH](http://stackoverflow.com/questions/4145667/how-to-override-the-path-of-php-to-use-the-mamp-path/10653443#10653443). If that is not desirable, you can change your PATH for just one request: `PATH=/path/to/php:$PATH` drush status ...`

!!! note

    Drush 9 cannot run commandfiles from Drush 8 and below (e.g. example.drush.inc). See our [guide on porting commandfiles](https://weitzman.github.io/blog/port-to-drush9). Also note that alias and config files use a new .yml format in Drush 9.

Drupal Compatibility
-----------------
<table>
  <tr>
    <th> Drush Version </th> 
    <th> Drush Branch </th>
    <th> PHP </th>
    <th> Compatible Drupal versions </th>
    <th> Code Style </th>
    <th> Isolation Tests </th>
    <th> Functional Tests </th>
  </tr>
  <tr>
    <td> Drush 9 </td>
    <td> <a href="https://travis-ci.org/drush-ops/drush">master</a> </td>
    <td> 5.6+ </td>
    <td> D8.4+ </td>
    <td align="center">
      <img src="https://api.shippable.com/projects/5507addd5ab6cc1352a213b5/badge?branch=master" />
    </td>
    <td align="center">
      <img src="https://travis-ci.org/drush-ops/drush.svg?branch=master" />
    </td>
    <td align="center">
      <img src="https://circleci.com/gh/drush-ops/drush.svg?style=shield" />
    </td>
  </tr>
  <tr>
    <td> Drush 8 </td>
    <td> <a href="https://travis-ci.org/drush-ops/drush">8.x</a> </td>
    <td> 5.4.5+ </td>
    <td> D6, D7, D8.3- </td>
    <td align="center">
      <img src="https://circleci.com/gh/drush-ops/drush.svg?branch=8.x&style=shield" />
    </td>
    <td align="center">
      -
    </td>
    <td align="center">
      <img src="https://travis-ci.org/drush-ops/drush.svg?branch=8.x" />
    </td>
  </tr>
  <tr>
    <td> Drush 7 </td>
    <td> <a href="https://travis-ci.org/drush-ops/drush">7.x</a> </td>
    <td> 5.3.0+ </td>
    <td> D6, D7 </td>
    <td colspan="3" align="center"> Unsupported </td>
  </tr>
  <tr>
    <td> Drush 6 </td>
    <td> <a href="https://travis-ci.org/drush-ops/drush">6.x</a> </td>
    <td> 5.3.0+ </td>
    <td> D6, D7 </td>
    <td colspan="3" align="center"> Unsupported </td>
  </tr>
  <tr>
    <td> Drush 5 </td>
    <td> <a href="https://travis-ci.org/drush-ops/drush">5.x</a> </td>
    <td> 5.2.0+ </td>
    <td> D6, D7 </td>
    <td colspan="3" align="center"> Unsupported </td>
  </tr>
</table>

Drush make
----------
Drush make is an extension to drush that can create a ready-to-use drupal site,
pulling sources from various locations. It does this by parsing a flat text file
(similar to a drupal `.info` file) and downloading the sources it describes. In
practical terms, this means that it is possible to distribute a complicated
Drupal distribution as a single text file.

Among Drush make's capabilities are:

- Downloading Drupal core, as well as contrib modules from drupal.org.
- Checking code out from SVN, git, and bzr repositories.
- Getting plain `.tar.gz` and `.zip` files (particularly useful for libraries
  that can not be distributed directly with drupal core or modules).
- Fetching and applying patches.
- Fetching modules, themes, and installation profiles, but also external
  libraries.


Usage
-----
The `drush make` command can be executed from a path within a Drupal codebase or
independent of any Drupal sites entirely. See the examples below for instances
where `drush make` can be used within an existing Drupal site.

    drush make [-options] [filename.make] [build path]

The `.make` file format
-----------------------
Each makefile is a plain text file that adheres to YAML syntax. See
the included `examples/example.make.yml` for an example of a working
makefile.

The older Drupal `.info` INI format is also supported. See
`examples/example.make` for a working example.

### Core version

The make file always begins by specifying the core version of Drupal
for which each package must be compatible. Example:

    core: 7.x

### API version

The make file must specify which Drush Make API version it uses. This version
of Drush Make uses API version `2`

    api: 2


### Projects

An array of the projects (e.g. modules, themes, libraries, and drupal) to be
retrieved. Each project name can be specified as a single string value. If
further options need to be provided for a project, the project should be
specified as the key.

**Project with no further options:**

    projects:
      - drupal

**Project using options (see below):**

    projects:
      drupal:
        version: "7.33"

Do not use both types of declarations for a single project in your makefile.


### Project options

- `version`

  Specifies the version of the project to retrieve.
  This can be as loose as the major branch number or
  as specific as a particular point release.

        projects:
          views:
            # Picks the latest release.
            version: ~

        projects:
          views:
            version: "2.8"

        projects:
          views:
            version: "3.0-alpha2"

        # Shorthand syntax for versions if no other options are to be specified
        projects:
          views: "3.0-alpha2"

  Note that version numbers should be enclosed in
  quotes to ensure they are interpreted correctly
  by the YAML parser.

- `patch`

  One or more patches to apply to this project. An array of patches,
  each specified as a URL or local path relative to the makefile.

        projects:
          calendar:
            patch:
              rfc-fixes:
                url: "http://drupal.org/files/issues/cal-760316-rfc-fixes-2.diff"
                md5: "e4876228f449cb0c37ffa0f2142"
          adminrole:
            # shorthand syntax if no md5 checksum is specified
            patch:
              - "http://drupal.org/files/issues/adminrole_exceptions.patch"
              - "http://drupal.org/files/issues/adminrole-213212-01.patch"
              - "adminrole-customizations.patch"

- `subdir`

  Place a project within a subdirectory of the `--contrib-destination`
  specified. In the example below, `cck` will be placed in
  `sites/all/modules/contrib` instead of the default `sites/all/modules`.

        projects:
          cck:
            subdir: "contrib"

- `location`

  URL of an alternate project update XML server to use. Allows project XML data
  to be retrieved from sites other than `updates.drupal.org`.

        projects:
          tao:
            location: "http://code.developmentseed.com/fserver"

- `type`

  The project type. Must be provided if an update XML source is not specified
  and/or using version control or direct retrieval for a project. May be one of
  the following values: core, module, profile, theme.

        projects:
          mytheme:
            type: "theme"

- `directory_name`

  Provide an alternative directory name for this project. By default, the
  project name is used.

        projects:
          mytheme:
            directory_name: "yourtheme"

- `l10n_path`

  Specific URL (can include tokens) to a translation. Allows translations to be
  retrieved from l10n servers other than `localize.drupal.org`.

        projects:
          mytheme:
            l10n_path: "http://myl10nserver.com/files/translations/%project-%core-%version-%language.po"

- `l10n_url`

  URL to an l10n server XML info file. Allows translations to be retrieved from
  l10n servers other than `localize.drupal.org`.

        projects:
          mytheme:
            l10n_url: "http://myl10nserver.com/l10n_server.xml"

- `overwrite`

  Allows the project to be installed in a directory that is not empty.
  If not specified this is treated as FALSE, Drush make sets an error when the directory is not empty.
  If specified TRUE, Drush make will continue and use the existing directory.
  Useful when adding extra files and folders to existing folders in libraries or module extensions.

        projects:
          myproject:
            overwrite: TRUE

- `translations`

  Retrieve translations for the specified language, if available, for all projects.

        translations:
          - es
          - fr

- `do_recursion`

  Recursively build an included makefile. Defaults to 'true'. 

        do_recursion: false

- `variant`

  Which type of tarball to download for profiles. Valid options include:
    - 'full': complete distro including Drupal core, e.g. `distro_name-core.tar.gz`
    - 'projects': the fully built profile, projects defined drupal-org.make, etc., e.g. `distro_name-no-core.tar.gz`
    - 'profile-only' (just the bare profile, e.g. `distro_name.tar.gz`).
  Defaults to 'profile-only'. When using 'projects', `do_recursion: false` will be necessary to avoid recursively making any makefiles included in the profile.

        variant: projects



### Project download options

  Use an alternative download method instead of retrieval through update XML.

  If no download type is specified, make defaults the type to
  `git`. Additionally, if no url is specified, make defaults to use
  Drupal.org.

  The following methods are available:

- `download[type] = file`

  Retrieve a project as a direct download. Options:

  `url` - the URL of the file. Required.
          The URL can also be a path to a local file either using the bare path or
          the file:// protocol. The path may be absolute or relative to the makefile.

  `md5`, `sha1`, `sha256`, or `sha512` - one or more checksums for the file. Optional.

  `request_type` - the request type - get or post. post depends on
  http://drupal.org/project/make_post. Optional.

  `data` - The post data to be submitted with the request. Should be a
  valid URL query string. Requires http://drupal.org/project/make_post. Optional.

  `filename` - What to name the file, if it's not an archive. Optional.

  `subtree`  - if the download is an archive, only this subtree within the
  archive will be copied to the target destination. Optional.

- `download[type] = copy`

  Copies a project from a local folder. Options:

  `url` - the URL of the folder. Required.
          The URL must be a path to a local folder either using the bare path or
          the file:// protocol. The path may be absolute or relative to the makefile.

     projects[example][type] = "profile"
     projects[example][download][type] = "copy"
     projects[example][download][url] = "file://./example"

- `download[type] = bzr`

  Use a bazaar repository as the source for this project. Options:

  `url` - the URL of the repository. Required.

  `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files

- `download[type] = git`

  Use a git repository as the source for this project. Options:

  `url` - the URL of the repository. Required.

  `branch` - the branch to be checked out. Optional.

  `revision` - a specific revision identified by commit to check
    out. Optional. Note that it is recommended on use `branch` in
    combination with `revision` if relying on the .info file rewriting.

  `tag` - the tag to be checked out. Optional.

     projects[mytheme][download][type] = "git"
     projects[mytheme][download][url] = "git://github.com/jane_doe/mytheme.git"

  `refspec` - the git reference to fetch and checkout. Optional.

     If this is set, it will have priority over tag, revision and branch options.

  `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files

- `download[type] = svn`

  Use an SVN repository as the source for this project. Options:

  `url` - the URL of the repository. Required.

  `interactive` - whether to prompt the user for authentication credentials
  when using a private repository. Allows username and/or password options to
  be omitted. Optional.

  `username` - the username to use when retrieving an SVN project as a working
  copy or from a private repository. Optional.

  `password` - the password to use when retrieving an SVN project as a working
  copy or from a private repository. Optional.

     projects:
       mytheme:
         download:
           type: "svn"
           url: "http://example.com/svnrepo/cool-theme/"

  `working-copy` - If true, the checked out source will be kept as a working copy rather than exported as standalone files

  Shorthand for `download[url]` available for all download types:

     projects:
       mytheme:
         download: "git://github.com/jane_doe/mytheme.git"

  is equivalent to:

     projects:
       mytheme:
         download:
           url: "git://github.com/jane_doe/mytheme.git"

### Libraries

An array of non-Drupal-specific libraries to be retrieved (e.g. js, PHP or other
Drupal-agnostic components). Each library should be specified as the key of an
array of options in the libraries array.

**Example:**

    libraries:
      jquery_ui:
        download:
          type: "file"
          url: "http://jquery- ui.googlecode.com/files/jquery.ui-1.6.zip"
          md5: "c177d38bc7af59d696b2efd7dda5c605"


### Library options

Libraries share the `download`, `subdir`, and `directory_name` options with
projects. Additionally, they may specify a destination:

- `destination`

  The target path to which this library should be moved. The path is relative to
  that specified by the `--contrib-destination` option. By default, libraries
  are placed in the `libraries` directory.

        libraries:
          jquery_ui:
            destination: "modules/contrib/jquery_ui"


### Includes

An array of makefiles to include. Each include may be a local relative path to
the include makefile directory, a direct URL to the makefile, or from a git
repository. Includes are appended in order with the source makefile appended
last. As a result, values in the source makefile take precedence over those in
includes. Use `overrides` for the reverse order of precedence.

**Example:**

    includes:
      # Includes a file in the same directory.
      - "example.make"
      # Includes a file with a relative path.
      - "../example_relative/example_relative.make"
      # A remote-hosted file.
      - "http://www.example.com/remote.make"
      # A file on a git repository.
      - makefile: "example_dir/example.make"
        download:
          type: "git"
          url: "git@github.com:organisation/repository.git"
          # Branch could be tag or revision, it relies on the standard Drush git download feature.
          branch: "master"          

The `--includes` option is available for most make commands, and allows
makefiles to be included at build-time.

**Example:**

    # Build from a production makefile, but add development and test projects.
    $ drush make production.make --includes=dev.make,test.make


### Overrides

Similar to `includes`, `overrides` will include content from other makefiles.
However, the order of precedence is reversed. That is, they override the
keys/values of the source makefile.

The `--overrides` option is available for most make commands, and allows
overrides to be included at build-time.

**Example:**

    #production.make.yml:
    api: 2
    core: 8.x
    includes:
      - core.make
      - contrib.make
    projects:
      custom_feature_A:
        type: module
        download:
          branch: production
          type: git
          url: http://github.com/example/custom_feature_A.git
      custom_feature_B:
        type: module
        download:
          branch: production
          type: git
          url: http://github.com/example/custom_feature_B.git

     # Build production code-base.
     $ drush make production.make.yml

     #testing.make
     projects:
       custom_feature_A:
         download:
           branch: dev/bug_fix
       custom_feature_B:
         download:
           branch: feature/new_feature

     # Build production code-base using development/feature branches for custom code.
     $ drush make /path/to/production.make --overrides=http://url/of/testing.make


### Defaults

If all projects or libraries have identical settings for a given
attribute, the `defaults` array can be used to specify these,
rather than specifying the attribute for each project.

**Example:**

    # Specify common subdir of "contrib"
    defaults:
      projects:
        subdir: "contrib"
    # Projects that don't specify subdir will go to the 'contrib' directory.
    projects:
      views:
        version: "3.3"
      # Override a default value.
      devel:
        subdir: "development"

### Overriding properties

Makefiles which include others may override the included makefiles properties.
Properties in the includer takes precedence over the includee.

**Example:**

`base.make`

    core: "6.x"
      views:
        subdir: "contrib"
      cck:
        subdir: "contrib"

`extender.make`

    includes:
      - "base.make"
    projects:
      views:
        # This line overrides the included makefile's 'subdir' option
        subdir: "patched"

        # These lines overrides the included makefile, switching the download type
        # to a git clone.
        type: "module"
        download:
          type: "git"
          url: "http://git.drupal.org/project/views.git"

A project or library entry of an included makefile can be removed entirely by
setting the corresponding key to NULL:

      # This line removes CCK entirely which was defined in base.make
      cck: ~


Recursion
---------

If a project that is part of a build contains a `.make.yml` itself, Drush make will
automatically parse it and recurse into a derivative build.

For example, a full build tree may look something like this:

    Drush make distro.make distro

    distro.make FOUND
    - Drupal core
    - Foo bar install profile
      + foobar.make.yml FOUND
        - CCK
        - Token
        - Module x
          + x.make FOUND
            - External library x.js
        - Views
        - etc.

Recursion can be used to nest an install profile build in a Drupal site, easily
build multiple install profiles on the same site, fetch library dependencies
for a given module, or bundle a set of module and its dependencies together.
For Drush Make to recognize a makefile embedded within a project, the makefile
itself must have the same name as the project. For instance, the makefile
embedded within the managingnews profile must be called "managingnews.make". If
no makefile matching the project's name is found, Drush Make will look for a
"drupal-org.make.yml" makefile instead. The file must be in the project's root
directory. Subdirectories will be ignored.

**Build a full Drupal site with the Managing News install profile:**

    core: 6.x
    api: 2
    projects:
      - drupal
      - managingnews

** Use a distribution as core **

    core: 7.x
    api: 2
    projects:
      commerce_kickstart:
        type: "core"
        version: "7.x-1.19"

This behavior can be overridden globally using the `--no-recursion` option, or on a project-by-project basis by setting the `do_recursion` project parameter to 'false' in a makefile:

    core: 7.x
    api: 2
    projects:
      drupal:
        type: core
      hostmaster:
        type: profile
        do_recursion: false


Testing
-------
Drush make also comes with testing capabilities, designed to test Drush make
itself. Writing a new test is extremely simple. The process is as follows:

1. Figure out what you want to test. Write a makefile that will test
   this out.  You can refer to existing test makefiles for
   examples. These are located in `DRUSH/tests/makefiles`.
2. Drush make your makefile, and use the --md5 option. You may also use other
   options, but be sure to take note of which ones for step 4.
3. Verify that the result you got was in fact what you expected. If so,
   continue. If not, tweak it and re-run step 2 until it's what you expected.
4. Using the md5 hash that was spit out from step 2, make a new entry in the
   tests clase (DRUSH/tests/makeTest.php), following the example below.
    'machine-readable-name' => array(
      'name'     => 'Human readable name',
      'makefile' => 'tests/yourtest.make',
      'messages' => array(
          'Build hash: f68e6510-your-hash-e04fbb4ed',
      ),
      'options'  => array('any' => TRUE, 'other' => TRUE, 'options' => TRUE),
    ),
5. Test! Run Drush test suite (see DRUSH/tests/README.md). To just
   run the make tests:

     `./unish.sh --filter=makeMake .`


You can check for any messages you want in the message array, but the most
basic tests would just check the build hash.

Generate
--------

Drush make has a primitive makefile generation capability. To use it, simply
change your directory to the Drupal installation from which you would like to
generate the file, and run the following command:

`drush generate-makefile /path/to/make-file.make`

This will generate a basic makefile. If you have code from other repositories,
the makefile will not complete - you'll have to fill in some information before
it is fully functional.

Maintainers
-----------
- Jonathan Hedstrom ([jhedstrom](https://www.drupal.org/u/jhedstrom))
- Christopher Gervais ([ergonlogic](http://drupal.org/u/ergonlogic))
- [The rest of the Drush maintainers](https://github.com/drush-ops/drush/graphs/contributors)

Original Author
---------------
[Dmitri Gaskin (dmitrig01)](https://twitter.com/dmitrig01)
Drush Output Formats
====================

Many Drush commands produce output that may be rendered in a variety of different ways using a pluggable formatting system. Drush commands that support output formats will show a --format option in their help text. The available formats are also listed in the help text, along with the default value for the format option. The list of formats shown is abbreviated; to see the complete list of available formats, run the help command with the --verbose option.

The --pipe option is a quick, consistent way to get machine readable output from a command, in whatever way the command author thought was helpful. The --pipe option is equivalent to using --format=`<pipe-format>` The pipe format will be shown in the options section of the command help, under the --pipe option. For historic reasons, --pipe also hides all log messages.

To best understand how the various Drush output formatters work, it is best to first look at the output of the command using the 'var\_export' format. This will show the result of the command using the exact structure that was built by the command, without any reformatting. This is the standard format for the Drush command. Different formatters will take this information and present it in different ways.

Global Options
--------------

-   --list-separator: Specify how elements in a list should be separated. In lists of lists, this applies to the elements in the inner lists.
-   --line-separator: In nested lists of lists, specify how the outer lists ("lines") should be separated.

Output Formats
--------------

A list of available formats, and their affect on the output of certain Drush commands, is shown below.

You can then use an interactive PHP REPL with your bootstrapped site (remote or local). It’s a Drupal code playground. You can do quick code experimentation, grab some data, or run Drush commands. This can also help with debugging certain issues. See [this blog post](http://blog.damiankloip.net/2015/drush-php) for an introduction. Run `help` for a list of commands. 
Drush Shell Aliases
===================

A Drush shell alias is a shortcut to any Drush command or any shell command. Drush shell aliases are very similar to [git aliases](https://git.wiki.kernel.org/index.php/Aliases\#Advanced).

A shell alias is defined in a Drush configuration file called drushrc.php. See `drush topic docs-configuration`. There are two kinds of shell aliases: an alias whose value begins with a '!' will execute the rest of the line as bash commands. Aliases that do not start with a '!' will be interpreted as Drush commands.

        $options['shell-aliases']['pull'] = '!git pull';
        $options['shell-aliases']['noncore'] = 'pm-list --no-core';

With the above two aliases defined, `drush pull` will then be equivalent to `git pull`, and `drush noncore` will be equivalent to `drush pm-list --no-core`.

Shell Alias Replacements
------------------------

Shell aliases are even more powerful when combined with shell alias replacements and site aliases. Shell alias replacements take the form of {{sitealias-item}} or {{%pathalias-item}}, and also the special {{@target}}, which is replaced with the name of the site alias used, or '@none' if none was used.

For example, given the following site alias:

         $aliases['dev'] = array (
           'root' => '/path/to/drupal',
           'uri' => 'http://example.com',
           '#live' => '@acme.live',
         );

The alias below can be used for all your projects to fetch the database and files from the client's live site via `drush @dev pull-data`. Note that these aliases assume that the alias used defines an item named '\#live' (as shown in the above alias).

    $options['shell-aliases'] = array( 
      'pull-data' => '!drush sql-sync {{#live}} {{@target}} && drush rsync {{#live}}:%files {{@target}}:%files'
    );

If the user does not use these shell aliases with any site alias, then an error will be returned and the script will not run. These aliases with replacements can be used to quickly run combinations of drush sql-sync and rsync commands on the "standard" source or target site, reducing the risk of typos that might send information in the wrong direction or to the wrong site.

Drush Shell Scripts
===================

A drush shell script is any Unix shell script file that has its "execute" bit set (i.e., via `chmod +x myscript.drush`) and that begins with a specific line:

        #!/usr/bin/env drush

or

        #!/full/path/to/drush

The former is the usual form, and is more convenient in that it will allow you to run the script regardless of where drush has been installed on your system, as long as it appears in your PATH. The later form allows you to specify the drush command add options to use, as in:

        #!/full/path/to/drush php-script --some-option

Adding specific options is important only in certain cases, described later; it is usually not necessary.

Drush scripts do not need to be named "\*.drush" or "\*.script"; they can be named anything at all. To run them, make sure they are executable (`chmod +x helloworld.script`) and then run them from the shell like any other script.

There are two big advantages to drush scripts over bash scripts:

-   They are written in php
-   drush can bootstrap your Drupal site before running your script.

To bootstrap a Drupal site, provide an alias to the site to bootstrap as the first commandline argument.

For example:

        $ helloworld.script @dev a b c

If the first argument is a valid site alias, drush will remove it from the arument list and bootstrap that site, then run your script. The script itself will not see @dev on its argument list. If you do not want drush to remove the first site alias from your scripts argument list (e.g. if your script wishes to syncronise two sites, specified by the first two arguments, and does not want to bootstrap either of those two sites), then fully specify the drush command (php-script) and options to use, as shown above. By default, if the drush command is not specified, drush will provide the following default line:

        #!/full/path/to/drush php-script --bootstrap-to-first-arg

It is the option --bootstrap-to-first-arg that causes drush to pull off the first argument and bootstrap it. The way to get rid of that option is to specify the php-script line to run, and leave it off, like so:

        #!/full/path/to/drush php-script

Note that 'php-script' is the only built-in drush command that makes sense to put on the "shebang" ("\#!" is pronounced "shebang") line. However, if you wanted to, you could implement your own custom version of php-script (e.g. to preprocess the script input, perhaps), and specify that command on the shebang line.

Drush scripts can access their arguments via the drush\_shift() function:

            while ($arg = drush_shift()) {
              drush_print($arg);
            }

Options are available via drush\_get\_option('option-name'). The directory where the script was launched is available via drush_cwd()

See the example drush script in `drush topic docs-examplescript`, and the list of drush error codes in `drush topic docs-errorcodes`.

Strict Option Handling
======================

Some Drush commands use strict option handling; these commands require that all Drush global option appear on the command line before the Drush command name.

One example of this is the core-rsync command:

      drush --simulate core-rsync -v @site1 @site2

The --simulate option is a Drush global option that causes Drush to print out what it would do if the command is executed, without actually taking any action. Commands such as core-rsync that use strict option handling require that --simulate, if used, must appear before the command name. Most Drush commands allow the --simulate to be placed anywhere, such as at the end of the command line.

The -v option above is an rsync option. In this usage, it will cause the rsync command to run in verbose mode. It will not cause Drush to run in verbose mode, though, because it appears after the core-rsync command name. Most Drush commands would be run in verbose mode if a -v option appeared in the same location.

The advantage of strict option handling is that it allows Drush to pass options and arguments through to a shell command. Some shell commands, such as rsync and ssh, either have options that cannot be represented in Drush. For example, rsync allows the --exclude option to appear multiple times on the command line, but Drush only allows one instance of an option at a time for most Drush commands. Strict option handling overcomes this limitation, plus possible conflict between Drush options and shell command options with the same name, at the cost of greater restriction on where global options can be placed.

Usage
-----------

Drush can be run in your shell by typing "drush" from within any Drupal root directory.

    $ drush [options] <command> [argument1] [argument2]

Use the 'help' command to get a list of available options and commands:

    $ drush help

For even more documentation, use the 'topic' command:

    $ drush topic

Options
-----------

For multisite installations, use the --uri option to target a particular site.  If
you are outside the Drupal web root, you might need to use the --root, --uri or other
command line options just for Drush to work.

    $ drush --uri=http://example.com pm-updatecode

If you wish to be able to select your Drupal site implicitly from the
current working directory without using the --uri option, but you need your
base_url to be set correctly, you may force it by setting the uri in
a drushrc.php file located in the same directory as your settings.php file.

```
$options['uri'] = "http://example.com";
```

Site Aliases
------------

Drush lets you run commands on a remote server, or even on a set of remote
servers.  Once defined, aliases can be referenced with the @ nomenclature, i.e.

```bash
# Run pending updates on staging site.
$ drush @staging updatedb
# Synchronize staging files to production
$ drush rsync @staging:%files/ @live:%files
# Synchronize database from production to dev, excluding the cache table
$ drush sql-sync --structure-tables-key=custom @live @dev
```

See [example.aliases.drushrc.php](https://raw.githubusercontent.com/drush-ops/drush/8.x/examples/example.aliases.drushrc.php) for more information.

#!/usr/bin/env sh
#
# DRUSH WRAPPER
#
# A wrapper script which launches the Drush that is in your project's /vendor
# directory.  Copy it to the root of your project and edit as desired.
# You may rename this script to 'drush', if doing so does not cause a conflict
# (e.g. with a folder __ROOT__/drush).
#
# Below are options which you might want to add. More info at
# `drush topic core-global-options`:
#
# --local       Only discover commandfiles/site aliases/config that are
#               inside your project dir.
# --alias-path  A list of directories where Drush will search for site
#               alias files.
# --config      A list of paths to config files
# --include     A list of directories to search for commandfiles.
#
# Note that it is recommended to use --local when using a drush
# wrapper script.
#
# See the 'drush' script in the Drush installation root (../drush) for
# an explanation of the different 'drush' scripts.
#
# IMPORTANT:  Modify the path below if your 'vendor' directory has been
# relocated to another location in your composer.json file.
# `../vendor/bin/drush.launcher --local $@` is a common variant for
# composer-managed Drupal sites.
#
cd "`dirname $0`"
../vendor/bin/drush.launcher --local "$@"
<?php

/**
 * @file
 * Example of valid statements for an alias file.
 *
 * Use this file as a guide to creating your own aliases.
 *
 * Aliases are commonly used to define short names for
 * local or remote Drupal installations; however, an alias
 * is really nothing more than a collection of options.
 * A canonical alias named "dev" that points to a local
 * Drupal site named "http://example.com" looks like this:
 *
 * @code
 * $aliases['dev'] = array(
 *   'root' => '/path/to/drupal',
 *   'uri' => 'http://example.com',
 * );
 * @endcode
 *
 * With this alias definition, then the following commands
 * are equivalent:
 *
 *   $ drush @dev status
 *   $ drush --root=/path/to/drupal --uri=http://example.com status
 *
 * See the --uri option documentation below for hints on setting its value.
 *
 * Any option that can be placed on the drush commandline
 * can also appear in an alias definition.
 *
 * There are several ways to create alias files.
 *
 *   + Put each alias in a separate file called ALIASNAME.alias.drushrc.php
 *   + Put multiple aliases in a single file called aliases.drushrc.php
 *   + Put groups of aliases into files called GROUPNAME.aliases.drushrc.php
 *
 * Drush will search for aliases in any of these files using
 * the alias search path.  The following locations are examined
 * for alias files:
 *
 *   1. In any path set in $options['alias-path'] in drushrc.php,
 *      or (equivalently) any path passed in via --alias-path=...
 *      on the command line.
 *   2. In one of the default locations:
 *        a. /etc/drush
 *        b. $HOME/.drush
 *   3. In one of the site-specific locations:
 *        a. The /drush and /sites/all/drush folders for the current Drupal site
 *        b. The /drush folder in the directory above the current Drupal site
 *
 * These locations are searched recursively.  If there is a folder called
 * 'site-aliases' in any search path, then Drush will search for site aliases
 * only inside that directory.
 *
 * The preferred locations for alias files, then, are:
 *
 *   /etc/drush/site-aliases
 *   $HOME/.drush/site-aliases
 *   $ROOT/drush/site-aliases
 *   $ROOT/sites/all/drush/site-aliases
 *   $ROOT/../drush/site-aliases
 *
 * Or any path set in $options['alias-path'] or via --alias-path.
 *
 * Folders and files containing other versions of drush in their names will
 * be *skipped* (e.g. mysite.aliases.drush4rc.php or
 * drush4/mysite.aliases.drushrc.php). Names containing the current version of
 * Drush (e.g. mysite.aliases.drush5rc.php) will be loaded.
 *
 * Files stored in these locations can be used to create aliases
 * to local and remote Drupal installations.  These aliases can be
 * used in place of a site specification on the command line, and
 * may also be used in arguments to certain commands such as
 * "drush rsync" and "drush sql-sync".
 *
 * Alias files that are named after the single alias they contain
 * may use the syntax for the canonical alias shown at the top of
 * this file, or they may set values in $options, just
 * like a drushrc.php configuration file:
 *
 * @code
 * $options['uri'] = 'http://example.com';
 * $options['root'] = '/path/to/drupal';
 * @endcode
 *
 * When alias files use this form, then the name of the alias
 * is taken from the first part of the alias filename.
 *
 * Alias groups (aliases stored together in files called
 * GROUPNAME.aliases.drushrc.php, as mentioned above) also
 * create an implicit namespace that is named after the group
 * name.
 *
 * For example:
 *
 * @code
 * # File: mysite.aliases.drushrc.php
 * $aliases['dev'] = array(
 *   'root' => '/path/to/drupal',
 *   'uri' => 'http://example.com',
 * );
 * $aliases['live'] = array(
 *   'root' => '/other/path/to/drupal',
 *   'uri' => 'http://example.com',
 * );
 * @endcode
 *
 * Then the following special aliases are defined:
 * - @mysite: An alias named after the groupname may be used to reference all of
 *   the aliases in the group (e.g., `drush @mydrupalsite status`).
 * - @mysite.dev: A copy of @dev.
 * - @mysite.live: A copy of @live.
 *
 * Thus, aliases defined in an alias group file may be referred to
 * either by their simple (short) name, or by their full namespace-qualified
 * name.
 *
 * To see an example alias definition for the current bootstrapped
 * site, use the "site-alias" command with the built-in alias "@self":
 *
 *   $ drush site-alias @self
 *
 * TIP:  If you would like to have drush include a 'databases' record
 * in the output, include the options --with-db and --show-passwords:
 *
 *   $ drush site-alias @self --with-db --show-passwords
 *
 * Drush also supports *remote* site aliases.  When a site alias is
 * defined for a remote site, Drush will use the ssh command to run
 * the requested command on the remote machine.  The simplest remote
 * alias looks like this:
 *
 * @code
 * $aliases['live'] = array(
 *   'remote-host' => 'server.domain.com',
 *   'remote-user' => 'www-admin',
 * );
 * @endcode
 *
 * The form above requires that Drush be installed on the remote machine,
 * and that there also be an alias of the same name defined on that
 * machine.  The remote alias should define the 'root' and 'uri' elements,
 * as shown in the initial example at the top of this file.
 *
 * If you do not wish to maintain site aliases on the remote machine,
 * then you may define an alias that contains all of the elements
 * 'remote-host', 'remote-user', 'root' and 'uri'.  If you do this, then
 * Drush will make the remote call using the --root and --uri options
 * to identify the site, so no site alias is required on the remote server.
 *
 * @code
 * $aliases['live'] = array(
 *   'remote-host' => 'server.domain.com',
 *   'remote-user' => 'www-admin',
 *   'root' => '/other/path/to/drupal',
 *   'uri' => 'http://example.com',
 * );
 * @endcode
 *
 * If you would like to see all of the Drupal sites at a specified
 * root directory, use the built-in alias "@sites":
 *
 *   $ drush -r /path/to/drupal site-alias @sites
 *
 * It is also possible to define explicit lists of sites using a special
 * alias list definition.  Alias lists contain a list of alias names in
 * the group, and no other information.  For example:
 *
 * @code
 * $aliases['mydevsites'] = array(
 *   'site-list' => array('@mysite.dev', '@otherside.dev')
 * );
 * @endcode
 *
 * The built-in alias "@none" represents the state of no Drupal site;
 * to ignore the site at the cwd and just see default drush status:
 *
 *   $ drush @none status
 *
 * See `drush help site-alias` for more options for displaying site
 * aliases.  See `drush topic docs-bastion` for instructions on configuring
 * remote access to a Drupal site behind a firewall via a bastion server.
 *
 * Although most aliases will contain only a few options, a number
 * of settings that are commonly used appear below:
 *
 * - 'uri': In Drupal 7 and 8, the value of --uri should always be the same as
 *   when the site is being accessed from a web browser (e.g. http://example.com)
 *   In Drupal 6, the value of --uri should always be the same as the site's folder
 *   name in the 'sites' folder (e.g. default); it is best if the site folder name
 *   matches the URI from the browser, and is consistent on every instance of the
 *   same site (e.g. also use sites/example.com for http://example.com).
 * - 'root': The Drupal root; must not be specified as a relative path.
 * - 'remote-host': The fully-qualified domain name of the remote system
 *   hosting the Drupal instance. **Important Note: The remote-host option
 *   must be omitted for local sites, as this option controls various
 *   operations, such as whether or not rsync parameters are for local or
 *   remote machines, and so on. @see hook_drush_sitealias_alter() in drush.api.php
 * - 'remote-user': The username to log in as when using ssh or rsync.
 * - 'os': The operating system of the remote server.  Valid values
 *   are 'Windows' and 'Linux'. Be sure to set this value for all remote
 *   aliases because the default value is PHP_OS if 'remote-host'
 *   is not set, and 'Linux' (or $options['remote-os']) if it is. Therefore,
 *   if you set a 'remote-host' value, and your remote OS is Windows, if you
 *   do not set the 'OS' value, it will default to 'Linux' and could cause
 *   unintended consequences, particularly when running 'drush sql-sync'.
 * - 'ssh-options': If the target requires special options, such as a non-
 *   standard port, alternative identity file, or alternative
 *   authentication method, ssh-options can contain a string of extra
 *   options that are used with the ssh command, eg "-p 100"
 * - 'parent': Deprecated.  See "altering aliases", below.
 * - 'path-aliases': An array of aliases for common rsync targets.
 *   Relative aliases are always taken from the Drupal root.
 *   - '%drush-script': The path to the 'drush' script, or to 'drush.php'.
 *     This is used by backend invoke when drush
 *     runs a drush command.  The default is 'drush' on remote machines, or
 *     the full path to drush.php on the local machine.
 *   - '%drush': A read-only property: points to the folder that the drush
 *     script is stored in.
 *   - '%files': Path to 'files' directory.  This will be looked up if not
 *     specified.
 *   - '%root': A reference to the Drupal root defined in the 'root' item in the
 *     site alias record.
 * - 'php': path to custom php interpreter. Windows support limited to Cygwin.
 * - 'php-options': commandline options for php interpreter, you may
 *   want to set this to '-d error_reporting="E_ALL^E_DEPRECATED"'
 * - 'variables' : An array of name/value pairs which override Drupal
 *   variables/config. These values take precedence even over settings.php
 *   overrides.
 * - 'command-specific': These options will only be set if the alias
 *   is used with the specified command.  In the example below, the option
 *   `--no-dump` will be selected whenever the @stage alias
 *   is used in any of the following ways:
 *   - `drush @stage sql-sync @self @live`
 *   - `drush sql-sync @stage @live`
 *   - `drush sql-sync @live @stage`
 *   In case of conflicting options, command-specific options in targets
 *   (source and destination) take precedence over command-specific options
 *   in the bootstrapped site, and command-specific options in a destination
 *   alias will take precedence over those in a source alias.
 * - 'source-command-specific' and 'target-command-specific': Behaves exactly
 *   like the 'command-specific' option, but is applied only if the alias
 *   is used as the source or target, respectively, of an rsync or sql-sync
 *   command.  In the example below, `--skip-tables-list=comments` whenever
 *   the alias @live is the target of an sql-sync command, but comments will
 *   be included if @live is the source for the sql-sync command.
 * - '#peer': Settings that begin with a '#' are not used directly by Drush, and
 *   in fact are removed before making a backend invoke call (for example).
 *   These kinds of values are useful in conjunction with shell aliases.  See
 *   `drush topic docs-shell-aliases` for more information on this.
 * - '#env-vars': An associative array of keys and values that should be set on
 *    the remote side before invoking drush.
 * - rsync command options have specific requirements in order to
 *   be passed through by Drush. See the comments on the sample below:
 *
 * @code
 * 'command-specific' => array (
 *   'core-rsync' => array (
 *
 *     // single-letter rsync options are placed in the 'mode' key
 *     // instead of adding '--mode=rultvz' to drush rsync command.
 *     'mode' => 'rultvz',
 *
 *     // multi-letter rsync options without values must be set to
 *     // TRUE or NULL to work (i.e. setting $VALUE to 1, 0, or ''
 *     // will not work).
 *     'delete' => TRUE,
 *
 *     // if you need multiple excludes, use an rsync exclude file
 *     'exclude-from' => "'/etc/rsync/exclude.rules'",
 *
 *     // filter options with white space must be wrapped in "" to preserve
 *     // the inner ''.
 *     'filter' => "'exclude *.sql'",
 *
 *     // if you need multple filter options, see rsync merge-file options
 *     'filter' => "'merge /etc/rsync/default.rules'",
 *   ),
 * ),
 * @endcode
 *
 * Altering aliases:
 *
 * Alias records are written in php, so you may use php code to alter
 * alias records if you wish.  For example:
 *
 * @code
 * $common_live = array(
 *   'remote-host' => 'myserver.isp.com',
 *   'remote-user' => 'www-admin',
 * );
 *
 * $aliases['live'] = array(
 *   'uri' => 'http://example.com',
 *   'root' => '/path.to/root',
 * ) + $common_live;
 * @endcode
 *
 * If you wish, you might want to put $common_live in a separate file,
 * and include it at the top of each alias file that uses it.
 *
 * You may also use a policy file to alter aliases in code as they are
 * loaded by Drush.  See policy_drush_sitealias_alter in
 * `drush topic docs-policy` for details.
 *
 * Some examples appear below.  Remove the leading hash signs to enable.
 */

#$aliases['stage'] = array(
#    'uri' => 'http://stage.example.com',
#    'root' => '/path/to/remote/drupal/root',
#    'remote-host' => 'mystagingserver.myisp.com',
#    'remote-user' => 'publisher',
#    'os' => 'Linux',
#    'path-aliases' => array(
#      '%drush' => '/path/to/drush',
#      '%drush-script' => '/path/to/drush/drush',
#      '%files' => 'sites/mydrupalsite.com/files',
#      '%custom' => '/my/custom/path',
#     ),
#     'variables' => array(
#        'site_name' => 'My Drupal site',
#      ),
#     'command-specific' => array (
#       'sql-sync' => array (
#         'no-dump' => TRUE,
#       ),
#     ),
#     # This shell alias will run `mycommand` when executed via
#     # `drush @stage site-specific-alias`
#     'shell-aliases' => array (
#       'site-specific-alias' => '!mycommand',
#     ),
#  );
#$aliases['dev'] = array(
#    'uri' => 'http://dev.example.com',
#    'root' => '/path/to/drupal/root',
#    'variables' => array(
#      'mail_system' => array('default-system' => 'DevelMailLog'),
#    ),
#  );
#$aliases['server'] = array(
#    'remote-host' => 'mystagingserver.myisp.com',
#    'remote-user' => 'publisher',
#    'os' => 'Linux',
#  );
#$aliases['live'] = array(
#    'uri' => 'http://example.com',
#    'root' => $aliases['dev']['root'],
#  ) + $aliases['server'];
# -*- mode: shell-script; mode: flyspell-prog; ispell-local-dictionary: "american" -*-
#
# Example bash aliases to improve your Drush experience with bash.
# Use `drush init` to copy this file to your home directory, rename and
# customize it to suit, and source it from your ~/.bashrc file.
#
# Creates aliases to common Drush commands that work in a global context:
#
#       dr               - drush
#       ddd              - drush drupal-directory
#       dl               - drush pm-download
#       ev               - drush php-eval
#       sa               - drush site-alias
#       sa               - drush site-alias --local-only (show local site aliases)
#       st               - drush core-status
#       use              - drush site-set
#
# Aliases for Drush commands that work on the current drupal site:
#
#       cc               - drush cache-clear
#       cr               - drush cache-rebuild
#       cca              - drush cache-clear all
#       dis              - drush pm-disable
#       en               - drush pm-enable
#       i                - drush pm-info
#       pml              - drush pm-list
#       rf               - drush pm-refresh
#       unin             - drush pm-uninstall
#       up               - drush pm-update
#       upc              - drush pm-updatecode
#       updb             - drush updatedb
#       q                - drush sql-query
#
# Provides several common shell commands to work better with Drush:
#
#       ddd @dev         - print the path to the root directory of @dev
#       cdd @dev         - change the current working directory to @dev
#       lsd @dev         - ls root folder of @dev
#       lsd %files       - ls "files" directory of current site
#       lsd @dev:%devel  - ls devel module directory in @dev
#       @dev st          - drush @dev core-status
#       dssh @live       - ssh to the remote server @live points at
#       gitd @live pull  - run `git pull` on the drupal root of @live
#
# Drush site alias expansion is also done for the cpd command:
#
#       cpd -R @site1:%files @site2:%files
#
# Note that the 'cpd' alias only works for local sites.  Use
# `drush rsync` or gitd` to move files between remote sites.
#
# Aliases are also possible for the following standard
# commands. Uncomment their definitions below as desired.
#
#       cd                - cddl [*]
#       ls                - lsd
#       cp                - cpd
#       ssh               - dssh
#       git               - gitd
#
# These standard commands behave exactly the same as they always
# do, unless a Drush site specification such as @dev or @live:%files
# is used in one of the arguments.

# Aliases for common Drush commands that work in a global context.
alias dr='drush'
alias ddd='drush drupal-directory'
alias dl='drush pm-download'
alias ev='drush php-eval'
alias sa='drush site-alias'
alias lsa='drush site-alias --local-only'
alias st='drush core-status'
alias use='drush site-set'

# Aliases for Drush commands that work on the current drupal site
alias cc='drush cache-clear'
alias cr='drush cache-rebuild'
alias cca='drush cache-clear all'
alias dis='drush pm-disable'
alias en='drush pm-enable'
alias pmi='drush pm-info'
alias pml='drush pm-list'
alias rf='drush pm-refresh'
alias unin='drush pm-uninstall'
alias up='drush pm-update'
alias upc='drush pm-updatecode'
alias updb='drush updatedb'
alias q='drush sql-query'

# Overrides for standard shell commands. Uncomment to enable.  Alias
# cd='cdd' if you want to be able to use cd @remote to ssh to a
# remote site.

# alias cd='cddl'
# alias ls='lsd'
# alias cp='cpd'
# alias ssh='dssh'
# alias git='gitd'

# We extend the cd command to allow convenient
# shorthand notations, such as:
#   cd @site1
#   cd %modules
#   cd %devel
#   cd @site2:%files
# You must use 'cddl' instead of 'cd' if you are not using
# the optional 'cd' alias from above.
# This is the "local-only" version of the function;
# see the cdd function, below, for an expanded implementation
# that will ssh to the remote server when a remote site
# specification is used.
function cddl() {
  fastcddl "$1"
  use @self
}

# Use this function instead of 'cddl' if you have a very large number
# of alias files, and the 'cddl' function is getting too slow as a result.
# This function does not automatically set your prompt to the site that
# you 'cd' to, as 'cddl' does.
function fastcddl() {
  s="$1"
  if [ -z "$s" ]
  then
    builtin cd
  elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ]
  then
    d="$(drush drupal-directory $1 --local-only 2>/dev/null)"
    if [ $? == 0 ]
    then
      echo "cd $d";
      builtin cd "$d";
    else
      t="$(drush site-alias $1 >/dev/null 2>/dev/null)"
      if [ $? == 0 ]
      then
        echo "Cannot cd to remote site $s"
      else
        echo "Cannot cd to $s"
      fi
    fi
  else
    builtin cd "$s";
  fi
}

# Works just like the `cddl` shell alias above, with one additional
# feature: `cdd @remote-site` works like `ssh @remote-site`,
# whereas cd above will fail unless the site alias is local.  If
# you prefer this behavior, you can add `alias cd='cdd'` to your .bashrc
function cdd() {
  s="$1"
  if [ -z "$s" ]
  then
    builtin cd
  elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ]
  then
    d="$(drush drupal-directory $s 2>/dev/null)"
    rh="$(drush sa ${s%%:*} --fields=remote-host --format=list)"
    if [ -z "$rh" ]
    then
      echo "cd $d"
      builtin cd "$d"
    else
      if [ -n "$d" ]
      then
        c="cd \"$d\" \; bash"
        drush -s ${s%%:*} ssh --tty
        drush ${s%%:*} ssh --tty
      else
        drush ssh ${s%%:*}
      fi
    fi
  else
    builtin cd "$s"
  fi
}

# Allow `git @site gitcommand` as a shortcut for `cd @site; git gitcommand`.
# Also works on remote sites, though.
function gitd() {
  s="$1"
  if [ -n "$s" ] && [ ${s:0:1} == "@" ] || [ ${s:0:1} == "%" ]
  then
    d="$(drush drupal-directory $s 2>/dev/null)"
    rh="$(drush sa ${s%%:*} --fields=remote-host --format=list)"
    if [ -n "$rh" ]
    then
      drush ${s%%:*} ssh "cd '$d' ; git ${@:2}"
    else
      echo cd "$d" \; git "${@:2}"
      (
        cd "$d"
        "git" "${@:2}"
      )
    fi
  else
    "git" "$@"
  fi
}

# Get a directory listing on @site or @site:%files, etc, for local or remote sites.
function lsd() {
  p=()
  r=
  for a in "$@" ; do
    if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ]
    then
      p[${#p[@]}]="$(drush drupal-directory $a 2>/dev/null)"
      if [ ${a:0:1} == "@" ]
      then
        rh="$(drush sa ${a%:*} --fields=remote-host --format=list)"
        if [ -n "$rh" ]
        then
          r=${a%:*}
        fi
      fi
    elif [ -n "$a" ]
    then
      p[${#p[@]}]="$a"
    fi
  done
  if [ -n "$r" ]
  then
    drush $r ssh 'ls "${p[@]}"'
  else
    "ls" "${p[@]}"
  fi
}

# Copy files from or to @site or @site:%files, etc; local sites only.
function cpd() {
  p=()
  for a in "$@" ; do
    if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ]
    then
      p[${#p[@]}]="$(drush drupal-directory $a --local-only 2>/dev/null)"
    elif [ -n "$a" ]
    then
      p[${#p[@]}]="$a"
    fi
  done
  "cp" "${p[@]}"
}

# This alias allows `dssh @site` to work like `drush @site ssh`.
# Ssh commands, such as `dssh @site ls /tmp`, are also supported.
function dssh() {
  d="$1"
  if [ ${d:0:1} == "@" ]
  then
    drush "$d" ssh "${@:2}"
  else
    "ssh" "$@"
  fi
}

# Drush checks the current PHP version to ensure compatibility, and fails with
# an error if less than the supported minimum (currently 5.4.5). If you would
# like to try to run Drush on a lower version of PHP, you can un-comment the
# line below to skip this check. Note, however, that this is un-supported.

# DRUSH_NO_MIN_PHP=TRUE
;
; Example of a drush php settings override file.
;
; IMPORTANT: This file has no effect when using
; drush.phar. It is only effective when used
; a Composer built Drush is used. When a drush.phar
; hands off execution to a Composer built Drush,
; this file is effective.
;
; IMPORTANT:  Before following the instructions in
; this file, first check to see that the cli version
; of php is installed on your system.  (e.g. On
; debian systems, `sudo apt-get install php5-cli`.)
;
; Use this file in instances when your system is
; -not- configured to use separate php.ini files for
; webserver and cli use.  You can determine which
; php.ini file drush is using by running "drush status".
; If the php.ini file shown is your webserver ini
; file, then rename this file, example.drush.ini,
; to drush.ini and copy it to one of the following
; locations:
;
; 1. Drush installation folder
; 2. User's .drush folder (i.e. ~/.drush/drush.ini)
; 3. System wide configuration folder (i.e. /etc/drush/drush.ini)
;
; If the environment variable DRUSH_INI is defined,
; then the file it specified will be used as drush.ini.
;
;    export DRUSH_INI='/path/to/drush.ini'
;
; When in use, the variables defined in this file
; will override the setting values that appear in
; your php.ini file.  See the examples below for
; some values that may need to be set in order for
; drush to work.
;
; NOTE:  There is a certain amount of overhead
; required for each override, so drush.ini should
; only be used for a relatively small number
; of variables.  Comment out any variable that
; has the same value as the webserver php.ini
; to keep the size of the override list small.
;
; To fully specify the value of all php.ini variables,
; copy your webserver php.ini file to one of the
; locations mentioned above (e.g. /etc/drush/php.ini)
; and edit it to suit.  Alternately, you may use
; the environment variable PHP_INI to point at
; the file that Drush should use.
;
;    export PHP_INI='/path/to/php.ini'
;
; The options listed below are particularly relevant
; to drush.
;

;
; drush needs as much memory as Drupal in order
; to run; make the memory limit setting match
; what you have in your webserver's php.ini.
;
memory_limit = 128M

;
; Show all errors and direct them to stderr
; when running drush.
;
error_reporting = E_ALL | E_NOTICE | E_STRICT
display_errors = stderr

;
; If your php.ini for your webserver is too
; restrictive, you can re-enable functionality
; for drush by adjusting values in this file.
;
; Here are some examples of settings that are
; sometimes set to restrictive values in a
; webserver's php.ini:
;
;safe_mode =
;open_basedir =
;disable_functions =
;disable_classes =
<?php

/**
 * @file
 * Examples of valid statements for a Drush runtime config (drushrc) file.
 *
 * Use this file to cut down on typing out lengthy and repetitive command line
 * options in the Drush commands you use and to avoid mistakes.
 *
 * Rename this file to drushrc.php and optionally copy it to one of the places
 * listed below in order of precedence:
 *
 * 1.  Drupal site folder (e.g. sites/{default|example.com}/drushrc.php).
 * 2.  Drupal /drush and sites/all/drush folders, or the /drush folder
 *       in the directory above the Drupal root.
 * 3.  In any location, as specified by the --config (-c) option.
 * 4.  User's .drush folder (i.e. ~/.drush/drushrc.php).
 * 5.  System wide configuration folder (e.g. /etc/drush/drushrc.php).
 * 6.  Drush installation folder.
 *
 * If a configuration file is found in any of the above locations, it will be
 * loaded and merged with other configuration files in the search list.
 *
 * If you have some configuration options that are specific to a particular
 * version of Drush, then you may place them in a file called drush5rc.php.
 * The version-specific file is loaded in addition to, and after, the general-
 * purpose drushrc file.  Version-specific configuration files can be placed
 * in any of the locations specified above.
 *
 * IMPORTANT NOTE regarding configuration file on Windows:
 *
 * For Windows 7, Windows Vista, Windows Server 2008 and later versions is the
 * system window configuration folder C:\ProgramData\Drush.  For previous
 * versions of Windows is the folder C:\Documents and Settings\All Users\Drush.
 *
 * IMPORTANT NOTE on configuration file loading:
 *
 * At its core, Drush works by "bootstrapping" the Drupal environment in very
 * much the same way that is done during a normal page request from the web
 * server, so most Drush commands run in the context of a fully-initialized
 * website.
 *
 * Configuration files are loaded in the reverse order they are shown above. All
 * configuration files are loaded in the first bootstrapping phase, but
 * a configuration file in a specific Drupal site folder other than the default
 * (eg, sites/example.com/drushrc.php) will not be loaded unless a specific
 * Drupal site is selected.  However, it _will_ be loaded if a site is selected
 * (either via the current working directory or by use of the --uri option),
 * even if the Drush command being run does not bootstrap to the Drupal Site
 * phase.
 *
 * The Drush commands 'rsync' and 'sql-sync' are special cases.  These commands
 * will load the configuration file for the site specified by the source
 * parameter; however, they do not load the configuration file for the site
 * specified by the destination parameter, nor do they load configuration files
 * for remote sites.
 *
 * See `drush topic docs-bootstrap` for more information on how bootstrapping
 * affects the loading of Drush configuration files.
 */

// Specify the base_url that should be used when generating links
# $options['l'] = 'http://example.com/subdir';

// Specify your Drupal core base directory (useful if you use symlinks).
# $options['r'] = '/home/USER/workspace/drupal-6';

/**
 * Useful shell aliases:
 *
 * Drush shell aliases act similar to git aliases.  For best results, define
 * aliases in one of the drushrc file locations between #3 through #6 above.
 * More information on shell aliases can be found via:
 * `drush topic docs-shell-aliases` on the command line.
 *
 * @see https://git.wiki.kernel.org/index.php/Aliases#Advanced
 */
# $options['shell-aliases']['pull'] = '!git pull'; // We've all done it.
# $options['shell-aliases']['pulldb'] = '!git pull && drush updatedb';
# $options['shell-aliases']['noncore'] = 'pm-list --no-core';
# $options['shell-aliases']['self-alias'] = 'site-alias @self --with-db --alias-name=new';
# $options['shell-aliases']['site-get'] = '@none php-eval "return drush_sitealias_site_get();"';
// Add a 'pm-clone' to simplify git cloning from drupal.org.
# $options['shell-aliases']['pm-clone'] = 'pm-download --gitusername=YOURUSERNAME --package-handler=git_drupalorg';
// Save a sanitized sql dump. Customize alias names and --result-file.
# $options['shell-aliases']['sql-transfer'] = 'drush sql-sync @source @temp --sanitize && drush @temp sql-dump --result-file=/example && drush @temp sql-drop';

# Drupal 8.
# $options['shell-aliases']['offline'] = 'drush sset system.maintenance_mode 1 --input-format=integer';
# $options['shell-aliases']['online'] = 'drush sset system.maintenance_mode 0 --input-format=integer';
# $options['shell-aliases']['cpull'] = 'config-pull @example.prod @self --label=vcs';
# $options['shell-aliases']['wipe'] = 'cache-rebuild';

#Drupal 7 (and 6).
# $options['shell-aliases']['offline'] = 'variable-set -y --always-set maintenance_mode 1';
# $options['shell-aliases']['online'] = 'variable-delete -y --exact maintenance_mode';
# $options['shell-aliases']['wipe'] = 'cache-clear all';
# $options['shell-aliases']['dis-all'] = '!drush -y dis `drush pml --status=enabled --type=module --no-core --pipe`';
# $options['shell-aliases']['unsuck'] = 'pm-disable -y overlay,dashboard';

/**
 * Load a drushrc.php configuration file from the current working directory.
 */
# $options['config'][] = './drushrc.php';

/**
 * By default, Drush will download projects compatible with the current
 * version of Drupal, or, if no Drupal site is specified, then the Drupal-8
 * version of the project is downloaded.  Set default-major to select a
 * different default version.
 */
# $options['default-major'] = 7;

// Clone extensions (modules, themes, etc.) from drupal.org via 'pm-download'.
# $options['package-handler'] = 'git_drupalorg';

/**
 * Specify folders to search for Drush command files (*.drush.inc).  These
 * values are always merged with include paths defined on the command line or
 * in other configuration files.  On the command line, paths may be separated
 * by a colon (:) on Unix-based systems or a semi-colon (;) on Windows.
 */
# $options['include'] = array('/path/to/commands','/path2/to/more/commands');

/**
 * Specify the modules to ignore when searching for command files (*.drush.inc)
 * inside a Drupal site.
 */
# $options['ignored-modules'] = array('module1', 'module2');

/**
 * Specify the folders to search for Drush alias files (*.alias.drushrc.php and
 * *.aliases.drushrc.php).  These values are always merged with alias paths
 *  defined on the command line or in other configuration files.  On the command
 * line, paths may be separated by a colon (:) on Unix-based systems or a
 * semi-colon (;) on Windows.
 */
# $options['alias-path'] = array('/path/to/aliases','/path2/to/more/aliases');

/**
 * Specify the filename and path where 'sql-dump' should store backups of
 * database dumps.  The default is to dump to STDOUT, however if this option is
 * set in a drushrc.php file, the default behaviour can be achieved by
 * specifying a value of FALSE ("--result-file=0" on the command line).  Two
 * substitution tokens are available: @DATABASE is replaced with the name of the
 * database being dumped, and @DATE is replaced with the current time and date
 * of the dump of the form: YYYYMMDD_HHMMSS.  A value of TRUE ("--result-file=1"
 * on the command line) will cause 'sql-dump' to use the same temporary backup
 * location as 'pm-updatecode'.
 */
# $options['result-file'] = TRUE;
# $options['result-file'] = '/path/to/backup/dir/@DATABASE_@DATE.sql';

// Notify user via Notification Center (OSX) or libnotify (Linux) when command
// takes more than 30 seconds. See global options for more configuration.
# $options['notify'] = 30;

// Enable verbose mode.
# $options['v'] = 1;

// Show database passwords in 'status' and 'sql-conf' commands.
# $options['show-passwords'] = 1;

/**
 * Specify the logging level for PHP notices.  Defaults to "notice".  Set to
 * "warning" when doing Drush development.  Also make sure that error_reporting
 * is set to E_ALL in your php configuration file.  See `drush status` for the
 * path to your php.ini file.
 */
# $options['php-notices'] = 'warning';

/**
 * Specify the error handling of recoverable errors (E_RECOVERABLE_ERROR).
 * Defaults to 1 and will stop execution of Drush.
 * When set to 0, execution will continue.
 */
# $options['halt-on-error'] = 0;

/**
 * Specify options to pass to ssh in backend invoke.  The default is to prohibit
 * password authentication, and is included here, so you may add additional
 * parameters without losing the default configuration.
 */
# $options['ssh-options'] = '-o PasswordAuthentication=no';

// Set 'remote-os' to 'Windows' to make Drush use Windows shell escape rules
// for remote sites that do not have an 'os' item set.
# $options['remote-os'] = 'Linux';

// By default, unknown options are disallowed and result in an error.  Change
// them to issue only a warning and let command proceed.
# $options['strict'] = FALSE;

/**
 * Drush requires at least rsync version 2.6.9 for some functions to work
 * correctly.  rsync version 2.6.8 or earlier may give the following error
 * message: "--remove-source-files: unknown option".  To fix this, set
 * $options['rsync-version'] = '2.6.8'; (replace with the lowest version of
 * rsync installed on any system you are using with Drush).  Note that this
 * option can also be set in a site alias, which is the preferred solution if
 * newer versions of rsync are available on some of the systems you use.
 * See: http://drupal.org/node/955092
 */
# $options['rsync-version'] = '2.6.9';

/**
 * The output charset suitable to pass to the iconv PHP function's out_charset
 * parameter.
 *
 * Drush will convert its output from UTF-8 to the charset specified here.  It
 * is possible to use //TRANSLIT and //IGNORE charset name suffixes (see iconv
 * documentation).  If not defined, conversion will not be performed.
 */
# $options['output_charset'] = 'ISO-8859-1';
# $options['output_charset'] = 'KOI8-R//IGNORE';
# $options['output_charset'] = 'ISO-8859-1//TRANSLIT';

/**
 * Multiple-site execution options:
 *
 * Some drush commands such as 'sql-sync' are intended for or capable of being
 * executed on multiple sites or server environments and will pass along the
 * options specified here to all instances of the command being executed.
 */

/**
 * By default, Drush will prepend the name of the site to the output of any
 * multiple-site command execution.  To disable this behavior, set the
 * "--no-label" option.
 */
# $options['no-label'] = TRUE;

/**
 * An explicit list of tables which should be included in sql-dump and sql-sync.
 */
# $options['tables']['common'] = array('user', 'permissions', 'role_permission', 'role');

/**
 * List of tables whose *data* is skipped by the 'sql-dump' and 'sql-sync'
 * commands when the "--structure-tables-key=common" option is provided.
 * You may add specific tables to the existing array or add a new element.
 */
# $options['structure-tables']['common'] = array('cache', 'cache_*', 'history', 'search_*', 'sessions', 'watchdog');

/**
 * List of tables to be omitted entirely from SQL dumps made by the 'sql-dump'
 * and 'sql-sync' commands when the "--skip-tables-key=common" option is
 * provided on the command line.  This is useful if your database contains
 * non-Drupal tables used by some other application or during a migration for
 * example.  You may add new tables to the existing array or add a new element.
 */
# $options['skip-tables']['common'] = array('migration_*');

/**
 * Override specific entries in Drupal's variable system or settings.php (D6/D7 only).
 */
# $options['variables']['site_name'] = 'My Drupal site';
# $options['variables']['theme_default'] = 'minnelli';
# $options['variables']['anonymous'] = 'Visitor';

/**
 * Command-specific execution options:
 *
 * Most execution options can be shared between multiple Drush commands; these
 * are specified as top-level elements of the $options array in the prior
 * examples above.  On the other hand, other options are command-specific, and,
 * in some cases, a shared option needs a different configuration depending on
 * which command is being executing.
 *
 * To define options that are only applicable to certain commands, make an entry
 * in the $command-specific array as shown below.  The name of the command may
 * be either the command's full name or any of the command's aliases.
 *
 * Options defined here will be overridden by options of the same name on the
 * command line.  Unary flags such as "--verbose" are overridden via special
 * "--no-xxx" options (e.g. "--no-verbose").
 *
 * Limitation: If 'verbose' is set in a command-specific option, it must be
 * cleared by '--no-verbose', not '--no-v', and visa-versa.
 */

// Ensure all rsync commands use verbose output.
# $command_specific['rsync'] = array('verbose' => TRUE);

// Prevent drush ssh command from adding a cd to Drupal root before provided command.
# $command_specific['ssh'] = array('cd' => FALSE);

// Additional folders to search for scripts.
// Separate by : (Unix-based systems) or ; (Windows).
# $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts';

// Always show release notes when running pm-update or pm-updatecode.
# $command_specific['pm-update'] = array('notes' => TRUE);
# $command_specific['pm-updatecode'] = array('notes' => TRUE);

// Set a predetermined username and password when using site-install.
# $command_specific['site-install'] = array('account-name' => 'alice', 'account-pass' => 'secret');

// Use Drupal version specific CLI history instead of per site.
# $command_specific['core-cli'] = array('version-history' => TRUE);
; Example makefile
; ----------------
; This is an example makefile to introduce new users of drush_make to the
; syntax and options available to drush_make. For a full description of all
; options available, see README.txt.

; This make file is a working makefile - try it! Any line starting with a `;`
; is a comment.

; Core version
; ------------
; Each makefile should begin by declaring the core version of Drupal that all
; projects should be compatible with.

core = 7.x

; API version
; ------------
; Every makefile needs to declare it's Drush Make API version. This version of
; drush make uses API version `2`.

api = 2

; Core project
; ------------
; In order for your makefile to generate a full Drupal site, you must include
; a core project. This is usually Drupal core, but you can also specify
; alternative core projects like Pressflow. Note that makefiles included with
; install profiles *should not* include a core project.

; Use Pressflow instead of Drupal core:
; projects[pressflow][type] = "core"
; projects[pressflow][download][type] = "file"
; projects[pressflow][download][url] = "http://launchpad.net/pressflow/6.x/6.15.73/+download/pressflow-6.15.73.tar.gz"

; Git clone of Drupal 7.x. Requires the `core` property to be set to 7.x.
; projects[drupal][type] = "core"
; projects[drupal][download][type] = git
; projects[drupal][download][url] = http://git.drupal.org/project/drupal.git

projects[] = drupal

; Projects
; --------
; Each project that you would like to include in the makefile should be
; declared under the `projects` key. The simplest declaration of a project
; looks like this:

; To include the most recent views module:

projects[] = views

; This will, by default, retrieve the latest recommended version of the project
; using its update XML feed on Drupal.org. If any of those defaults are not
; desirable for a project, you will want to use the keyed syntax combined with
; some options.

; If you want to retrieve a specific version of a project:

; projects[views] = 2.16

; Or an alternative, extended syntax:

projects[ctools][version] = 1.3

; Check out the latest version of a project from Git. Note that when using a
; repository as your project source, you must explicitly declare the project
; type so that drush_make knows where to put your project.

projects[data][type] = module
projects[data][download][type] = git
projects[data][download][url] = http://git.drupal.org/project/views.git
projects[data][download][revision] = DRUPAL-6--3

; For projects on drupal.org, some shorthand is available. If any
; download parameters are specified, but not type, the default is git.
projects[cck_signup][download][revision] = "2fe932c"
; It is recommended to also specify the corresponding branch so that
; the .info file rewriting can obtain a version string that works with
; the core update module
projects[cck_signup][download][branch] = "7.x-1.x"

; Clone a project from github.

projects[tao][type] = theme
projects[tao][download][type] = git
projects[tao][download][url] = git://github.com/developmentseed/tao.git

; If you want to install a module into a sub-directory, you can use the
; `subdir` attribute.

projects[admin_menu][subdir] = custom

; To apply a patch to a project, use the `patch` attribute and pass in the URL
; of the patch.

projects[admin_menu][patch][687750] = "http://drupal.org/files/issues/admin_menu.long_.31.patch"

; If all projects or libraries share common attributes, the `defaults`
; array can be used to specify these globally, rather than
; per-project.

defaults[projects][subdir] = "contrib"
# Example makefile
# ----------------
# This is an example makefile to introduce new users of drush make to the
# syntax and options available to drush make.

# This make file is a working makefile - try it! Any line starting with a `#`
# is a comment.

# Core version
# ------------
# Each makefile should begin by declaring the core version of Drupal that all
# projects should be compatible with.

core: "7.x"

# API version
# ------------
# Every makefile needs to declare it's Drush Make API version. This version of
# drush make uses API version `2`.

api: 2

# Core project
# ------------
# In order for your makefile to generate a full Drupal site, you must include
# a core project. This is usually Drupal core, but you can also specify
# alternative core projects like Pressflow. Note that makefiles included with
# install profiles *should not* include a core project.

# Use Pressflow instead of Drupal core:
# projects:
#   pressflow:
#     type: "core"
#     download:
#       type: "file"
#       url: "http://launchpad.net/pressflow/6.x/6.15.73/+download/pressflow-6.15.73.tar.gz"
#
# Git clone of Drupal 7.x. Requires the `core` property to be set to 7.x.
# projects
#   drupal:
#     type: "core"
#     download:
#       url: "http://git.drupal.org/project/drupal.git"

projects:
  drupal:
    version: ~

  # Projects
  # --------
  # Each project that you would like to include in the makefile should be
  # declared under the `projects` key. The simplest declaration of a project
  # looks like this:

  # To include the most recent views module:

  views:
    version: ~

  # This will, by default, retrieve the latest recommended version of the
  # project using its update XML feed on Drupal.org. If any of those defaults
  # are not desirable for a project, you will want to use the keyed syntax
  # combined with some options.

  # If you want to retrieve a specific version of a project:

  # projects:
  #   views: "2.16"

  # Or an alternative, extended syntax:

  ctools:
    version: "1.3"

  # Check out the latest version of a project from Git. Note that when using a
  # repository as your project source, you must explicitly declare the project
  # type so that drush_make knows where to put your project.

  data:
    type: "module"
    download:
      type: "git" # Note, 'git' is the default, no need to specify.
      url: "http://git.drupal.org/project/views.git"
      revision: "7.x-3.x"

  # For projects on drupal.org, some shorthand is available. If any
  # download parameters are specified, but not type, the default is git.
  cck_signup:
    download:
      revision: "2fe932c"
      # It is recommended to also specify the corresponding branch so that
      # the .info file rewriting can obtain a version string that works with
      # the core update module
      branch: "7.x-1.x"

  # Clone a project from github.

  tao:
    type: theme
    download:
      url: "git://github.com/developmentseed/tao.git"

  # If you want to install a module into a sub-directory, you can use the
  # `subdir` attribute.

  admin_menu:
    subdir: custom

  # To apply patches to a project, use the `patch` attribute and pass in the URL
  # of the patch, one per line prefaced with `- `.

    patch:
      - "http://drupal.org/files/issues/admin_menu.long_.31.patch"

# If all projects or libraries share common attributes, the `defaults`
# array can be used to specify these globally, rather than
# per-project.

defaults:
  projects:
    subdir: "contrib"
# -*- mode: shell-script; mode: flyspell-prog; ispell-local-dictionary: "american" -*-
#
# Example PS1 prompt.
#
# Use `drush init` to copy this to ~/.drush/drush.prompt.sh, and source it in ~/.bashrc
#
# Features:
#
# Displays Git repository and Drush alias status in your prompt.
if [ -n "$(type -t __git_ps1)" ] && [ "$(type -t __git_ps1)" = function ] && [ "$(type -t __drush_ps1)" ] && [ "$(type -t __drush_ps1)" = function ]; then

  # This line enables color hints in your Drush prompt. Modify the below
  # __drush_ps1_colorize_alias() to customize your color theme.
  DRUSH_PS1_SHOWCOLORHINTS=true

  # Git offers various prompt customization options as well as seen in
  # https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh.
  # Adjust the following lines to enable the corresponding features:
  #
  GIT_PS1_SHOWDIRTYSTATE=true
  GIT_PS1_SHOWUPSTREAM=auto
  # GIT_PS1_SHOWSTASHSTATE=true
  # GIT_PS1_SHOWUNTRACKEDFILES=true
  GIT_PS1_SHOWCOLORHINTS=true

  # The following line sets your bash prompt according to this example:
  #
  #   username@hostname ~/working-directory (git-branch)[@drush-alias] $
  #
  # See http://ss64.com/bash/syntax-prompt.html for customization options.
  export PROMPT_COMMAND='__git_ps1 "\u@\h \w" "$(__drush_ps1 "[%s]") \\\$ "'

  # PROMPT_COMMAND is used in the example above rather than PS1 because neither
  # Git nor Drush color hints are compatible with PS1. If you don't want color
  # hints, however, and prefer to use PS1, you can still do so by commenting out
  # the PROMPT_COMMAND line above and uncommenting the PS1 line below:
  #
  # export PS1='\u@\h \w$(__git_ps1 " (%s)")$(__drush_ps1 "[%s]")\$ '

  __drush_ps1_colorize_alias() {
    if [[ -n ${ZSH_VERSION-} ]]; then
      local COLOR_BLUE='%F{blue}'
      local COLOR_CYAN='%F{cyan}'
      local COLOR_GREEN='%F{green}'
      local COLOR_MAGENTA='%F{magenta}'
      local COLOR_RED='%F{red}'
      local COLOR_WHITE='%F{white}'
      local COLOR_YELLOW='%F{yellow}'
      local COLOR_NONE='%f'
    else
      # Using \[ and \] around colors is necessary to prevent issues with
      # command line editing/browsing/completion.
      local COLOR_BLUE='\[\e[94m\]'
      local COLOR_CYAN='\[\e[36m\]'
      local COLOR_GREEN='\[\e[32m\]'
      local COLOR_MAGENTA='\[\e[35m\]'
      local COLOR_RED='\[\e[91m\]'
      local COLOR_WHITE='\[\e[37m\]'
      local COLOR_YELLOW='\[\e[93m\]'
      local COLOR_NONE='\[\e[0m\]'
    fi

    # Customize your color theme below.
    case "$__DRUPAL_SITE" in
      *.live|*.prod) local ENV_COLOR="$COLOR_RED" ;;
      *.stage|*.test) local ENV_COLOR="$COLOR_YELLOW" ;;
      *.local) local ENV_COLOR="$COLOR_GREEN" ;;
      *) local ENV_COLOR="$COLOR_BLUE" ;;
    esac

    __DRUPAL_SITE="${ENV_COLOR}${__DRUPAL_SITE}${COLOR_NONE}"
  }

fi
#!/usr/bin/env sh

#
# Git bisect is a helpful way to discover which commit an error
# occurred in.  This example file gives simple instructions for
# using git bisect with Drush to quickly find erroneous commits
# in Drush commands or Drupal modules, presuming that you can
# trigger the error condition via Drush (e.g. using `drush php-eval`).
#
# Follow these simple steps:
#
#   $ git bisect start
#   $ git bisect bad              # Tell git that the current commit does not work
#   $ git bisect good bcadd5a     # Tell drush that the commithash 12345 worked fine
#   $ git bisect run mytestscript.sh
#
# 'git bisect run' will continue to call 'git bisect good' and 'git bisect bad',
# based on whether the script's exit code was 0 or 1, respectively.
#
# Replace 'mytestscript.sh' in the example above with a custom script that you
# write yourself.  Use the example script at the end of this document as a
# guide.  Replace the example command with one that calls the Drush command
# that you would like to test, and replace the 'grep' string with a value
# that appears when the error exists in the commit, but does not appear when
# commit is okay.
#
# If you are using Drush to test Drupal or an external Drush module, use:
#
#   $ git bisect run drush mycommand --strict=2
#
# This presumes that there is one or more '[warning]' or '[error]'
# messages emitted when there is a problem, and no warnings or errors
# when the commit is okay.  Omit '--strict=2' to ignore warnings, and
# signal failure only when 'error' messages are emitted.
#
# If you need to test for an error condition explicitly, to find errors
# that do not return any warning or error log messages on their own, you
# can use the Drush php-eval command to force an error when `myfunction()`
# returns FALSE. Replace 'myfunction()' with the name of an appropriate
# function in your module that can be used to detect the error condition
# you are looking for.
#
#   $ git bisect run drush ev 'if(!myfunction()) { return drush_set_error("ERR"); }'
#
drush mycommand --myoption 2>&1 | grep -q 'string that indicates there was a problem'
if [ $? == 0 ] ; then
  exit 1
else
  exit 0
fi
#!/usr/bin/env drush

//
// This example demonstrates how to write a drush
// "shebang" script.  These scripts start with the
// line "#!/usr/bin/env drush" or "#!/full/path/to/drush".
//
// See `drush topic docs-scripts` for more information.
//
drush_print("Hello world!");
drush_print();
drush_print("The arguments to this command were:");

//
// If called with --everything, use drush_get_arguments
// to print the commandline arguments.  Note that this
// call will include 'php-script' (the drush command)
// and the path to this script.
//
if (drush_get_option('everything')) {
  drush_print("  " . implode("\n  ", drush_get_arguments()));
}
//
// If --everything is not included, then use
// drush_shift to pull off the arguments one at
// a time.  drush_shift only returns the user
// commandline arguments, and does not include
// the drush command or the path to this script.
//
else {
  while ($arg = drush_shift()) {
    drush_print('  ' . $arg);
  }
}

drush_print();

//
// We can check which site was bootstrapped via
// the '@self' alias, which is defined only if
// there is a bootstrapped site.
//
$self_record = drush_sitealias_get_record('@self');
if (empty($self_record)) {
  drush_print('No bootstrapped site.');
}
else {
  drush_print('The following site is bootstrapped:');
  _drush_sitealias_print_record($self_record);
}
<?php

/**
 * Implements hook_pm_post_update().
 *
 * Restore sqlsrv driver after core update.
 */
function pm_update_pm_post_update($project_name, $installed_release, $project) {
  // Restore sqlsrv database driver.
  if ($project_name == 'drupal') {
    $sqlsrv_dir = 'includes/database/sqlsrv';
    if (file_exists($project['backup_target'] . '/' . $sqlsrv_dir)) {
      if (drush_copy_dir($project['backup_target'] . '/' . $sqlsrv_dir, $project['full_project_path'] . '/' . $sqlsrv_dir)) {
        drush_log('SQLSRV database driver has been restored.', 'info');
      }
    }
  }
}

<?php

/**
 * @file
 * Example policy commandfile. Modify as desired.
 *
 * Validates commands as they are issued and returns an error
 * or changes options when policy is violated.
 *
 * You can copy this file to any of the following
 *   1. A .drush folder in your HOME folder.
 *   2. Anywhere in a folder tree below an active module on your site.
 *   3. /usr/share/drush/commands (configurable)
 *   4. In an arbitrary folder specified with the --include option.
 *   5. Drupal's /drush or sites/all/drush folder, or in the /drush
 *        folder in the directory above the Drupal root (note: sql-sync
 *        validation won't work in any of these locations).
 */

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * Prevent catastrophic braino. Note that this file has to be local to the
 * machine that intitiates sql-sync command.
 */
function drush_policy_sql_sync_validate($source = NULL, $destination = NULL) {
  if ($destination == '@prod') {
    return drush_set_error('POLICY_DENY', dt('Per examples/policy.drush.inc, you may never overwrite the production database.'));
  }
}

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * We can also limit rsync operations to production sites.
 */
function drush_policy_core_rsync_validate($source = NULL, $destination = NULL) {
  if (preg_match("/^@prod/", $destination)) {
    return drush_set_error('POLICY_DENY', dt('Per examples/policy.drush.inc, you may never rsync to the production site.'));
  }
}

/**
 * Implements hook_drush_sitealias_alter
 *
 * Alter alias record data in code.
 */
function policy_drush_sitealias_alter(&$alias_record) {
  // A duplicate of the old implementation of the 'parent' element.
  // Keep this if you want to keep using 'parent', but do not want
  // to be nagged (or worse, break when it is removed).
  if (isset($alias_record['parent'])) {
    // Fetch and merge in each parent
    foreach (explode(',', $alias_record['parent']) as $parent) {
      $parent_record = drush_sitealias_get_record($parent);
      unset($parent_record['#name']);
      unset($parent_record['#file']);
      unset($parent_record['#hidden']);
      $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases'));
      foreach ($array_based_keys as $array_based_key) {
        if (isset($alias_record[$array_based_key]) && isset($parent_record[$array_based_key])) {
          $alias_record[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_record[$array_based_key]);
        }
      }
      $alias_record = array_merge($parent_record, $alias_record);
    }
    unset($alias_record['parent']);
  }
}

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * Encourage folks to use `composer` instead of Drush pm commands
 */
function drush_policy_pm_updatecode_validate() {
  return _deny_message();
}

function drush_policy_pm_update_validate() {
  return _deny_message();
}

function drush_policy_pm_download_validate() {
  return _deny_message();
}

function _deny_message() {
  if (!drush_get_option('pm-force')) {
    $msg = 'This codebase is assembled with Composer instead of Drush. Use `composer update` and `composer require` instead of `drush pm-updatecode` and `drush pm-download`. You may override this error by using the --pm-force option.';
    return drush_set_error('POLICY_PM_DENY', dt($msg));
  }
}

/**
 * Implements hook_drush_help_alter().
 *
 * When a hook extends a command with additional options, it must
 * implement help alter and declare the option(s).  Doing so will add
 * the option to the help text for the modified command, and will also
 * allow the new option to be specified on the command line.  Without
 * this, Drush will fail with an error when a user attempts to use
 * the option.
 */
function policy_drush_help_alter($command) {
  if ($command['command'] == 'updatedb') {
    $command['options']['token'] = 'Per site policy, you must specify a token in the --token option for all commands.';
  }
  elseif (in_array($command['command'], array('pm-updatecode', 'pm-update', 'pm-download'))) {
    $command['options']['pm-force'] = 'Override site policy and allow Drush codebase management (pm-* commands)';
  }
}

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * To test this example without copying, execute
 * `drush --include=./examples updatedb` from within your drush directory.
 *
 * Unauthorized users may view pending updates but not execute them.
 */
function drush_policy_updatedb_validate() {
  // Check for a token in the request. In this case, we require --token=secret.
  if (!drush_get_option('token') == 'secret') {
    drush_log(dt('Per site policy, you must add a secret --token complete this command. See examples/policy.drush.inc.  If you are running a version of drush prior to 4.3 and are not sure why you are seeing this message, please see http://drupal.org/node/1024824.'), 'warning');
    drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
    drush_set_context('DRUSH_NEGATIVE', TRUE);
  }
}

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * Only sudo tells me to make a sandwich: http://xkcd.com/149/
 */
function drush_policy_make_me_a_sandwich_validate() {
  if (drush_is_windows()) {
    // $name = drush_get_username();
    // TODO: implement check for elevated process using w32api
    // as sudo is not available for Windows
    // @see http://php.net/manual/en/book.w32api.php
    // @see http://social.msdn.microsoft.com/Forums/en/clr/thread/0957c58c-b30b-4972-a319-015df11b427d
  }
  else {
    $name = posix_getpwuid(posix_geteuid());
    if ($name['name'] !== 'root') {
      return drush_set_error('POLICY_MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
    }
  }
}
::::::::::::::::::::::::::::::::::MMMMMMMMMMMM:::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::MMMMMMMM:::::::::::MM:::::::::::::::::::::::::::::::::
:::::::::::::::::::MMMMM::::::::::::::::::::MMMM:::::::::::::::::::::::::::::::
::::::::::::::MMMMM:::::::::M::::::::M::::::::::MMMM:::::::::::::::::::::::::::
::::::::::MMMM::M:::::::::::::::::::::::::::::::::::MMMM:::::::::::::::::::::::
:::::::MMM::::::::::::::::::::::::::::::::::::::::::::::MMMM:::::::::::::::::::
:::::MM:::::::::::::::::::::::::::::::::::::::::::::::::::::MMMM:::::::::::::::
:::::M::::::::::::::::::::::::::::::::::::::::::::::::::::::::::MMM::::::::::::
:::::MMM$MM::::::::::::::::::::::::::::::::::::::::::::::::::::::::MMM:::::::::
:::::M$$$$$MM:::::::::::::::::::::::::::::::::::::::::::::M::::::::::MMM:::::::
::MMMMM$M$$$$MM:::::::::M:::::::::::::::::::::::::::::::::::::::::::MMMMM::::::
::MIIIMMMM$M$$$MM:::::::::::::::::::::::::::::::::::::::::::MMMMMMM$$$$M:::::::
:::MIIIIIIMM$$$$$MM::::::::::::::M:::::::::::::::::::MMMMMMM$$$$$$$$$$$$:::::::
:MMIIIIIIIIMM$$$$$$MMM::::::::::::::::::::::::MMMMMM$$$$$$$$$$$$$$$$$$$$M::::::
MIIMMMMMIIIIIMMM$$$$$$MM:::::::::::::::MMMMMM$$$$$$$$$$$$$$$$$$$$$$$$$$$M::::::
MMM:MM:MMMMIIIIMMM$$$$$$MM:::::::MMMMMMM$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$MMMMM::
::M::::MM::IIIIIIMM$$$$$$$$MMMMM$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$MMMMMMMMMIIM:::
::MM:::M::MIIMMIIIIIM$M$$$$M$$$$$$$$$$$$$$$$$$$$$$$$$$$$MMMMMMMIIIIIMMMMMMM::::
::::MM::MMMMM:MIIIIIIMM$M$$M$$$$$$$$$$$$$$$$$$$$$$MMMMMMMIIIIIIIIIIIM::MMM:::::
::::::$MM:::::MIIIMMIIIMM$M$$$$$$$$$$$$$$$$MMMMMMIMMMIIIIM:::MMMIIIIM:::::MM:::
::::::$$$MM:::::M::MIIIMMMM$M$$$$$$$MMMMMMIIIIIIMMM:MMMMM:::::::::::::MMMMMM:::
::::::M$$$$MM:MMM::MMM:::MIMMMMMMMMMMIIMMIIIIIIIMM+M:::::MMMM::::MMMMM$M:::::::
::::::MM$M$$$M+$$M::::::MIIIIIIIMIIIIM::::MIIIIIM:MM::::M$$M+MM$$$$$$$$::::::::
:::::::MMM$$$$$$$M:::::::MIIIIIM::MMM:::::::MMMM::::::::M$$M$$$$$$$$$$$M:::::::
::::::::::MM$$$$$$$MM::::MMMMMM:::::::::::::::::::MMMMMMM$$$$$$$$$$$$$$M:::::::
::::::::::::MM$M$$$$$MM:MM$$$$M::::::::::MMMMMMM$$$$$$$$$$$$$$$$$$MMMMM::::::::
::::::::::::::MMM$M$$$$MM$$$$$M:MMMMMMM$$$$$$$$$$$$$$$$$$$$$MMMMMM:::::::::::::
:::::::::::::::::MMM$M$M$$$$$$MMM$$$$$$$$$$$$$$$$$$$$$$MMMMM:M:M:::::::::::::::
:::::::::::::::::::MMMM$$M$$$$$$M$$$$$$$$$$$$$$$$$MMMMMM::::::M::::::::::::::::
::::::::::::::::::::::MMMMM$$$$$M$$$$$$$$$$$$$MMMMM::::::::::::::::::::::::::::
:::::::::::::::::::::::::MMMM$M$M$$$$$$$$$MMMMM::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::MMMMM$$$$MMMM::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::MMMMM:::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
I have discovered a truly marvelous proof that it is impossible to 
separate a sandwich into two cubes, or four sandwiches into two
fourth of a sandwich, or in general, any sandwich larger than the 
second into two like sandwiches. This text file is too narrow to contain it.
<?php

/**
 * @file
 * Example drush command.
 *
 * To run this *fun* command, execute `sudo drush --include=./examples mmas`
 * from within your drush directory.
 *
 * See `drush topic docs-commands` for more information about command authoring.
 *
 * You can copy this file to any of the following
 *   1. A .drush folder in your HOME folder.
 *   2. Anywhere in a folder tree below an active module on your site.
 *   3. /usr/share/drush/commands (configurable)
 *   4. In an arbitrary folder specified with the --include option.
 *   5. Drupal's /drush or /sites/all/drush folders, or in the /drush
 *        folder in the directory above the Drupal root.
 */

/**
 * Implements hook_drush_command().
 *
 * In this hook, you specify which commands your
 * drush module makes available, what it does and
 * description.
 *
 * Notice how this structure closely resembles how
 * you define menu hooks.
 *
 * See `drush topic docs-commands` for a list of recognized keys.
 */
function sandwich_drush_command() {
  $items = array();

  // The 'make-me-a-sandwich' command.
  $items['make-me-a-sandwich'] = array(
    'description' => "Makes a delicious sandwich.",
    'arguments' => array(
      'filling' => 'The type of the sandwich (turkey, cheese, etc.). Defaults to ascii.',
    ),
    'options' => array(
      'spreads' => array(
        'description' => 'Comma delimited list of spreads.',
        'example-value' => 'mayonnaise,mustard',
      ),
    ),
    'examples' => array(
      'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
    ),
    'aliases' => array('mmas'),
    // No bootstrap at all.
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );

  // The 'sandwiches-served' command.  Informs how many 'mmas' commands
  // completed.
  $items['sandwiches-served'] = array(
    'description' => "Report how many sandwiches we have made.",
    'examples' => array(
      'drush sandwiches-served' => 'Show how many sandwiches we have served.',
    ),
    'aliases' => array('sws'),
    // Example output engine data:  command returns a single keyed
    // data item (e.g. array("served" => 1)) that can either be
    // printed with a label (e.g. "served: 1"), or output raw with
    // --pipe (e.g. "1").
    'engines' => array(
      'outputformat' => array(
        'default' => 'key-value',
        'pipe-format' => 'string',
        'label' => 'Sandwiches Served',
        'require-engine-capability' => array('format-single'),
      ),
    ),
    // No bootstrap at all.
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );

  // The 'spreads-status' command.  Prints a table about available spreads.
  $items['spreads-status'] = array(
    'description' => "Show a table of information about available spreads.",
    'examples' => array(
      'drush spreads-status' => 'Show a table of spreads.',
    ),
    'aliases' => array('sps'),
    // Example output engine data:  command returns a deep array
    // that can either be printed in table format or as a json array.
    'engines' => array(
      'outputformat' => array(
        'default' => 'table',
        'pipe-format' => 'json',
        // Commands that return deep arrays will usually use
        // machine-ids for the column data.  A 'field-labels'
        // item maps from the machine-id to a human-readable label.
        'field-labels' => array(
          'name' => 'Name',
          'description' => 'Description',
          'available' => 'Num',
          'taste' => 'Taste',
        ),
        // In table format, the 'column-widths' item is consulted
        // to determine the default weights for any named column.
        'column-widths' => array(
          'name' => 10,
          'available' => 3,
        ),
        'require-engine-capability' => array('format-table'),
      ),
    ),
    // No bootstrap at all.
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );

  // Commandfiles may also add topics.  These will appear in
  // the list of topics when `drush topic` is executed.
  // To view this topic, run `drush --include=/full/path/to/examples topic`
  $items['sandwich-exposition'] = array(
    'description' => 'Ruminations on the true meaning and philosophy of sandwiches.',
    'hidden' => TRUE,
    'topic' => TRUE,
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
    'callback' => 'drush_print_file',
    'callback arguments' => array(dirname(__FILE__) . '/sandwich-topic.txt'),
  );

  return $items;
}

/**
 * Implements hook_drush_help().
 *
 * This function is called whenever a drush user calls
 * 'drush help <name-of-your-command>'. This hook is optional. If a command
 * does not implement this hook, the command's description is used instead.
 *
 * This hook is also used to look up help metadata, such as help
 * category title and summary.  See the comments below for a description.
 */
function sandwich_drush_help($section) {
  switch ($section) {
    case 'drush:make-me-a-sandwich':
      return dt("This command will make you a delicious sandwich, just how you like it.");

    // The 'title' meta item is used to name a group of
    // commands in `drush help`.  If a title is not defined,
    // the default is "All commands in ___", with the
    // specific name of the commandfile (e.g. sandwich).
    // Command files with less than four commands will
    // be placed in the "Other commands" section, _unless_
    // they define a title.  It is therefore preferable
    // to not define a title unless the file defines a lot
    // of commands.
    case 'meta:sandwich:title':
      return dt("Sandwich commands");

    // The 'summary' meta item is displayed in `drush help --filter`,
    // and is used to give a general idea what the commands in this
    // command file do, and what they have in common.
    case 'meta:sandwich:summary':
      return dt("Automates your sandwich-making business workflows.");
  }
}

/**
 * Implements drush_hook_COMMAND_validate().
 *
 * The validate command should exit with
 * `return drush_set_error(...)` to stop execution of
 * the command.  In practice, calling drush_set_error
 * OR returning FALSE is sufficient.  See drush.api.php
 * for more details.
 */
function drush_sandwich_make_me_a_sandwich_validate() {
  if (drush_is_windows()) {
    // $name = drush_get_username();
    // @todo Implement check for elevated process using w32api
    // as sudo is not available for Windows
    // @see http://php.net/manual/en/book.w32api.php
    // @see http://social.msdn.microsoft.com/Forums/en/clr/thread/0957c58c-b30b-4972-a319-015df11b427d
  }
  else {
    $name = posix_getpwuid(posix_geteuid());
    if ($name['name'] !== 'root') {
      return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
    }
  }
}

/**
 * Implements drush_hook_COMMAND().
 *
 * The command callback is where the action takes place.
 *
 * The function name should be same as command name but with dashes turned to
 * underscores and 'drush_commandfile_' prepended, where 'commandfile' is
 * taken from the file 'commandfile.drush.inc', which in this case is
 * 'sandwich'. Note also that a simplification step is also done in instances
 * where the commandfile name is the same as the beginning of the command name,
 * "drush_example_example_foo" is simplified to just "drush_example_foo".
 * To also implement a hook that is called before your command, implement
 * "drush_hook_pre_example_foo".  For a list of all available hooks for a
 * given command, run drush in --debug mode.
 *
 * If for some reason you do not want your hook function to be named
 * after your command, you may define a 'callback' item in your command
 * object that specifies the exact name of the function that should be
 * called.
 *
 * In this function, all of Drupal's API is (usually) available, including
 * any functions you have added in your own modules/themes.
 *
 * @see drush_invoke()
 * @see drush.api.php
 */
function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
  $str_spreads = '';
  // Read options with drush_get_option. Note that the options _must_
  // be documented in the $items structure for this command in the 'command'
  // hook. See `drush topic docs-commands` for more information.
  if ($spreads = drush_get_option('spreads')) {
    $list = implode(' and ', explode(',', $spreads));
    $str_spreads = ' with just a dash of ' . $list;
  }
  $msg = dt('Okay. Enjoy this !filling sandwich!str_spreads.',
            array('!filling' => $filling, '!str_spreads' => $str_spreads)
         );
  drush_print("\n" . $msg . "\n");

  if (drush_get_context('DRUSH_NOCOLOR')) {
    $filename = dirname(__FILE__) . '/sandwich-nocolor.txt';
  }
  else {
    $filename = dirname(__FILE__) . '/sandwich.txt';
  }
  drush_print(file_get_contents($filename));
  // Find out how many sandwiches have been served, and set
  // the cached value to one greater.
  $served = drush_sandwich_sandwiches_served();
  drush_cache_set(drush_get_cid('sandwiches-served'), $served + 1);
}

/**
 * Implements drush_hook_COMMAND().
 *
 * Demonstrates how to return a simple value that is transformed by
 * the selected formatter to display either with a label (using the
 * key-value formatter) or as the raw value itself (using the string formatter).
 */
function drush_sandwich_sandwiches_served() {
  $served = 0;
  $served_object = drush_cache_get(drush_get_cid('sandwiches-served'));
  if ($served_object) {
    $served = $served_object->data;
  }
  // In the default format, key-value, this return value
  // will print " Sandwiches Served    :  1".  In the default pipe
  // format, only the array value ("1") is returned.
  return $served;
}

/**
 * Implements drush_hook_COMMAND().
 *
 * This ficticious command shows how a deep array can be constructed
 * and used as a command return value that can be output by different
 * output formatters.
 */
function drush_sandwich_spreads_status() {
  return array(
    'ketchup' => array(
      'name' => 'Ketchup',
      'description' => 'Some say its a vegetable, but we know its a sweet spread.',
      'available' => '7',
      'taste' => 'sweet',
    ),
    'mayonnaise' => array(
      'name' => 'Mayonnaise',
      'description' => 'A nice dairy-free spead.',
      'available' => '12',
      'taste' => 'creamy',
    ),
    'mustard' => array(
      'name' => 'Mustard',
      'description' => 'Pardon me, but could you please pass that plastic yellow bottle?',
      'available' => '8',
      'taste' => 'tangy',
    ),
    'pickles' => array(
      'name' => 'Pickles',
      'description' => 'A necessary part of any sandwich that does not taste terrible.',
      'available' => '63',
      'taste' => 'tasty',
    ),
  );
}

/**
 * Command argument complete callback.
 *
 * Provides argument values for shell completion.
 *
 * @return array
 *   Array of popular fillings.
 */
function sandwich_make_me_a_sandwich_complete() {
  return array('values' => array('turkey', 'cheese', 'jelly', 'butter'));
}
 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 . . .  . . . . . . . .:8 ;t;;t;;;;:..:;%SX888@X%t;.. . . . 
 . .  .. . . . . . .%t%;%@%%%%%%%%%%X@8888XS%t;...:;ttt%X. .
 . . .  . . . . . .X:8%X%%%XS%%%%%%%XS%%%%%@%%%%X%%%@%S88 . 
 . . . . . .  .  . X@ @%%%X8X%%%88%%%8X%%%%%%%%%XXt@8@88@. .
 . . . . .  . . .t@tS;%%8XSX%@XSX%@XSS%8@@88X@888X8SS S;S.  
 . . . . . . .@%XS%%%%%S8@X%@8%XXSSXX%S@SSSX888.;@ 888@ . . 
 . . . . . :.8:S%%%XS8X@@X%S@SSSS8SXSXXX%X88X:;@8@:S  88S. .
 . . .  .8%S8%%%%%%8@SSSXXXSXSXSXSXSSS8S888 :@%:%XX:%8%:X:  
 . .  .:8 %%%%@%%8@S%%XXSXSSSS8S@X%XSXX88 ;@X;SX88X8;%X88t. 
 . . 88S%S%%%%8XSSXSX@@S@%XS8@SS%@S%888 88@S:8. .;.@%X:@8;. 
 . .  88.8888888@XX   888888%X%@XX 88SS8@@;S@8.%;8@S%%:8   .
 . .  S%:8 @SSSS8 @@8@8 8 88888888@%S:8:S8 @..%S SXX8888;. .
 . . %:8S8888@88SXS S S::X@.8.8 X%S%8X:X88..% @@.S.%% .;. . 
 .    .XX8@8;;%%t;;;;:@X@888888@888888.88S;8:8  ...    . . .
 . . 8.;;@8@8:%%%%%t.8@%ttX@8@@@S8%8 X8S;X:@; :... . . . . .
 . tS:8@;88.;:8888X8S:.tX88888X88  S8tStS88 :.. . . . . . . 
 .:X;;:t%;tt%888S@8XS888@8.:tt@;88.tXXX8:::... . . . . . . .
 .:X8St:8SXS XS8@X 8.8%888%X8@@X88tXS8t; . . . . . . .  . . 
 .    :8888.88888888X@@X @ X X%S%;88;8t .. . .  . . . . .  .
 ... ..: .    .@@888%St    @ @ 8SS 8:; . . . . . . . . . . .
   .  .  .      ..::. ..:;;::::. ... . . . . . . . . . . . .
 .. . . .     . .. . .  .  .  .. . . . . . .  . . . . . . . 
<?php

/**
 * @file
 * Example "Sync enable" sql-sync command alter.
 *
 * For Drupal 8, please use the config_split module instead of this code. Drupal
 * 6 and 7 sites may use this example.
 *
 * Sync_enable adds options to sql-sync to enable and disable
 * modules after an sql-sync operation.  One use case for this
 * is to use Drush site aliases to automatically enable your
 * development modules whenever you sync from your live site to
 * your dev site.  You may also add or remove permissions at
 * the same time.
 *
 * For example:
 *
 * @code
 * $aliases['dev'] = array (
 *   'root' => '/srv/www/drupal',
 *   'uri' => 'site.com',
 *   'target-command-specific' => array(
 *     'sql-sync'  => array(
 *       'enable'  => array('devel', 'hacked'),
 *       'disable' => array('securepages'),
 *       'permission' => array(
 *         'authenticated user' => array(
 *           'add' => array('access devel information', 'access environment indicator'),
 *           'remove' => 'change own password',
 *         ),
 *         'anonymous user' => array(
 *           'add' => 'access environment indicator',
 *         ),
 *       ),
 *     ),
 *   ),
 * );
 * @endcode
 *
 * To use this feature, copy the 'target-command-specific'
 * item from the example alias above, place it in your development
 * site aliases, and customize the development module list
 * to suit.  You must also copy the sync_enable.drush.inc
 * file to a location where Drush will find it, such as
 * $HOME/.drush.  See `drush topic docs-commands` for more
 * information.
 *
 * To set variables on a development site:
 *
 * Instead of calling variable_set and variable_delete in a post-sync
 * hook, consider adding $conf variables to settings.php.
 *
 * For example:
 *
 * $conf['error_level'] = 2;
 * error_reporting(E_ALL);
 * ini_set('display_errors', TRUE);
 * ini_set('display_startup_errors', TRUE);
 * $conf['preprocess_css'] = 0;
 * $conf['cache'] = 0;
 * $conf['googleanalytics_account'] = '';
 */

/**
 * Implements hook_drush_help_alter().
 *
 * When a hook extends a command with additional options, it must
 * implement help alter and declare the option(s).  Doing so will add
 * the option to the help text for the modified command, and will also
 * allow the new option to be specified on the command line.  Without
 * this, Drush will fail with an error when a user attempts to use
 * the option.
 */
function sync_enable_drush_help_alter(&$command) {
  if ($command['command'] == 'sql-sync') {
    $command['options']['updb']  = "Apply database updates on the target database after the sync operation has completed.";
    $command['options']['enable']  = "Enable the specified modules in the target database after the sync operation has completed.";
    $command['options']['disable'] = "Disable the specified modules in the target database after the sync operation has completed.";
    $command['options']['permission'] = "Add or remove permissions from a role in the target database after the sync operation has completed. The value of this option must be an array, so it may only be specified in a site alias record or drush configuration file.  See `drush topic docs-example-sync-extension`.";
  }
}

/**
 * Implements drush_hook_post_COMMAND().
 *
 * The post hook is only called if the sql-sync operation completes
 * without an error.  When called, we check to see if the user specified
 * any modules to enable/disable.  If so, we will call pm-enable/pm-disable on
 * each module.
 */
function drush_sync_enable_post_sql_sync($source = NULL, $destination = NULL) {
  $updb = drush_get_option('updb', FALSE);
  if ($updb) {
    drush_log('Run database updates', 'ok');
    drush_invoke_process($destination, 'updatedb', array(), array('yes' => TRUE));
  }
  $modules_to_enable = drush_get_option_list('enable');
  if (!empty($modules_to_enable)) {
    drush_log(dt("Enable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_enable))), 'ok');
    drush_invoke_process($destination, 'pm-enable', $modules_to_enable, array('yes' => TRUE));
  }
  $modules_to_disable = drush_get_option_list('disable');
  if (!empty($modules_to_disable)) {
    drush_log(dt("Disable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_disable))), 'ok');
    drush_invoke_process($destination, 'pm-disable', $modules_to_disable, array('yes' => TRUE));
  }
  $permissions_table = drush_get_option('permission');
  if (!empty($permissions_table)) {
    foreach ($permissions_table as $role_name => $actions) {
      if (array_key_exists('add', $actions)) {
        $permissions_to_add = is_array($actions['add']) ? $actions['add'] : explode(', ', $actions['add']);
        foreach ($permissions_to_add as $permission) {
          $values = drush_invoke_process($destination, 'role-add-perm', array($role_name, $permission), array(), array('integrate' => TRUE));
        }
      }
      if (array_key_exists('remove', $actions)) {
        $permissions_to_remove = is_array($actions['remove']) ? $actions['remove'] : explode(', ', $actions['remove']);
        foreach ($permissions_to_remove as $permission) {
          $values = drush_invoke_process($destination, 'role-remove-perm', array($role_name, $permission), array(), array('integrate' => TRUE));
        }
      }
    }
  }
}
<?php

/**
 * @file
 * Example "Sync via HTTP" sql-sync command alter.
 *
 * Sync_via_http allows you to sql-sync your database using HTTP
 * (e.g. wget or curl) instead of rsync.  This is helpful for
 * exporting your database to colaborators without shell access
 * to the production or staging server.
 *
 * For example:
 *
 * @code
 * $aliases['staging'] = array (
 *   'root' => '/srv/www/drupal',
 *   'uri' => 'staging.site.com',
 *   'source-command-specific' => array(
 *     'sql-sync'  => array(
 *       'http-sync'  => 'https://staging.site.com/protected-directory/site-database-dump.sql',
 *       'http-sync-user' => 'wwwadmin',
 *       'http-sync-password' => 'secretsecret',
 *     ),
 *   ),
 * );
 * @endcode
 *
 * To use this feature, copy the 'source-command-specific'
 * item from the example alias above, place it in your staging
 * site aliases, and custom the access credentials as
 * necessary.  You must also copy the sync_via_http.drush.inc
 * file to a location where Drush will find it, such as
 * $HOME/.drush.  See `drush topic docs-commands` for more
 * information.
 *
 * IMPORTANT NOTE:  This example does not cause the sql dump
 * to be performed; it is presumed that the dump file already
 * exists at the provided URL.  For a full solution, a web page
 * that initiated an sql-dump (or perhaps a local sql-sync followed
 * by an sql-sanitize and then an sql-dump) would be necessary.
 */

/**
 * Implements hook_drush_help_alter().
 *
 * When a hook extends a command with additional options, it must
 * implement help alter and declare the option(s).  Doing so will add
 * the option to the help text for the modified command, and will also
 * allow the new option to be specified on the command line.  Without
 * this, Drush will fail with an error when a user attempts to use
 * the option.
 */
function sync_via_http_drush_help_alter(&$command) {
  if ($command['command'] == 'sql-sync') {
    $command['options']['http-sync']  = "Copy the database via http instead of rsync.  Value is the url that the existing database dump can be found at.";
    $command['sub-options']['http-sync']['http-sync-user']  = "Username for the protected directory containing the sql dump.";
    $command['sub-options']['http-sync']['http-sync-password']  = "Password for the same directory.";
  }
}

/**
 * Implements drush_hook_pre_COMMAND().
 *
 * During the pre hook, determine if the http-sync option has been
 * specified.  If it has been, then disable the normal ssh + rsync
 * dump-and-transfer that sql-sync usually does, and transfer the
 * database dump via an http download.
 */
function drush_sync_via_http_pre_sql_sync($source = NULL, $destination = NULL) {
  $sql_dump_download_url = drush_get_option('http-sync');
  if (!empty($sql_dump_download_url)) {
    $user = drush_get_option('http-sync-user', FALSE);
    $password = drush_get_option('http-sync-password', FALSE);
    $source_dump_file = _drush_sync_via_http_download_file($sql_dump_download_url, $user, $password);
    if ($source_dump_file === FALSE) {
      return drush_set_error('DRUSH_CANNOT_DOWNLOAD', dt("The URL !url could not be downloaded.", array('!url' => $sql_dump_download_url)));
    }
    drush_set_option('target-dump', $source_dump_file);
    drush_set_option('no-dump', TRUE);
    drush_set_option('no-sync', TRUE);
  }
}

/**
 * Downloads a files.
 *
 * Optionaly uses user authentication, using either wget or curl, as available.
 */
function _drush_sync_via_http_download_file($url, $user = FALSE, $password = FALSE, $destination = FALSE, $overwrite = TRUE) {
  static $use_wget;
  if ($use_wget === NULL) {
    $use_wget = drush_shell_exec('which wget');
  }

  $destination_tmp = drush_tempnam('download_file');
  if ($use_wget) {
    if ($user && $password) {
      drush_shell_exec("wget -q --timeout=30 --user=%s --password=%s -O %s %s", $user, $password, $destination_tmp, $url);
    }
    else {
      drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url);
    }
  }
  else {
    if ($user && $password) {
      drush_shell_exec("curl -s -L --connect-timeout 30 --user %s:%s -o %s %s", $user, $password, $destination_tmp, $url);
    }
    else {
      drush_shell_exec("curl -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url);
    }
  }
  if (!drush_get_context('DRUSH_SIMULATE')) {
    if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) {
      @file_put_contents($destination_tmp, $file);
    }
    if (!drush_file_not_empty($destination_tmp)) {
      // Download failed.
      return FALSE;
    }
  }
  if ($destination) {
    drush_move_dir($destination_tmp, $destination, $overwrite);
    return $destination;
  }
  return $destination_tmp;
}
<?php

/**
 * @file
 * Example XKCD Drush command.
 *
 * To run this *fun* command, execute `drush --include=./examples xkcd` from
 * within your drush directory.
 *
 * See `drush topic docs-commands` for more information about command authoring.
 *
 * You can copy this file to any of the following
 *   1. A .drush folder in your HOME folder.
 *   2. Anywhere in a folder tree below an active module on your site.
 *   3. /usr/share/drush/commands (configurable)
 *   4. In an arbitrary folder specified with the --include option.
 *   5. Drupal's /drush or /sites/all/drush folders, or in the /drush
 *       folder in the directory above the Drupal root.
 */

/**
 * Implements hook_drush_command().
 *
 * In this hook, you specify which commands your drush module makes available,
 * what it does and description.
 *
 * Notice how this structure closely resembles how you define menu hooks.
 *
 * See `drush topic docs-commands` for a list of recognized keys.
 */
function xkcd_drush_command() {
  $items = array();

  // The 'xkcd' command.
  $items['xkcd-fetch'] = array(
    'description' => "Retrieve and display xkcd cartoons.",
    'arguments' => array(
      'search' => 'Optional argument to retrive the cartoons matching an index number, keyword search or "random". If omitted the latest cartoon will be retrieved.',
    ),
    'options' => array(
      'image-viewer' => 'Command to use to view images (e.g. xv, firefox). Defaults to "display" (from ImageMagick).',
      'google-custom-search-api-key' => 'Google Custom Search API Key, available from https://code.google.com/apis/console/. Default key limited to 100 queries/day globally.',
    ),
    'examples' => array(
      'drush xkcd' => 'Retrieve and display the latest cartoon.',
      'drush xkcd sandwich' => 'Retrieve and display cartoons about sandwiches.',
      'drush xkcd 123 --image-viewer=eog' => 'Retrieve and display cartoon #123 in eog.',
      'drush xkcd random --image-viewer=firefox' => 'Retrieve and display a random cartoon in Firefox.',
    ),
    'aliases' => array('xkcd'),
    // No bootstrap at all.
    'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );

  return $items;
}

/**
 * Implements hook_drush_help().
 *
 * This function is called whenever a drush user calls
 * 'drush help <name-of-your-command>'. This hook is optional. If a command
 * does not implement this hook, the command's description is used instead.
 *
 * This hook is also used to look up help metadata, such as help
 * category title and summary.  See the comments below for a description.
 */
function xkcd_drush_help($section) {
  switch ($section) {
    case 'drush:xkcd-fetch':
      return dt("A command line tool (1) for a web site tool (2), that emulates
(badly) a web based tool (3) that emulates (badly) a command line tool (4) to
access a web site (5) with awesome geek humor.\n
(1) Drush
(2) Drupal
(3) http://uni.xkcd.com/
(4) BASH
(5) http://xkcd.com/");
  }
}

/**
 * Implements drush_hook_COMMAND().
 *
 * The command callback is where the action takes place.
 *
 * The function name should be same as command name but with dashes turned to
 * underscores and 'drush_commandfile_' prepended, where 'commandfile' is
 * taken from the file 'commandfile.drush.inc', which in this case is
 * 'sandwich'. Note also that a simplification step is also done in instances
 * where the commandfile name is the same as the beginning of the command name,
 * "drush_example_example_foo" is simplified to just "drush_example_foo".
 * To also implement a hook that is called before your command, implement
 * "drush_hook_pre_example_foo".  For a list of all available hooks for a
 * given command, run drush in --debug mode.
 *
 * If for some reason you do not want your hook function to be named
 * after your command, you may define a 'callback' item in your command
 * object that specifies the exact name of the function that should be
 * called.
 *
 * In this function, all of Drupal's API is (usually) available, including
 * any functions you have added in your own modules/themes.
 *
 * @see drush_invoke()
 * @see drush.api.php
 *
 * @param string $search
 *   An optional string with search keyworks, cartoon ID or "random".
 */
function drush_xkcd_fetch($search = '') {
  if (empty($search)) {
    drush_xkcd_display('http://xkcd.com');
  }
  elseif (is_numeric($search)) {
    drush_xkcd_display('http://xkcd.com/' . $search);
  }
  elseif ($search == 'random') {
    $xkcd_response = @json_decode(file_get_contents('http://xkcd.com/info.0.json'));
    if (!empty($xkcd_response->num)) {
      drush_xkcd_display('http://xkcd.com/' . rand(1, $xkcd_response->num));
    }
  }
  else {
    // This uses an API key with a limited number of searches per.
    $search_response = @json_decode(file_get_contents('https://www.googleapis.com/customsearch/v1?key=' . drush_get_option('google-custom-search-api-key', 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek') . '&cx=012652707207066138651:zudjtuwe28q&q=' . $search));
    if (!empty($search_response->items)) {
      foreach ($search_response->items as $item) {
        drush_xkcd_display($item->link);
      }
    }
    else {
      drush_set_error('DRUSH_XKCD_SEARCH_FAIL', dt('The search failed or produced no results.'));
    }
  }
}

/**
 * Display a given XKCD cartoon.
 *
 * Retrieve and display a table of metadata for an XKCD cartoon, then retrieve
 * and display the cartoon using a specified image viewer.
 *
 * @param string $url
 *   A string with the URL of the cartoon to display.
 */
function drush_xkcd_display($url) {
  $xkcd_response = @json_decode(file_get_contents($url . '/info.0.json'));
  if (!empty($xkcd_response->num)) {
    $data = (array) $xkcd_response;
    $data['date'] = $data['year'] . '/' . $data['month'] . '/' . $data['day'];
    unset($data['safe_title'], $data['news'], $data['link'], $data['year'], $data['month'], $data['day']);
    drush_print_table(drush_key_value_to_array_table($data));
    $img = drush_download_file($data['img']);
    drush_register_file_for_deletion($img);
    drush_shell_exec(drush_get_option('image-viewer', 'display') . ' ' . $img);
  }
  else {
    drush_set_error('DRUSH_XKCD_METADATA_FAIL', dt('Unable to retrieve cartoon metadata.'));
  }
}
<?php

/**
 * @file
 * annotationcommand_adapter.inc
 */

use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Consolidation\AnnotatedCommand\AnnotatedCommandFactory;
use Consolidation\AnnotatedCommand\CommandProcessor;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Input\ArrayInput;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Drush\Command\DrushInputAdapter;
use Drush\Command\DrushOutputAdapter;

use Symfony\Component\Console\Output\ConsoleOutput;

/**
 * Cache the command file discovery object.
 *
 * @return CommandFileDiscovery
 */
function annotationcommand_adapter_get_discovery() {
  static $discovery;
  if (!isset($discovery)) {
    $discovery = new CommandFileDiscovery();
    $discovery
      ->setIncludeFilesAtBase(false)
      ->setSearchLocations(['Commands'])
      ->setSearchPattern('#.*Commands.php$#');
  }
  return $discovery;
}

/**
 * Initialize and cache the command factory. Drush 9 uses dependency injection.
 *
 * @return AnnotatedCommandFactory
 */
function annotationcommand_adapter_get_factory() {
  static $factory;
  if (!isset($factory)) {
    $factory = new AnnotatedCommandFactory();
    $factory->commandProcessor()->hookManager()->add('annotatedcomand_adapter_backend_result', HookManager::EXTRACT_OUTPUT);
    $formatter = new FormatterManager();
    $formatter->addDefaultFormatters();
    $formatter->addDefaultSimplifiers();
    $factory->commandProcessor()->setFormatterManager($formatter);
  }
  return $factory;
}

/**
 * Fetch the command processor from the factory.
 *
 * @return AnnotatedCommandFactory
 */
function annotationcommand_adapter_get_processor() {
  $factory = annotationcommand_adapter_get_factory();
  return $factory->commandProcessor();
}

/**
 * Fetch the formatter manager from the command processor
 *
 * @return FormatterManager
 */
function annotatedcomand_adapter_get_formatter() {
  $commandProcessor = annotationcommand_adapter_get_processor();
  return $commandProcessor->formatterManager();
}

/**
 * Callback function called by HookManager::EXTRACT_OUTPUT to set
 * the backend result.
 */
function annotatedcomand_adapter_backend_result($structured_data) {
  $return = drush_backend_get_result();
  if (empty($return)) {
    drush_backend_set_result($structured_data);
  }
}

/**
 * Return the cached commands built by annotationcommand_adapter_discover.
 * @see drush_get_commands()
 */
function annotationcommand_adapter_commands() {
  $annotation_commandfiles = drush_get_context('DRUSH_ANNOTATED_COMMANDFILES');
  // Remove any entry in the commandfiles list from an ignored module.
  $ignored = implode('|', drush_get_option_list('ignored-modules'));
  $regex = "#/(modules|themes|profiles)(/|/.*/)($ignored)/#";
  foreach ($annotation_commandfiles as $key => $path) {
    if (preg_match($regex, $path)) {
      unset($annotation_commandfiles[$key]);
    }
  }
  $commands = annotationcommand_adapter_get_commands($annotation_commandfiles);
  $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS');
  return array_merge($commands, $module_service_commands);
}

/**
 * Search for annotation commands at the provided search path.
 * @see _drush_find_commandfiles()
 */
function annotationcommand_adapter_discover($searchpath, $phase = false, $phase_max = false) {
  if (empty($searchpath)) {
    return;
  }
  if (($phase >= DRUSH_BOOTSTRAP_DRUPAL_SITE) && (drush_drupal_major_version() >= 8)) {
    return;
  }
  $annotation_commandfiles = [];
  // Assemble a cid specific to the bootstrap phase and searchpaths.
  // Bump $cf_version when making a change to a dev version of Drush
  // that invalidates the commandfile cache.
  $cf_version = 1;
  $cid = drush_get_cid('annotationfiles-' . $phase, array(), array_merge($searchpath, array($cf_version)));
  $command_cache = drush_cache_get($cid);
  if (isset($command_cache->data)) {
    $annotation_commandfiles = $command_cache->data;
  }
  else {
    // Check to see if this is the Drush searchpath for instances where we are
    // NOT going to do a full bootstrap (e.g. when running a help command)
    if (($phase == DRUSH_BOOTSTRAP_DRUPAL_SITE) && ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      $searchpath = annotationcommand_adapter_refine_searchpaths($searchpath);
    }
    $discovery = annotationcommand_adapter_get_discovery();
    $annotation_commandfiles = $discovery->discoverNamespaced($searchpath, '\Drupal');
    drush_cache_set($cid, $annotation_commandfiles);
  }
  drush_set_context(
    'DRUSH_ANNOTATED_COMMANDFILES',
    array_merge(
      drush_get_context('DRUSH_ANNOTATED_COMMANDFILES'),
      $annotation_commandfiles
    )
  );
}

/**
 * This function is set as the $command['callback'] for Symfony Console commands
 * e.g. those provided by Drupal 8 modules.  Note that Drush 9 calls these with
 * Symfony's Application::run() method, but Drush 8 continues to use the
 * legacy Drush command dispatcher for all commands.
 *
 * @return bolean false if command failed (expect drush_set_error was called in this case)
 */
function annotationcommand_adapter_run_console_command() {
  $args = func_get_args();
  $command = drush_get_command();

  $console_command = $command['drush-console-command'];
  // TODO: Build an appropriate input object
  $input = annotationcommand_adapter_build_input($console_command, $args);
  $output = new ConsoleOutput();
  $result = $console_command->run($input, $output);

  return $result;
}

/**
 * TODO: This could probably just be a DrushInputAdapter now.
 */
function annotationcommand_adapter_build_input($console_command, $userArgs) {
  $args = [];
  $defaultOptions = [];
  $definition = $console_command->getDefinition();
  $inputArguments = $definition->getArguments();
  foreach ($inputArguments as $key => $inputOption) {
    $value = array_shift($userArgs);
    if (!isset($value)) {
      $value = $inputOption->getDefault();
    }
    $args[$key] = $value;
  }
  $inputOptions = $definition->getOptions();
  foreach ($inputOptions as $option => $inputOption) {
    $defaultOptions[$option] = $inputOption->getDefault();
  }
  foreach ($defaultOptions as $option => $value) {
    $args["--$option"] = drush_get_option($option, $value);
  }
  // TODO: Need to add global options. Note that ArrayInput is validated.
  $input = new ArrayInput($args, $definition);
  return $input;
}

/**
 * Collect all of the options defined in every relevant context, and
 * merge them together to form the options array.
 *
 * @return array
 */
function annotationcommand_adapter_get_options($command) {
  $default_options = isset($command['consolidation-option-defaults']) ? $command['consolidation-option-defaults'] : [];
  $options = drush_redispatch_get_options() + $default_options;

  $options += drush_get_merged_options();

  return $options;
}

/**
 * This function is set as the $command['callback'] for commands that have
 * been converted to annotated commands.  Note that Drush 9 calls these with
 * Symfony's Application::run() method, but Drush 8 continues to use the
 * legacy Drush command dispatcher for all commands.
 *
 * @return bolean false if command failed (expect drush_set_error was called in this case)
 */
function annotationcommand_adapter_process_command() {
  $userArgs = func_get_args();
  $commandprocessor = annotationcommand_adapter_get_processor();
  $command = drush_get_command();
  annotationcommand_adapter_add_hook_options($command);
  $args = [];
  foreach ($command['consolidation-arg-defaults'] as $key => $default) {
    $value = array_shift($userArgs);
    if (!isset($value)) {
      $value = $default;
    }
    $args[$key] = $value;
  }

  $input = new DrushInputAdapter($args, annotationcommand_adapter_get_options($command), $command['command']);
  $output = new DrushOutputAdapter();
  $annotationData = $command['annotations'];
  $commandData = new CommandData(
    $annotationData,
    $input,
    $output
  );
  $commandData->setIncludeOptionsInArgs($command['add-options-to-arguments']);
  $names = annotationcommand_adapter_command_names($command);

  // n.b.: backend result is set by a post-alter hook.
  $result = $commandprocessor->process(
    $output,
    $names,
    $command['annotated-command-callback'],
    $commandData
  );

  return $result;
}

/**
 * Internal function called by annotationcommand_adapter_commands, which
 * is called by drush_get_commands().
 *
 * @param array $annotation_commandfiles path => class mapping
 *
 * @return object[]
 */
function annotationcommand_adapter_get_commands($annotation_commandfiles) {
  $commands = [];
  // This will give us a list containing something akin to:
  //   'modules/default_content/src/CliTools/DefaultContentCommands.php' =>
  //   '\\Drupal\\default_content\\CliTools\\DefaultContentCommands',
  foreach ($annotation_commandfiles as $commandfile_path => $commandfile_class) {
    if (file_exists($commandfile_path)) {
      $commandhandler = annotationcommand_adapter_create_commandfile_instance($commandfile_path, $commandfile_class);
      $commands_for_this_commandhandler = annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path);
      $commands = array_merge($commands, $commands_for_this_commandhandler);
    }
  }
  return $commands;
}

/**
 * Create and cache a commandfile instance.
 *
 * @param string $commandfile_path Path to the commandfile implementation
 * @param string $commandfile_class Namespace and class of the commandfile object
 *
 * @return object
 */
function annotationcommand_adapter_create_commandfile_instance($commandfile_path, $commandfile_class) {
  $cache =& drush_get_context('DRUSH_ANNOTATION_COMMANDFILE_INSTANCES');
  if (!isset($cache[$commandfile_path])) {
    include_once $commandfile_path;
    $commandhandler = new $commandfile_class;
    $cache[$commandfile_path] = $commandhandler;
  }
  return $cache[$commandfile_path];
}

/**
 * TODO: document
 */
function annotationcommand_adapter_cache_module_console_commands($console_command, $commandfile_path = null) {
  if (!isset($commandfile_path)) {
    $class = new \ReflectionClass($console_command);
    $commandfile_path = $class->getFileName();
  }
  $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS');
  $commands = annotationcommand_adapter_get_command_for_console_command($console_command, $commandfile_path);
  drush_set_context('DRUSH_MODULE_SERVICE_COMMANDS', array_merge($commands, $module_service_commands));
}

/**
 * TODO: document
 */
function annotationcommand_adapter_cache_module_service_commands($commandhandler, $commandfile_path = null) {
  if (!isset($commandfile_path)) {
    $class = new \ReflectionClass($commandhandler);
    $commandfile_path = $class->getFileName();
  }
  $module_service_commands = drush_get_context('DRUSH_MODULE_SERVICE_COMMANDS');
  $commands = annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path, false);
  drush_set_context('DRUSH_MODULE_SERVICE_COMMANDS', array_merge($commands, $module_service_commands));
}

/**
 * Convert a Symfony Console command into a Drush $command record
 *
 * @param Symfony\Component\Console\Command\Command $console_command The Symfony Console command to convert
 * @param string $commandfile_path Path to console command file
 *
 * @return array Drush $command record
 */
function annotationcommand_adapter_get_command_for_console_command($console_command, $commandfile_path) {
  $commands = [];
  $commandfile = basename($commandfile_path, '.php');
  $factory = annotationcommand_adapter_get_factory();
  $inputDefinition = $console_command->getDefinition();
  $inputArguments = $inputDefinition->getArguments();
  $inputOptions = $inputDefinition->getOptions();
  $aliases = $console_command->getAliases();
  $command_name = strtolower($console_command->getName());
  $standard_alias = str_replace(':', '-', $command_name);
  if ($command_name != $standard_alias) {
    $aliases[] = $standard_alias;
  }
  $command = [
    'name' => $command_name,
    'callback' => 'annotationcommand_adapter_run_console_command',
    'drush-console-command' => $console_command,
    'commandfile' => $commandfile,
    'category' => $commandfile,
    'options' => [],
    'arguments' => [],
    'description' => $console_command->getDescription(),
    'examples' => $console_command->getUsages(),
    'aliases' => $aliases,
  ];
  foreach ($inputArguments as $arg => $inputArg) {
    $command['arguments'][$arg] = $inputArg->getDescription();
  }
  $command['required-arguments'] = $inputDefinition->getArgumentRequiredCount();
  foreach ($inputOptions as $option => $inputOption) {
    $description = $inputOption->getDescription();
    $default = $inputOption->getDefault();
    $command['options'][$option] = ['description' => $description];
    if (!empty($default)) {
      $command['options'][$option]['example-value'] = $default;
    }
  }
  $command += drush_command_defaults($command_name, $commandfile, $commandfile_path);
  $commands[$command_name] = $command;
  return $commands;
}

/**
 * Convert an annotated command command handler object into a Drush $command record.
 *
 * @param object $commandhandler Command handler object
 * @param string $commandfile_path
 * @param boolean $includeAllPublicMethods TODO remove this, make it 'false' always
 *
 * @return array Drush $command record
 */
function annotationcommand_adapter_get_commands_for_commandhandler($commandhandler, $commandfile_path, $includeAllPublicMethods = true) {
  $cache =& drush_get_context('DRUSH_ANNOTATION_COMMANDS_FOR_COMMANDFILE');
  if (isset($cache[$commandfile_path])) {
    return $cache[$commandfile_path];
  }
  $factory = annotationcommand_adapter_get_factory();
  $commands = [];
  $commandfile = basename($commandfile_path, '.php');

  $commandinfo_list = $factory->getCommandInfoListFromClass($commandhandler);

  foreach ($commandinfo_list as $commandinfo) {
    // Hooks are automatically registered when the commandhandler is
    // created via registerCommandClass(), so we don't need to do it again here.
    // $factory->registerCommandHook($commandinfo, $commandhandler);
    $aliases = $commandinfo->getAliases();
    $command_name = strtolower($commandinfo->getName());
    $standard_alias = str_replace(':', '-', $command_name);
    if ($command_name != $standard_alias) {
      $aliases[] = $standard_alias;
    }
    $handle_remote_commands = $commandinfo->getAnnotation('handle-remote-commands') == 'true';
    // TODO: if there is no 'bootstrap' annotation, maybe we should default to NONE instead of FULL?
    if ($bootstrap = $commandinfo->getAnnotation('bootstrap')) {
      $bootstrap = constant($bootstrap);
    }
    $command = [
      'name' => $command_name,
      //'callback' => [$commandhandler, $commandinfo->getMethodName()],
      'callback' => 'annotationcommand_adapter_process_command',
      'annotated-command-callback' => [$commandhandler, $commandinfo->getMethodName()],
      'commandfile' => $commandfile,
      'category' => $commandfile,
      'options' => [],
      'arguments' => [],
      'description' => $commandinfo->getDescription(),
      'examples' => $commandinfo->getExampleUsages(),
      'bootstrap' => $bootstrap,
      'handle-remote-commands' => $handle_remote_commands,
      'aliases' => $aliases,
      'add-options-to-arguments' => TRUE,
      'consolidation-output-formatters' => TRUE,
      'consolidation-option-defaults' => $commandinfo->options()->getValues(),
      'consolidation-arg-defaults' => $commandinfo->arguments()->getValues(),
    ];
    $required_arguments = 0;
    foreach ($commandinfo->arguments()->getValues() as $arg => $default) {
      $command['arguments'][$arg] = $commandinfo->arguments()->getDescription($arg);
      if (!isset($default)) {
        ++$required_arguments;
      }
    }
    $command['required-arguments'] = $required_arguments;
    foreach ($commandinfo->options()->getValues() as $option => $default) {
      $description = $commandinfo->options()->getDescription($option);
      $command['options'][$option] = ['description' => $description];
      if (!empty($default)) {
        $command['options'][$option]['example-value'] = $default;
      }
      $fn = 'annotationcommand_adapter_alter_option_description_' . $option;
      if (function_exists($fn)) {
        $command['options'][$option] = $fn($command['options'][$option], $commandinfo, $default);
      }
    }
    $command['annotations'] = $commandinfo->getAnnotations();
    // If the command has a '@return' annotation, then
    // remember information we will need to use the output formatter.
    $returnType = $commandinfo->getReturnType();
    if (isset($returnType)) {
      $command['return-type'] = $returnType;
    }
    $command += drush_command_defaults($command_name, $commandfile, $commandfile_path);
    $commands[$command_name] = $command;
  }
  $cache[$commandfile_path] = $commands;
  return $commands;
}

/**
 * Modify a $command record, adding option definitions defined by any
 * command hook.
 *
 * @param array $command Drush command record to modify
 */
function annotationcommand_adapter_add_hook_options(&$command)
{
  // Get options added by hooks.  We postpone doing this until the
  // last minute rather than doing it when processing commandfiles
  // so that we do not need to worry about what order we process the
  // commandfiles in -- we can load extensions late, and still have
  // the extension hook a core command, or have an early-loaded global
  // extension hook a late-loaded extension (e.g. attached to a module).
  $names = annotationcommand_adapter_command_names($command);
  $names[] = '*'; // we are missing annotations here; maybe we just don't support that? (TODO later, maybe)
  $factory = annotationcommand_adapter_get_factory();
  $extraOptions = $factory->hookManager()->getHookOptions($names);
  foreach ($extraOptions as $commandinfo) {
    if (!isset($command['consolidation-option-defaults'])) {
      $command['consolidation-option-defaults'] = array();
    }
    $command['consolidation-option-defaults'] += $commandinfo->options()->getValues();
    foreach ($commandinfo->options()->getValues() as $option => $default) {
      $description = $commandinfo->options()->getDescription($option);
      $command['options'][$option] = ['description' => $description];
      if (!empty($default)) {
        $command['options'][$option]['example-value'] = $default;
      }
      $fn = 'annotationcommand_adapter_alter_option_description_' . $option;
      if (function_exists($fn)) {
        $command['options'][$option] = $fn($command['options'][$option], $commandinfo, $default);
      }
    }
  }
}

/**
 * Build all of the name variants for a Drush $command record
 *
 * @param array $command Drush command record
 *
 * @return string[]
 */
function annotationcommand_adapter_command_names($command)
{
  $names = array_merge(
    [$command['command']],
    $command['aliases']
  );
  if (!empty($command['annotated-command-callback'])) {
    $commandHandler = $command['annotated-command-callback'][0];
    $reflectionClass = new \ReflectionClass($commandHandler);
    $commandFileClass = $reflectionClass->getName();
    $names[] = $commandFileClass;
  }
  return $names;
}

/**
 * Convert from an old-style Drush initialize hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 */
function annotationcommand_adapter_call_initialize($names, CommandData $commandData)
{
  $factory = annotationcommand_adapter_get_factory();
  $hookManager = $factory->hookManager();

  $hooks = $hookManager->getHooks(
    $names,
    [
      HookManager::PRE_INITIALIZE,
      HookManager::INITIALIZE,
      HookManager::POST_INITIALIZE,
    ],
    $commandData->annotationData()
  );

  foreach ((array)$hooks as $hook) {
    $hook($commandData->input(), $commandData->annotationData());
  }
}

/**
 * Convert from an old-style Drush pre-validate hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 *
 * @return boolean|object
 */
function annotationcommand_adapter_call_hook_pre_validate($names, CommandData $commandData)
{
  return annotationcommand_adapter_call_validate_interface(
    $names,
    [
      HookManager::PRE_ARGUMENT_VALIDATOR,
    ],
    $commandData
  );
}

/**
 * Convert from an old-style Drush validate hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 *
 * @return boolean|object
 */
function annotationcommand_adapter_call_hook_validate($names, CommandData $commandData)
{
  return annotationcommand_adapter_call_validate_interface(
    $names,
    [
      HookManager::ARGUMENT_VALIDATOR,
    ],
    $commandData
  );
}

/**
 * Convert from an old-style Drush pre-command hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 *
 * @return boolean|object
 */
function annotationcommand_adapter_call_hook_pre_command($names, CommandData $commandData)
{
  return annotationcommand_adapter_call_validate_interface(
    $names,
    [
      HookManager::PRE_COMMAND_HOOK,
    ],
    $commandData
  );
}

/**
 * Convert from an old-style Drush 'command' hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 *
 * @return boolean|object
 */
function annotationcommand_adapter_call_hook_command($names, CommandData $commandData)
{
  return annotationcommand_adapter_call_validate_interface(
    $names,
    [
      HookManager::COMMAND_HOOK,
    ],
    $commandData
  );
}

/**
 * Convert from an old-style Drush post-command hook into annotated-command hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 * @param mixed $return The return value of the command being executed
 *
 * @return mixed The altered command return value
 */
function annotationcommand_adapter_call_hook_post_command($names, CommandData $commandData, $return)
{
  return annotationcommand_adapter_call_process_interface(
    $names,
    [
      HookManager::POST_COMMAND_HOOK,
    ],
    $commandData,
    $return
  );
}

/**
 * After the primary Drush command hook is called, call all of the annotated-command
 * process and alter hooks.
 * @see _drush_invoke_hooks().
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 * @param mixed $return The return value of the command being executed
 *
 * @return mixed The altered command return value
 */
function annotationcommand_adapter_call_hook_process_and_alter($names, $commandData, $return)
{
  return annotationcommand_adapter_call_process_interface(
    $names,
    [
      HookManager::PRE_PROCESS_RESULT,
      HookManager::PROCESS_RESULT,
      HookManager::POST_PROCESS_RESULT,
      HookManager::PRE_ALTER_RESULT,
      HookManager::ALTER_RESULT,
      HookManager::POST_ALTER_RESULT,
    ],
    $commandData,
    $return
  );
}

/**
 * Given a list of hooks that conform to the interface ProcessResultInterface,
 * call them and return the result.
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param string[] $hooks All of the HookManager hooks that should be called
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 * @param mixed $return The return value of the command being executed
 *
 * @return mixed The altered command return value
 */
function annotationcommand_adapter_call_process_interface($names, $hooks, CommandData $commandData, $return)
{
  $factory = annotationcommand_adapter_get_factory();
  $hookManager = $factory->hookManager();

  $hooks = $hookManager->getHooks($names, $hooks, $commandData->annotationData());

  foreach ((array)$hooks as $hook) {
    $result = $hook($return, $commandData);
    if (isset($result)) {
      $return = $result;
    }
  }
  return $return;
}

/**
 * Given a list of hooks that conform to the interface ValidatorInterface,
 * call them and return the result.
 *
 * @param string[] $names All of the applicable names for the command being hooked
 * @param string[] $hooks All of the HookManager hooks that should be called
 * @param CommandData $commandData All of the parameter data associated with the
 *   current command invokation, including the InputInterface, OutputInterface
 *   and AnnotationData
 *
 * @return boolean|object
 */
function annotationcommand_adapter_call_validate_interface($names, $hooks, CommandData $commandData)
{
  $factory = annotationcommand_adapter_get_factory();
  $hookManager = $factory->hookManager();

  $annotationData = $commandData->annotationData();
  $hooks = $hookManager->getHooks($names, $hooks, $annotationData);

  foreach ((array)$hooks as $hook) {
    $validated = $hook($commandData);
    // TODO: if $validated is a CommandError, maybe the best thing to do is 'return drush_set_error()'?
    if (is_object($validated) || ($validated === false)) {
      return $validated;
    }
  }
  return true;
}

/**
 * TODO: Document
 */
function annotationcommand_adapter_alter_option_description_format($option_help, $commandinfo, $default) {
  $formatterManager = annotatedcomand_adapter_get_formatter();
  $return_type = $commandinfo->getReturnType();
  if (!empty($return_type)) {
    $available_formats = $formatterManager->validFormats($return_type);
    $option_help['description'] = dt('Select output format. Available: !formats.', array('!formats' => implode(', ', $available_formats)));
    if (!empty($default)) {
      $option_help['description'] .= dt(' Default is !default.', array('!default' => $default));
    }
  }
  return $option_help;
}

/**
 * TODO: Document
 */
function annotationcommand_adapter_alter_option_description_fields($option_help, $commandinfo, $default) {
  $formatOptions = new FormatterOptions($commandinfo->getAnnotations()->getArrayCopy());
  $field_labels = $formatOptions->get(FormatterOptions::FIELD_LABELS);
  $default_fields = $formatOptions->get(FormatterOptions::DEFAULT_FIELDS, [], array_keys($field_labels));
  $available_fields = array_keys($field_labels);
  $option_help['example-value'] = implode(', ', $default_fields);
  $option_help['description'] = dt('Fields to output. All available fields are: !available.', array('!available' => implode(', ', $available_fields)));
  return $option_help;
}

/**
 * In some circumstances, Drush just does a deep search for any *.drush.inc
 * file, so that it can find all commands, in enabled and disabled modules alike,
 * for the purpose of displaying the help text for that command.
 */
function annotationcommand_adapter_refine_searchpaths($searchpath) {
  $result = [];
  foreach ($searchpath as $path) {
    $max_depth = TRUE;
    $pattern = '/.*\.info$/';
    if (drush_drupal_major_version() > 7) {
      $pattern = '/.*\.info.yml$/';
    }
    $locations = drush_scan_directory($path, $pattern, ['.', '..'], false, $max_depth);

    // Search for any directory that might be a module or theme (contains
    // a *.info or a *.info.yml file)
    foreach ($locations as $key => $info) {
      $result[dirname($key)] = true;
    }
  }
  return array_keys($result);
}
<?php
/**
 * This file was recommended at http://php.net/manual/en/function.array-column.php#refsect1-function.array-column-seealso
 *
 * This file is part of the array_column library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Ben Ramsey (http://benramsey.com)
 * @license http://opensource.org/licenses/MIT MIT
 */

if (!function_exists('array_column')) {
  /**
   * Returns the values from a single column of the input array, identified by
   * the $columnKey.
   *
   * Optionally, you may provide an $indexKey to index the values in the returned
   * array by the values from the $indexKey column in the input array.
   *
   * @param array $input A multi-dimensional array (record set) from which to pull
   *                     a column of values.
   * @param mixed $columnKey The column of values to return. This value may be the
   *                         integer key of the column you wish to retrieve, or it
   *                         may be the string key name for an associative array.
   * @param mixed $indexKey (Optional.) The column to use as the index/keys for
   *                        the returned array. This value may be the integer key
   *                        of the column, or it may be the string key name.
   * @return array
   */
  function array_column($input = null, $columnKey = null, $indexKey = null)
  {
    // Using func_get_args() in order to check for proper number of
    // parameters and trigger errors exactly as the built-in array_column()
    // does in PHP 5.5.
    $argc = func_num_args();
    $params = func_get_args();

    if ($argc < 2) {
      trigger_error("array_column() expects at least 2 parameters, {$argc} given", E_USER_WARNING);
      return null;
    }

    if (!is_array($params[0])) {
      trigger_error(
        'array_column() expects parameter 1 to be array, ' . gettype($params[0]) . ' given',
        E_USER_WARNING
      );
      return null;
    }

    if (!is_int($params[1])
      && !is_float($params[1])
      && !is_string($params[1])
      && $params[1] !== null
      && !(is_object($params[1]) && method_exists($params[1], '__toString'))
    ) {
      trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);
      return false;
    }

    if (isset($params[2])
      && !is_int($params[2])
      && !is_float($params[2])
      && !is_string($params[2])
      && !(is_object($params[2]) && method_exists($params[2], '__toString'))
    ) {
      trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);
      return false;
    }

    $paramsInput = $params[0];
    $paramsColumnKey = ($params[1] !== null) ? (string) $params[1] : null;

    $paramsIndexKey = null;
    if (isset($params[2])) {
      if (is_float($params[2]) || is_int($params[2])) {
        $paramsIndexKey = (int) $params[2];
      } else {
        $paramsIndexKey = (string) $params[2];
      }
    }

    $resultArray = array();

    foreach ($paramsInput as $row) {
      $key = $value = null;
      $keySet = $valueSet = false;

      if ($paramsIndexKey !== null && array_key_exists($paramsIndexKey, $row)) {
        $keySet = true;
        $key = (string) $row[$paramsIndexKey];
      }

      if ($paramsColumnKey === null) {
        $valueSet = true;
        $value = $row;
      } elseif (is_array($row) && array_key_exists($paramsColumnKey, $row)) {
        $valueSet = true;
        $value = $row[$paramsColumnKey];
      }

      if ($valueSet) {
        if ($keySet) {
          $resultArray[$key] = $value;
        } else {
          $resultArray[] = $value;
        }
      }

    }

    return $resultArray;
  }

}
<?php

/**
 * @file
 * Drush backend API
 *
 * When a drush command is called with the --backend option,
 * it will buffer all output, and instead return a JSON encoded
 * string containing all relevant information on the command that
 * was just executed.
 *
 * Through this mechanism, it is possible for Drush commands to
 * invoke each other.
 *
 * There are many cases where a command might wish to call another
 * command in its own process, to allow the calling command to
 * intercept and act on any errors that may occur in the script that
 * was called.
 *
 * A simple example is if there exists an 'update' command for running
 * update.php on a specific site. The original command might download
 * a newer version of a module for installation on a site, and then
 * run the update script in a separate process, so that in the case
 * of an error running a hook_update_n function, the module can revert
 * to a previously made database backup, and the previously installed code.
 *
 * By calling the script in a separate process, the calling script is insulated
 * from any error that occurs in the called script, to the level that if a
 * php code error occurs (ie: misformed file, missing parenthesis, whatever),
 * it is still able to reliably handle any problems that occur.
 *
 * This is nearly a RESTful API. @see http://en.wikipedia.org/wiki/REST
 *
 * Instead of :
 *   http://[server]/[apipath]/[command]?[arg1]=[value1],[arg2]=[value2]
 *
 * It will call :
 *  [apipath] [command] --[arg1]=[value1] --[arg2]=[value2] --backend
 *
 * [apipath] in this case will be the path to the drush.php file.
 * [command] is the command you would call, for instance 'status'.
 *
 * GET parameters will be passed as options to the script.
 * POST parameters will be passed to the script as a JSON encoded associative array over STDIN.
 *
 * Because of this standard interface, Drush commands can also be executed on
 * external servers through SSH pipes, simply by prepending, 'ssh username@server.com'
 * in front of the command.
 *
 * If the key-based ssh authentication has been set up between the servers,
 * this will just work.  By default, drush is configured to disallow password
 * authentication; if you would like to enter a password for every connection,
 * then in your drushrc.php file, set $options['ssh-options'] so that it does NOT
 * include '-o PasswordAuthentication=no'.  See examples/example.drushrc.php.
 *
 * The results from backend API calls can be fetched via a call to
 * drush_backend_get_result().
 */

use Drush\Log\LogLevel;

/**
 * Identify the JSON encoded output from a command.
 *
 * Note that Drush now outputs a null ("\0") before the DRUSH_BACKEND_OUTPUT_DELIMITER,
 * but this null occurs where this constant is output rather than being
 * included in the define.  This is done to maintain compatibility with
 * older versions of Drush, so that Drush-7.x can correctly parse backend messages
 * from calls made to Drush-5.x and earlier.  The null is removed via trim().
 */
define('DRUSH_BACKEND_OUTPUT_START', 'DRUSH_BACKEND_OUTPUT_START>>>');
define('DRUSH_BACKEND_OUTPUT_DELIMITER', DRUSH_BACKEND_OUTPUT_START . '%s<<<DRUSH_BACKEND_OUTPUT_END');

/**
 * Identify JSON encoded "packets" embedded inside of backend
 * output; used to send out-of-band information durring a backend
 * invoke call (currently only used for log and error messages).
 */
define('DRUSH_BACKEND_PACKET_START', "DRUSH_BACKEND:");
define('DRUSH_BACKEND_PACKET_PATTERN', "\0" . DRUSH_BACKEND_PACKET_START . "%s\n\0");

/**
 * The backend result is the original PHP data structure (usually an array)
 * used to generate the output for the current command.
 */
function drush_backend_set_result($value) {
  if (drush_get_context('DRUSH_BACKEND')) {
    drush_set_context('BACKEND_RESULT', $value);
  }
}

/**
 * Retrieves the results from the last call to backend_invoke.
 *
 * @returns array
 *   An associative array containing information from the last
 *   backend invoke.  The keys in the array include:
 *
 *     - output: This item contains the textual output of
 *       the command that was executed.
 *     - object: Contains the PHP object representation of the
 *       result of the command.
 *     - self: The self object contains the alias record that was
 *       used to select the bootstrapped site when the command was
 *       executed.
 *     - error_status: This item returns the error status for the
 *       command.  Zero means "no error".
 *     - log: The log item contains an array of log messages from
 *       the command execution ordered chronologically.  Each log
 *       entery is an associative array.  A log entry contains
 *       following items:
 *         o  type: The type of log entry, such as 'notice' or 'warning'
 *         o  message: The log message
 *         o  timestamp: The time that the message was logged
 *         o  memory: Available memory at the time that the message was logged
 *         o  error: The error code associated with the log message
 *            (only for log entries whose type is 'error')
 *     - error_log: The error_log item contains another representation
 *       of entries from the log.  Only log entries whose 'error' item
 *       is set will appear in the error log.  The error log is an
 *       associative array whose key is the error code, and whose value
 *       is an array of messages--one message for every log entry with
 *       the same error code.
 *     - context: The context item contains a representation of all option
 *       values that affected the operation of the command, including both
 *       the command line options, options set in a drushrc.php configuration
 *       files, and options set from the alias record used with the command.
 */
function drush_backend_get_result() {
  return drush_get_context('BACKEND_RESULT');
}

/**
 * Print the json-encoded output of this command, including the
 * encoded log records, context information, etc.
 */
function drush_backend_output() {
  $data = array();

  if (drush_get_context('DRUSH_PIPE')) {
    $pipe = drush_get_context('DRUSH_PIPE_BUFFER');
    $data['output'] = $pipe; // print_r($pipe, TRUE);
  }
  else {
    // Strip out backend commands.
    $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0"));
    $packet_regex = str_replace("\n", "", $packet_regex);
    $data['output'] = preg_replace("/$packet_regex/s", '', drush_backend_output_collect(NULL));
  }

  if (drush_get_context('DRUSH_QUIET', FALSE)) {
    ob_end_clean();
  }

  $result_object = drush_backend_get_result();
  if (isset($result_object)) {
    $data['object'] = $result_object;
  }

  $error = drush_get_error();
  $data['error_status'] = ($error) ? $error : DRUSH_SUCCESS;

  $data['log'] = drush_get_log(); // Append logging information
  // The error log is a more specific version of the log, and may be used by calling
  // scripts to check for specific errors that have occurred.
  $data['error_log'] = drush_get_error_log();
  // If there is a @self record, then include it in the result
  $self_record = drush_sitealias_get_record('@self');
  if (!empty($self_record)) {
    $site_context = drush_get_context('site', array());
    unset($site_context['config-file']);
    unset($site_context['context-path']);
    unset($self_record['loaded-config']);
    unset($self_record['#name']);
    $data['self'] = array_merge($site_context, $self_record);
  }

  // Return the options that were set at the end of the process.
  $data['context']  = drush_get_merged_options();
  printf("\0" . DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data));
}

/**
 * Callback to collect backend command output.
 */
function drush_backend_output_collect($string) {
  static $output = '';
  if (!isset($string)) {
    return $output;
  }

  $output .= $string;
  return $string;
}

/**
 * Output buffer functions that discards all output but backend packets.
 */
function drush_backend_output_discard($string) {
  $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0"));
  $packet_regex = str_replace("\n", "", $packet_regex);
  if (preg_match_all("/$packet_regex/s", $string, $matches)) {
    return implode('', $matches[0]);
  }
}

/**
 * Output a backend packet if we're running as backend.
 *
 * @param packet
 *   The packet to send.
 * @param data
 *   Data for the command.
 *
 * @return
 *  A boolean indicating whether the command was output.
 */
function drush_backend_packet($packet, $data) {
  if (drush_get_context('DRUSH_BACKEND')) {
    $data['packet'] = $packet;
    $data = json_encode($data);
    // We use 'fwrite' instead of 'drush_print' here because
    // this backend packet is out-of-band data.
    fwrite(STDERR, sprintf(DRUSH_BACKEND_PACKET_PATTERN, $data));
    return TRUE;
  }

  return FALSE;
}

/**
 * Parse output returned from a Drush command.
 *
 * @param string
 *    The output of a drush command
 * @param integrate
 *    Integrate the errors and log messages from the command into the current process.
 * @param outputted
 *    Whether output has already been handled.
 *
 * @return
 *   An associative array containing the data from the external command, or the string parameter if it
 *   could not be parsed successfully.
 */
function drush_backend_parse_output($string, $backend_options = array(), $outputted = FALSE) {
  $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)');

  preg_match("/$regex/s", $string, $match);

  if (!empty($match) && $match[1]) {
    // we have our JSON encoded string
    $output = $match[1];
    // remove the match we just made and any non printing characters
    $string = trim(str_replace(sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string));
  }

  if (!empty($output)) {
    $data = json_decode($output, TRUE);
    if (is_array($data)) {
      _drush_backend_integrate($data, $backend_options, $outputted);
      return $data;
    }
  }
  return $string;
}

/**
 * Integrate log messages and error statuses into the current
 * process.
 *
 * Output produced by the called script will be printed if we didn't print it
 * on the fly, errors will be set, and log messages will be logged locally, if
 * not already logged.
 *
 * @param data
 *    The associative array returned from the external command.
 * @param outputted
 *    Whether output has already been handled.
 */
function _drush_backend_integrate($data, $backend_options, $outputted) {
  // In 'integrate' mode, logs and errors have already been handled
  // by drush_backend_packet (sender) drush_backend_parse_packets (receiver - us)
  // during incremental output.  We therefore do not need to call drush_set_error
  // or drush_log here.  The exception is if the sender is an older version of
  // Drush (version 4.x) that does not send backend packets, then we will
  // not have processed the log entries yet, and must print them here.
  $received_packets = drush_get_context('DRUSH_RECEIVED_BACKEND_PACKETS', FALSE);
  if (is_array($data['log']) && $backend_options['log'] && (!$received_packets)) {
    foreach($data['log'] as $log) {
      $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message'];
      if (isset($backend_options['#output-label'])) {
        $message = $backend_options['#output-label'] . $message;
      }
      if (isset($log['error']) && $backend_options['integrate']) {
        drush_set_error($log['error'], $message);
      }
      elseif ($backend_options['integrate']) {
        drush_log($message, $log['type']);
      }
    }
  }
  // Output will either be printed, or buffered to the drush_backend_output command.
  // If the output has already been printed, then we do not need to show it again on a failure.
  if (!$outputted) {
    if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) {
      drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output'])));
    }
    elseif ($backend_options['output']) {
      _drush_backend_print_output($data['output'], $backend_options);
    }
  }
}

/**
 * Supress log message output during backend integrate.
 */
function _drush_backend_integrate_log($entry) {
}

/**
 * Call an external command using proc_open.
 *
 * @param cmds
 *    An array of records containing the following elements:
 *      'cmd' - The command to execute, already properly escaped
 *      'post-options' - An associative array that will be JSON encoded
 *        and passed to the script being called. Objects are not allowed,
 *        as they do not json_decode gracefully.
 *      'backend-options' - Options that control the operation of the backend invoke
 *     - OR -
 *    An array of commands to execute. These commands already need to be properly escaped.
 *    In this case, post-options will default to empty, and a default output label will
 *    be generated.
 * @param data
 *    An associative array that will be JSON encoded and passed to the script being called.
 *    Objects are not allowed, as they do not json_decode gracefully.
 *
 * @return
 *   False if the command could not be executed, or did not return any output.
 *   If it executed successfully, it returns an associative array containing the command
 *   called, the output of the command, and the error code of the command.
 */
function _drush_backend_proc_open($cmds, $process_limit, $context = NULL) {
  $descriptorspec = array(
    0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
    1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
  );

  $open_processes = array();
  $bucket = array();
  $process_limit = max($process_limit, 1);
  $is_windows = drush_is_windows();
  // Loop through processes until they all close, having a nap as needed.
  $nap_time = 0;
  while (count($open_processes) || count($cmds)) {
    $nap_time++;
    if (count($cmds) && (count($open_processes) < $process_limit)) {
      // Pop the site and command (key / value) from the cmds array
      end($cmds);
      list($site, $cmd) = each($cmds);
      unset($cmds[$site]);

      if (is_array($cmd)) {
        $c = $cmd['cmd'];
        $post_options = $cmd['post-options'];
        $backend_options = $cmd['backend-options'];
      }
      else {
        $c = $cmd;
        $post_options = array();
        $backend_options = array();
      }
      $backend_options += array(
        '#output-label' => '',
        '#process-read-size' => 4096,
      );
      $process = array();
      drush_log($backend_options['#output-label'] . $c);
      $process['process'] = proc_open($c, $descriptorspec, $process['pipes'], null, null, array('context' => $context));
      if (is_resource($process['process'])) {
        if ($post_options) {
          fwrite($process['pipes'][0], json_encode($post_options)); // pass the data array in a JSON encoded string
        }
        // If we do not close stdin here, then we cause a deadlock;
        // see: http://drupal.org/node/766080#comment-4309936
        // If we reimplement interactive commands to also use
        // _drush_proc_open, then clearly we would need to keep
        // this open longer.
        fclose($process['pipes'][0]);

        $process['info'] = stream_get_meta_data($process['pipes'][1]);
        stream_set_blocking($process['pipes'][1], FALSE);
        stream_set_timeout($process['pipes'][1], 1);
        $bucket[$site]['cmd'] = $c;
        $bucket[$site]['output'] = '';
        $bucket[$site]['remainder'] = '';
        $bucket[$site]['backend-options'] = $backend_options;
        $bucket[$site]['end_of_output'] = FALSE;
        $bucket[$site]['outputted'] = FALSE;
        $open_processes[$site] = $process;
      }
      // Reset the $nap_time variable as there might be output to process next
      // time around:
      $nap_time = 0;
    }
    // Set up to call stream_select(). See:
    // http://php.net/manual/en/function.stream-select.php
    // We can't use stream_select on Windows, because it doesn't work for
    // streams returned by proc_open.
    if (!$is_windows) {
      $ss_result = 0;
      $read_streams = array();
      $write_streams = array();
      $except_streams = array();
      foreach ($open_processes as $site => &$current_process) {
        if (isset($current_process['pipes'][1])) {
          $read_streams[] = $current_process['pipes'][1];
        }
      }
      // Wait up to 2s for data to become ready on one of the read streams.
      if (count($read_streams)) {
        $ss_result = stream_select($read_streams, $write_streams, $except_streams, 2);
        // If stream_select returns a error, then fallback to using $nap_time.
        if ($ss_result !== FALSE) {
          $nap_time = 0;
        }
      }
    }

    foreach ($open_processes as $site => &$current_process) {
      if (isset($current_process['pipes'][1])) {
        // Collect output from stdout
        $bucket[$site][1] = '';
        $info = stream_get_meta_data($current_process['pipes'][1]);

        if (!feof($current_process['pipes'][1]) && !$info['timed_out']) {
          $string = $bucket[$site]['remainder'] . fread($current_process['pipes'][1], $backend_options['#process-read-size']);
          $bucket[$site]['remainder'] = '';
          $output_end_pos = strpos($string, DRUSH_BACKEND_OUTPUT_START);
          if ($output_end_pos !== FALSE) {
            $trailing_string = substr($string, 0, $output_end_pos);
            $trailing_remainder = '';
            // If there is any data in the trailing string (characters prior
            // to the backend output start), then process any backend packets
            // embedded inside.
            if (strlen($trailing_string) > 0) {
              drush_backend_parse_packets($trailing_string, $trailing_remainder, $bucket[$site]['backend-options']);
            }
            // If there is any data remaining in the trailing string after
            // the backend packets are removed, then print it.
            if (strlen($trailing_string) > 0) {
              _drush_backend_print_output($trailing_string . $trailing_remainder, $bucket[$site]['backend-options']);
              $bucket[$site]['outputted'] = TRUE;
            }
            $bucket[$site]['end_of_output'] = TRUE;
          }
          if (!$bucket[$site]['end_of_output']) {
            drush_backend_parse_packets($string, $bucket[$site]['remainder'], $bucket[$site]['backend-options']);
            // Pass output through.
            _drush_backend_print_output($string, $bucket[$site]['backend-options']);
            if (strlen($string) > 0) {
              $bucket[$site]['outputted'] = TRUE;
            }
          }
          $bucket[$site][1] .= $string;
          $bucket[$site]['output'] .= $string;
          $info = stream_get_meta_data($current_process['pipes'][1]);
          flush();

          // Reset the $nap_time variable as there might be output to process
          // next time around:
          if (strlen($string) > 0) {
            $nap_time = 0;
          }
        }
        else {
          fclose($current_process['pipes'][1]);
          unset($current_process['pipes'][1]);
          // close the pipe , set a marker

          // Reset the $nap_time variable as there might be output to process
          // next time around:
          $nap_time = 0;
        }
      }
      else {
        // if both pipes are closed for the process, remove it from active loop and add a new process to open.
        $bucket[$site]['code'] = proc_close($current_process['process']);
        unset($open_processes[$site]);

        // Reset the $nap_time variable as there might be output to process next
        // time around:
        $nap_time = 0;
      }
    }

    // We should sleep for a bit if we need to, up to a maximum of 1/10 of a
    // second.
    if ($nap_time > 0) {
      usleep(max($nap_time * 500, 100000));
    }
  }
  return $bucket;
  // TODO: Handle bad proc handles
  //}
  //return FALSE;
}



/**
 * Print the output received from a call to backend invoke,
 * adding the label to the head of each line if necessary.
 */
function _drush_backend_print_output($output_string, $backend_options) {
  if ($backend_options['output'] && !empty($output_string)) {
    $output_label = array_key_exists('#output-label', $backend_options) ? $backend_options['#output-label'] : FALSE;
    if (!empty($output_label)) {
      // Remove one, and only one newline from the end of the
      // string. Else we'll get an extra 'empty' line.
      foreach (explode("\n", preg_replace('/\\n$/', '', $output_string)) as $line) {
        fwrite(STDOUT, $output_label . rtrim($line) . "\n");
      }
    }
    else {
      fwrite(STDOUT, $output_string);
    }
  }
}

/**
 * Parse out and remove backend packet from the supplied string and
 * invoke the commands.
 */
function drush_backend_parse_packets(&$string, &$remainder, $backend_options) {
  $remainder = '';
  $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0"));
  $packet_regex = str_replace("\n", "", $packet_regex);
  if (preg_match_all("/$packet_regex/s", $string, $match, PREG_PATTERN_ORDER)) {
    drush_set_context('DRUSH_RECEIVED_BACKEND_PACKETS', TRUE);
    foreach ($match[1] as $packet_data) {
      $entry = (array) json_decode($packet_data);
      if (is_array($entry) && isset($entry['packet'])) {
        $function = 'drush_backend_packet_' . $entry['packet'];
        if (function_exists($function)) {
          $function($entry, $backend_options);
        }
        else {
          drush_log(dt("Unknown backend packet @packet", array('@packet' => $entry['packet'])), LogLevel::NOTICE);
        }
      }
      else {
        drush_log(dt("Malformed backend packet"), LogLevel::ERROR);
        drush_log(dt("Bad packet: @packet", array('@packet' => print_r($entry, TRUE))), LogLevel::DEBUG);
        drush_log(dt("String is: @str", array('@str' => $packet_data), LogLevel::DEBUG));
      }
    }
    $string = preg_replace("/$packet_regex/s", '', $string);
  }
  // Check to see if there is potentially a partial packet remaining.
  // We only care about the last null; if there are any nulls prior
  // to the last one, they would have been removed above if they were
  // valid drush packets.
  $embedded_null = strrpos($string, "\0");
  if ($embedded_null !== FALSE) {
    // We will consider everything after $embedded_null to be part of
    // the $remainder string if:
    //   - the embedded null is less than strlen(DRUSH_BACKEND_OUTPUT_START)
    //     from the end of $string (that is, there might be a truncated
    //     backend packet header, or the truncated backend output start
    //     after the null)
    //   OR
    //   - the embedded null is followed by DRUSH_BACKEND_PACKET_START
    //     (that is, the terminating null for that packet has not been
    //     read into our buffer yet)
    if (($embedded_null + strlen(DRUSH_BACKEND_OUTPUT_START) >= strlen($string)) || (substr($string, $embedded_null + 1, strlen(DRUSH_BACKEND_PACKET_START)) == DRUSH_BACKEND_PACKET_START)) {
      $remainder = substr($string, $embedded_null);
      $string = substr($string, 0, $embedded_null);
    }
  }
}

/**
 * Backend command for setting errors.
 */
function drush_backend_packet_set_error($data, $backend_options) {
  if (!$backend_options['integrate']) {
    return;
  }
  $output_label = "";
  if (array_key_exists('#output-label', $backend_options)) {
    $output_label = $backend_options['#output-label'];
  }
  drush_set_error($data['error'], $data['message'], $output_label);
}

/**
 * Default options for backend_invoke commands.
 */
function _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options) {
  // By default, if the caller does not specify a value for 'output', but does
  // specify 'integrate' === FALSE, then we will set output to FALSE.  Otherwise we
  // will allow it to default to TRUE.
  if ((array_key_exists('integrate', $backend_options)) && ($backend_options['integrate'] === FALSE) && (!array_key_exists('output', $backend_options))) {
    $backend_options['output'] = FALSE;
  }
  $has_site_specification = array_key_exists('root', $site_record) || array_key_exists('uri', $site_record);
  $result = $backend_options + array(
     'method' => 'GET',
     'output' => TRUE,
     'log' => TRUE,
     'integrate' => TRUE,
     'backend' => TRUE,
     'dispatch-using-alias' => !$has_site_specification,
  );
  // Convert '#integrate' et. al. into backend options
  foreach ($command_options as $key => $value) {
    if (substr($key,0,1) === '#') {
      $result[substr($key,1)] = $value;
    }
  }
  return $result;
}

/**
 * Execute a new local or remote command in a new process.
 *
 * n.b. Prefer drush_invoke_process() to this function.
 *
 * @param invocations
 *   An array of command records to exacute. Each record should contain:
 *     'site':
 *       An array containing information used to generate the command.
 *         'remote-host'
 *            Optional. A remote host to execute the drush command on.
 *         'remote-user'
 *            Optional. Defaults to the current user. If you specify this, you can choose which module to send.
 *         'ssh-options'
 *            Optional.  Defaults to "-o PasswordAuthentication=no"
 *         '#env-vars'
 *            Optional. An associative array of environmental variables to prefix the Drush command with.
 *         'path-aliases'
 *            Optional; contains paths to folders and executables useful to the command.
 *         '%drush-script'
 *            Optional. Defaults to the current drush.php file on the local machine, and
 *            to simply 'drush' (the drush script in the current PATH) on remote servers.
 *            You may also specify a different drush.php script explicitly.  You will need
 *            to set this when calling drush on a remote server if 'drush' is not in the
 *            PATH on that machine.
 *     'command':
 *       A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
 *     'args':
 *       An array of arguments for the command.
 *     'options'
 *       Optional. An array containing options to pass to the remote script.
 *       Array items with a numeric key are treated as optional arguments to the
 *       command.
 *     'backend-options':
 *       Optional. Additional parameters that control the operation of the invoke.
 *         'method'
 *            Optional. Defaults to 'GET'.
 *            If this parameter is set to 'POST', the $data array will be passed
 *            to the script being called as a JSON encoded string over the STDIN
 *            pipe of that process. This is preferable if you have to pass
 *            sensitive data such as passwords and the like.
 *            For any other value, the $data array will be collapsed down into a
 *            set of command line options to the script.
 *         'integrate'
 *            Optional. Defaults to TRUE.
 *            If TRUE, any error statuses will be integrated into the current
 *            process. This might not be what you want, if you are writing a
 *            command that operates on multiple sites.
 *         'log'
 *            Optional. Defaults to TRUE.
 *            If TRUE, any log messages will be integrated into the current
 *            process.
 *         'output'
 *            Optional. Defaults to TRUE.
 *            If TRUE, output from the command will be synchronously printed to
 *            stdout.
 *         'drush-script'
 *            Optional. Defaults to the current drush.php file on the local
 *            machine, and to simply 'drush' (the drush script in the current
 *            PATH) on remote servers.  You may also specify a different drush.php
 *            script explicitly.  You will need to set this when calling drush on
 *            a remote server if 'drush' is not in the PATH on that machine.
 *          'dispatch-using-alias'
 *            Optional. Defaults to FALSE.
 *            If specified as a non-empty value the drush command will be
 *            dispatched using the alias name on the command line, instead of
 *            the options from the alias being added to the command line
 *            automatically.
 * @param common_options
 *    Optional. Merged in with the options for each invocation.
 * @param backend_options
 *    Optional. Merged in with the backend options for each invocation.
 * @param default_command
 *    Optional. Used as the 'command' for any invocation that does not
 *    define a command explicitly.
 * @param default_site
 *    Optional. Used as the 'site' for any invocation that does not
 *    define a site explicitly.
 * @param context
 *    Optional. Passed in to proc_open if provided.
 *
 * @return
 *   If the command could not be completed successfully, FALSE.
 *   If the command was completed, this will return an associative array containing the data from drush_backend_output().
 */
function drush_backend_invoke_concurrent($invocations, $common_options = array(), $common_backend_options = array(), $default_command = NULL, $default_site = NULL, $context = NULL) {
  $index = 0;

  // Slice and dice our options in preparation to build a command string
  $invocation_options = array();
  foreach ($invocations as $invocation)  {
    $site_record = isset($invocation['site']) ? $invocation['site'] : $default_site;
    // NULL is a synonym to '@self', although the latter is preferred.
    if (!isset($site_record)) {
      $site_record = '@self';
    }
    // If the first parameter is not a site alias record,
    // then presume it is an alias name, and try to look up
    // the alias record.
    if (!is_array($site_record)) {
      $site_record = drush_sitealias_get_record($site_record);
    }
    $command = isset($invocation['command']) ? $invocation['command'] : $default_command;
    $args = isset($invocation['args']) ? $invocation['args'] : array();
    $command_options = isset($invocation['options']) ? $invocation['options'] : array();
    $backend_options = isset($invocation['backend-options']) ? $invocation['backend-options'] : array();
    // If $backend_options is passed in as a bool, interpret that as the value for 'integrate'
    if (!is_array($common_backend_options)) {
      $integrate = (bool)$common_backend_options;
      $common_backend_options = array('integrate' => $integrate);
    }

    $command_options += $common_options;
    $backend_options += $common_backend_options;

    $backend_options = _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options);
    $backend_options += array(
      'drush-script' => NULL,
    );

    // Insure that contexts such as DRUSH_SIMULATE and NO_COLOR are included.
    $command_options += _drush_backend_get_global_contexts($site_record);

    // Add in command-specific options as well
    $command_options += drush_command_get_command_specific_options($site_record, $command);

    // If the caller has requested it, don't pull the options from the alias
    // into the command line, but use the alias name for dispatching.
    if (!empty($backend_options['dispatch-using-alias']) && isset($site_record['#name'])) {
      list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options(array(), $command_options, $backend_options);
      $site_record_to_dispatch = '@' . ltrim($site_record['#name'], '@');
    }
    else {
      list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options($site_record, $command_options, $backend_options);
      $site_record_to_dispatch = '';
    }
    if (array_key_exists('backend-simulate', $backend_options)) {
      $drush_global_options['simulate'] = TRUE;
    }
    $site_record += array('path-aliases' => array(), '#env-vars' => array());
    $site_record['path-aliases'] += array(
      '%drush-script' => $backend_options['drush-script'],
    );

    $site = (array_key_exists('#name', $site_record) && !array_key_exists($site_record['#name'], $invocation_options)) ? $site_record['#name'] : $index++;
    $invocation_options[$site] = array(
      'site-record' => $site_record,
      'site-record-to-dispatch' => $site_record_to_dispatch,
      'command' => $command,
      'args' => $args,
      'post-options' => $post_options,
      'drush-global-options' => $drush_global_options,
      'commandline-options' => $commandline_options,
      'command-options' => $command_options,
      'backend-options' => $backend_options,
    );
  }

  // Calculate the length of the longest output label
  $max_name_length = 0;
  $label_separator = '';
  if (!array_key_exists('no-label', $common_options) && (count($invocation_options) > 1)) {
    $label_separator = array_key_exists('label-separator', $common_options) ? $common_options['label-separator'] : ' >> ';
    foreach ($invocation_options as $site => $item) {
      $backend_options = $item['backend-options'];
      if (!array_key_exists('#output-label', $backend_options)) {
        if (is_numeric($site)) {
          $backend_options['#output-label'] = ' * [@self.' . $site;
          $label_separator = '] ';
        }
        else {
          $backend_options['#output-label'] = $site;
        }
        $invocation_options[$site]['backend-options']['#output-label'] = $backend_options['#output-label'];
      }
      $name_len = strlen($backend_options['#output-label']);
      if ($name_len > $max_name_length) {
        $max_name_length = $name_len;
      }
      if (array_key_exists('#label-separator', $backend_options)) {
        $label_separator = $backend_options['#label-separator'];
      }
    }
  }
  // Now pad out the output labels and add the label separator.
  $reserve_margin = $max_name_length + strlen($label_separator);
  foreach ($invocation_options as $site => $item) {
    $backend_options = $item['backend-options'] + array('#output-label' => '');
    $invocation_options[$site]['backend-options']['#output-label'] = str_pad($backend_options['#output-label'], $max_name_length, " ") . $label_separator;
    if ($reserve_margin) {
      $invocation_options[$site]['drush-global-options']['reserve-margin'] = $reserve_margin;
    }
  }

  // Now take our prepared options and generate the command strings
  $cmds = array();
  foreach ($invocation_options as $site => $item) {
    $site_record = $item['site-record'];
    $site_record_to_dispatch = $item['site-record-to-dispatch'];
    $command = $item['command'];
    $args = $item['args'];
    $post_options = $item['post-options'];
    $commandline_options = $item['commandline-options'];
    $command_options = $item['command-options'];
    $drush_global_options = $item['drush-global-options'];
    $backend_options = $item['backend-options'];
    $is_remote = array_key_exists('remote-host', $site_record);
    $is_different_site =
      $is_remote ||
      (isset($site_record['root']) && ($site_record['root'] != drush_get_context('DRUSH_DRUPAL_ROOT'))) ||
      (isset($site_record['uri']) && ($site_record['uri'] != drush_get_context('DRUSH_SELECTED_URI')));
    $os = drush_os($site_record);
    // If the caller did not pass in a specific path to drush, then we will
    // use a default value.  For commands that are being executed on the same
    // machine, we will use DRUSH_COMMAND, which is the path to the drush.php
    // that is running right now.  For remote commands, we will run a wrapper
    // script instead of drush.php called drush.
    $drush_path = $site_record['path-aliases']['%drush-script'];
    if (!$drush_path && !$is_remote && $is_different_site) {
      $drush_path = find_wrapper_or_launcher($site_record['root']);
    }
    $env_vars = $site_record['#env-vars'];
    $php = array_key_exists('php', $site_record) ? $site_record['php'] : (array_key_exists('php', $command_options) ? $command_options['php'] : NULL);
    $drush_command_path = drush_build_drush_command($drush_path, $php, $os, $is_remote, $env_vars);
    $cmd = _drush_backend_generate_command($site_record, $drush_command_path . " " . _drush_backend_argument_string($drush_global_options, $os) . " " . $site_record_to_dispatch . " " . $command, $args, $commandline_options, $backend_options) . ' 2>&1';
    $cmds[$site] = array(
      'cmd' => $cmd,
      'post-options' => $post_options,
      'backend-options' => $backend_options,
    );
  }

  return _drush_backend_invoke($cmds, $common_backend_options, $context);
}

/**
 * Find all of the drush contexts that are used to cache global values and
 * return them in an associative array.
 */
function _drush_backend_get_global_contexts($site_record) {
  $result = array();
  $global_option_list = drush_get_global_options(FALSE);
  foreach ($global_option_list as $global_key => $global_metadata) {
    if (is_array($global_metadata)) {
      $value = '';
      if (!array_key_exists('never-propagate', $global_metadata)) {
        if ((array_key_exists('propagate', $global_metadata))) {
          $value = drush_get_option($global_key);
        }
        elseif ((array_key_exists('propagate-cli-value', $global_metadata))) {
          $value = drush_get_option($global_key, '', 'cli');
        }
        elseif ((array_key_exists('context', $global_metadata))) {
          // If the context is declared to be a 'local-context-only',
          // then only put it in if this is a local dispatch.
          if (!array_key_exists('local-context-only', $global_metadata) || !array_key_exists('remote-host', $site_record)) {
            $value = drush_get_context($global_metadata['context'], array());
          }
        }
        if (!empty($value) || ($value === '0')) {
          $result[$global_key] = $value;
        }
      }
    }
  }
  return $result;
}

/**
 * Take all of the values in the $command_options array, and place each of
 * them into one of the following result arrays:
 *
 *     - $post_options: options to be encoded as JSON and written to the
 *       standard input of the drush subprocess being executed.
 *     - $commandline_options: options to be placed on the command line of the drush
 *       subprocess.
 *     - $drush_global_options: the drush global options also go on the command
 *       line, but appear before the drush command name rather than after it.
 *
 * Also, this function may modify $backend_options.
 */
function _drush_backend_classify_options($site_record, $command_options, &$backend_options) {
  // In 'POST' mode (the default, remove everything (except the items marked 'never-post'
  // in the global option list) from the commandline options and put them into the post options.
  // The post options will be json-encoded and sent to the command via stdin
  $global_option_list = drush_get_global_options(FALSE); // These should be in the command line.
  $additional_global_options = array();
  if (array_key_exists('additional-global-options', $backend_options)) {
    $additional_global_options = $backend_options['additional-global-options'];
    $command_options += $additional_global_options;
  }
  $method_post = ((!array_key_exists('method', $backend_options)) || ($backend_options['method'] == 'POST'));
  $post_options = array();
  $commandline_options = array();
  $drush_global_options = array();
  $drush_local_options = array();
  $additional_backend_options = array();
  foreach ($site_record as $key => $value) {
    if (!in_array($key, drush_sitealias_site_selection_keys())) {
      if ($key[0] == '#') {
        $backend_options[$key] = $value;
      }
      if (!isset($command_options[$key])) {
        if (array_key_exists($key, $global_option_list)) {
          $command_options[$key] = $value;
        }
      }
    }
  }
  if (array_key_exists('drush-local-options', $backend_options)) {
    $drush_local_options = $backend_options['drush-local-options'];
    $command_options += $drush_local_options;
  }
  if (!empty($backend_options['backend']) && empty($backend_options['interactive']) && empty($backend_options['fork'])) {
    $drush_global_options['backend'] = '2';
  }
  foreach ($command_options as $key => $value) {
    $global = array_key_exists($key, $global_option_list);
    $propagate = TRUE;
    $special = FALSE;
    if ($global) {
      $propagate = (!array_key_exists('never-propagate', $global_option_list[$key]));
      $special = (array_key_exists('never-post', $global_option_list[$key]));
      if ($propagate) {
        // We will allow 'merge-pathlist' contexts to be propogated.  Right now
        // these are all 'local-context-only' options; if we allowed them to
        // propogate remotely, then we would need to get the right path separator
        // for the remote machine.
        if (is_array($value) && array_key_exists('merge-pathlist', $global_option_list[$key])) {
          $value = implode(PATH_SEPARATOR, $value);
        }
      }
    }
    // Just remove options that are designated as non-propagating
    if ($propagate === TRUE) {
      // In METHOD POST, move command options to post options
      if ($method_post && ($special === FALSE)) {
        $post_options[$key] = $value;
      }
      // In METHOD GET, ignore options with array values
      elseif (!is_array($value)) {
        if ($global || array_key_exists($key, $additional_global_options)) {
          $drush_global_options[$key] = $value;
        }
        else {
          $commandline_options[$key] = $value;
        }
      }
    }
  }
  return array($post_options, $commandline_options, $drush_global_options, $additional_backend_options);
}

/**
 * Create a new pipe with proc_open, and attempt to parse the output.
 *
 * We use proc_open instead of exec or others because proc_open is best
 * for doing bi-directional pipes, and we need to pass data over STDIN
 * to the remote script.
 *
 * Exec also seems to exhibit some strangeness in keeping the returned
 * data intact, in that it modifies the newline characters.
 *
 * @param cmd
 *   The complete command line call to use.
 * @param post_options
 *   An associative array to json-encode and pass to the remote script on stdin.
 * @param backend_options
 *   Options for the invocation.
 *
 * @return
 *   If no commands were executed, FALSE.
 *
 *   If one command was executed, this will return an associative array containing
 *   the data from drush_backend_output().  The result code is stored
 *   in $result['error_status'] (0 == no error).
 *
 *   If multiple commands were executed, this will return an associative array
 *   containing one item, 'concurrent', which will contain a list of the different
 *   backend invoke results from each concurrent command.
 */
function _drush_backend_invoke($cmds, $common_backend_options = array(), $context = NULL) {
  if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('override-simulated', $common_backend_options) && !array_key_exists('backend-simulate', $common_backend_options)) {
    foreach ($cmds as $cmd) {
      drush_print(dt('Simulating backend invoke: !cmd', array('!cmd' => $cmd['cmd'])));
    }
    return FALSE;
  }
  foreach ($cmds as $cmd) {
    drush_log(dt('Backend invoke: !cmd', array('!cmd' => $cmd['cmd'])), 'command');
  }
  if (!empty($common_backend_options['interactive']) || !empty($common_backend_options['fork'])) {
    foreach ($cmds as $cmd) {
      $exec_cmd = $cmd['cmd'];
      if (array_key_exists('fork', $common_backend_options)) {
        $exec_cmd .= ' --quiet &';
      }

      $result_code = drush_shell_proc_open($exec_cmd);
      $ret = array('error_status' => $result_code);
    }
  }
  else {
    $process_limit = drush_get_option_override($common_backend_options, 'concurrency', 1);
    $procs = _drush_backend_proc_open($cmds, $process_limit, $context);
    $procs = is_array($procs) ? $procs : array($procs);

    $ret = array();
    foreach ($procs as $site => $proc) {
      if (($proc['code'] == DRUSH_APPLICATION_ERROR) && isset($common_backend_options['integrate'])) {
        drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error."));
      }

      if ($proc['output']) {
        $values = drush_backend_parse_output($proc['output'], $proc['backend-options'], $proc['outputted']);
        if (is_array($values)) {
          $values['site'] = $site;
          if (empty($ret)) {
            $ret = $values;
          }
          elseif (!array_key_exists('concurrent', $ret)) {
            $ret = array('concurrent' => array($ret, $values));
          }
          else {
            $ret['concurrent'][] = $values;
          }
        }
        else {
          $ret = drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: !code)", array("!return" => $proc['output'], "!code" =>  $proc['code'])));
        }
      }
    }
  }
  return empty($ret) ? FALSE : $ret;
}

/**
 * Helper function that generates an anonymous site alias specification for
 * the given parameters.
 */
function drush_backend_generate_sitealias($backend_options) {
  // Ensure default values.
  $backend_options += array(
    'remote-host' => NULL,
    'remote-user' => NULL,
    'ssh-options' => NULL,
    'drush-script' => NULL,
    'env-vars' => NULL
  );
  return array(
    'remote-host' => $backend_options['remote-host'],
    'remote-user' => $backend_options['remote-user'],
    'ssh-options' => $backend_options['ssh-options'],
    '#env-vars' => $backend_options['env-vars'],
    'path-aliases' => array(
      '%drush-script' => $backend_options['drush-script'],
    ),
  );
}

/**
 * Generate a command to execute.
 *
 * @param site_record
 *   An array containing information used to generate the command.
 *   'remote-host'
 *      Optional. A remote host to execute the drush command on.
 *   'remote-user'
 *      Optional. Defaults to the current user. If you specify this, you can choose which module to send.
 *   'ssh-options'
 *      Optional.  Defaults to "-o PasswordAuthentication=no"
 *   '#env-vars'
 *      Optional. An associative array of environmental variables to prefix the Drush command with.
 *   'path-aliases'
 *      Optional; contains paths to folders and executables useful to the command.
 *      '%drush-script'
 *        Optional. Defaults to the current drush.php file on the local machine, and
 *        to simply 'drush' (the drush script in the current PATH) on remote servers.
 *        You may also specify a different drush.php script explicitly.  You will need
 *        to set this when calling drush on a remote server if 'drush' is not in the
 *        PATH on that machine.
 * @param command
 *    A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
 * @param args
 *    An array of arguments for the command.
 * @param command_options
 *    Optional. An array containing options to pass to the remote script.
 *    Array items with a numeric key are treated as optional arguments to the
 *    command.  This parameter is a reference, as any options that have been
 *    represented as either an option, or an argument will be removed.  This
 *    allows you to pass the left over options as a JSON encoded string,
 *    without duplicating data.
 * @param backend_options
 *    Optional. An array of options for the invocation.
 *    @see drush_backend_invoke for documentation.
 *
 * @return
 *   A text string representing a fully escaped command.
 */
function _drush_backend_generate_command($site_record, $command, $args = array(), $command_options = array(), $backend_options = array()) {
  $site_record += array(
    'remote-host' => NULL,
    'remote-user' => NULL,
    'ssh-options' => NULL,
    'path-aliases' => array(),
  );
  $backend_options += array(
    '#tty' => FALSE,
  );

  $hostname = $site_record['remote-host'];
  $username = $site_record['remote-user'];
  $ssh_options = $site_record['ssh-options'];
  $os = drush_os($site_record);

  if (drush_is_local_host($hostname)) {
    $hostname = null;
  }

  foreach ($command_options as $key => $arg) {
    if (is_numeric($key)) {
      $args[] = $arg;
      unset($command_options[$key]);
    }
  }

  $cmd[] = $command;
  foreach ($args as $arg) {
    $cmd[] = drush_escapeshellarg($arg, $os);
  }
  $option_str = _drush_backend_argument_string($command_options, $os);
  if (!empty($option_str)) {
    $cmd[] = " " . $option_str;
  }
  $command = implode(' ', array_filter($cmd, 'strlen'));
  if (isset($hostname)) {
    $username = (isset($username)) ? drush_escapeshellarg($username, "LOCAL") . "@" : '';
    $ssh_options = $site_record['ssh-options'];
    $ssh_options = (isset($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no");

    $ssh_cmd[] = "ssh";
    $ssh_cmd[] = $ssh_options;
    if ($backend_options['#tty']) {
      $ssh_cmd[] = '-t';
    }
    $ssh_cmd[] = $username . drush_escapeshellarg($hostname, "LOCAL");
    $ssh_cmd[] = drush_escapeshellarg($command . ' 2>&1', "LOCAL");

    // Remove NULLs and separate with spaces
    $command = implode(' ', array_filter($ssh_cmd, 'strlen'));
  }

  return $command;
}

/**
 * Map the options to a string containing all the possible arguments and options.
 *
 * @param data
 *    Optional. An array containing options to pass to the remote script.
 *    Array items with a numeric key are treated as optional arguments to the command.
 *    This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed.
 *    This allows you to pass the left over options as a JSON encoded string, without duplicating data.
 * @param method
 *    Optional. Defaults to 'GET'.
 *    If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
 *    the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
 *    For any other value, the $data array will be collapsed down into a set of command line options to the script.
 * @return
 *    A properly formatted and escaped set of arguments and options to append to the drush.php shell command.
 */
function _drush_backend_argument_string($data, $os = NULL) {
  $options = array();

  foreach ($data as $key => $value) {
    if (!is_array($value) && !is_object($value) && isset($value)) {
      if (substr($key,0,1) != '#') {
        $options[$key] = $value;
      }
    }
  }

  $option_str = '';
  foreach ($options as $key => $value) {
    $option_str .= _drush_escape_option($key, $value, $os);
  }

  return $option_str;
}

/**
 * Return a properly formatted and escaped command line option
 *
 * @param key
 *   The name of the option.
 * @param value
 *   The value of the option.
 *
 * @return
 *   If the value is set to TRUE, this function will return " --key"
 *   In other cases it will return " --key='value'"
 */
function _drush_escape_option($key, $value = TRUE, $os = NULL) {
  if ($value !== TRUE) {
    $option_str = " --$key=" . drush_escapeshellarg($value, $os);
  }
  else {
    $option_str = " --$key";
  }
  return $option_str;
}

/**
 * Read options fron STDIN during POST requests.
 *
 * This function will read any text from the STDIN pipe,
 * and attempts to generate an associative array if valid
 * JSON was received.
 *
 * @return
 *   An associative array of options, if successfull. Otherwise FALSE.
 */
function _drush_backend_get_stdin() {
  $fp = fopen('php://stdin', 'r');
  // Windows workaround: we cannot count on stream_get_contents to
  // return if STDIN is reading from the keyboard.  We will therefore
  // check to see if there are already characters waiting on the
  // stream (as there always should be, if this is a backend call),
  // and if there are not, then we will exit.
  // This code prevents drush from hanging forever when called with
  // --backend from the commandline; however, overall it is still
  // a futile effort, as it does not seem that backend invoke can
  // successfully write data to that this function can read,
  // so the argument list and command always come out empty. :(
  // Perhaps stream_get_contents is the problem, and we should use
  // the technique described here:
  //   http://bugs.php.net/bug.php?id=30154
  // n.b. the code in that issue passes '0' for the timeout in stream_select
  // in a loop, which is not recommended.
  // Note that the following DOES work:
  //   drush ev 'print(json_encode(array("test" => "XYZZY")));' | drush status --backend
  // So, redirecting input is okay, it is just the proc_open that is a problem.
  if (drush_is_windows()) {
    // Note that stream_select uses reference parameters, so we need variables (can't pass a constant NULL)
    $read = array($fp);
    $write = NULL;
    $except = NULL;
    // Question: might we need to wait a bit for STDIN to be ready,
    // even if the process that called us immediately writes our parameters?
    // Passing '100' for the timeout here causes us to hang indefinitely
    // when called from the shell.
    $changed_streams = stream_select($read, $write, $except, 0);
    // Return on error (FALSE) or no changed streams (0).
    // Oh, according to http://php.net/manual/en/function.stream-select.php,
    // stream_select will return FALSE for streams returned by proc_open.
    // That is not applicable to us, is it? Our stream is connected to a stream
    // created by proc_open, but is not a stream returned by proc_open.
    if ($changed_streams < 1) {
      return FALSE;
    }
  }
  stream_set_blocking($fp, FALSE);
  $string = stream_get_contents($fp);
  fclose($fp);
  if (trim($string)) {
    return json_decode($string, TRUE);
  }
  return FALSE;
}
<?php
/**
 * @file
 *    Drush batch API.
 *
 * This file contains a fork of the Drupal Batch API that has been drastically
 * simplified and tailored to Drush's unique use case.
 *
 * The existing API is very targeted towards environments that are web accessible,
 * and would frequently attempt to redirect the user which would result in the
 * drush process being completely destroyed with no hope of recovery.
 *
 * While the original API does offer a 'non progressive' mode which simply
 * calls each operation in sequence within the current process, in most
 * implementations (D6), it would still attempt to redirect
 * unless very specific conditions were met.
 *
 * When operating in 'non progressive' mode, Drush would experience the problems
 * that the API was written to solve in the first place, specifically that processes
 * would exceed the available memory and exit with an error.
 *
 * Each major release of Drupal has also had slightly different implementations
 * of the batch API, and this provides a uniform interface to all of these
 * implementations.
 */

use Drush\Log\LogLevel;

/**
 * Class extending ArrayObject to allow the batch API to perform logging when
 * some keys of the array change.
 *
 * It is used to wrap batch's $context array and set log messages when values
 * are assigned to keys 'message' or 'error_message'.
 *
 * @see _drush_batch_worker().
 */
class DrushBatchContext extends ArrayObject {
  function offsetSet($name, $value) {
    if ($name == 'message') {
      drush_log(strip_tags($value), LogLevel::OK);
    }
    elseif ($name == 'error_message') {
      drush_set_error('DRUSH_BATCH_ERROR', strip_tags($value));
    }
    parent::offsetSet($name, $value);
  }
}

/**
 * Process a Drupal batch by spawning multiple Drush processes.
 *
 * This function will include the correct batch engine for the current
 * major version of Drupal, and will make use of the drush_backend_invoke
 * system to spawn multiple worker threads to handle the processing of
 * the current batch, while keeping track of available memory.
 *
 * The batch system will process as many batch sets as possible until
 * the entire batch has been completed or half of the available memory
 * has been used.
 *
 * This function is a drop in replacement for the existing batch_process()
 * function of Drupal.
 *
 * @param string $command
 *   (optional) The command to call for the back end process. By default this will be
 *   the 'batch-process' command, but some commands such as updatedb will
 *   have special initialization requirements, and will need to define and
 *   use their own command.
 * @param array $args
 *   (optional)
 * @param array $options
 *   (optional)
 */
function drush_backend_batch_process($command = 'batch-process', $args = array(), $options = array()) {
  // Command line options to pass to the command.
  $options['u'] = drush_user_get_class()->getCurrentUserAsSingle()->id();

  drush_include_engine('drupal', 'batch');
  _drush_backend_batch_process($command, $args, $options);
}

/**
 * Process sets from the specified batch.
 *
 * This function is called by the worker process that is spawned by the
 * drush_backend_batch_process function.
 *
 * The command called needs to call this function after it's special bootstrap
 * requirements have been taken care of.
 *
 * @param int $id
 *   The batch ID of the batch being processed.
 */
function drush_batch_command($id) {
  include_once(DRUSH_DRUPAL_CORE . '/includes/batch.inc');
  drush_include_engine('drupal', 'batch');
  _drush_batch_command($id);
}
<?php

use Drush\Log\LogLevel;

/**
 * No bootstrap.
 *
 * Commands that only preflight, but do not bootstrap, should use
 * a bootstrap level of DRUSH_BOOTSTRAP_NONE.
 */
define('DRUSH_BOOTSTRAP_NONE', -1);

/**
 * Use drush_bootstrap_max instead of drush_bootstrap_to_phase
 *
 * This constant is only usable as the value of the 'bootstrap'
 * item of a command object, or as the parameter to
 * drush_bootstrap_to_phase.  It is not a real bootstrap state.
 */
define('DRUSH_BOOTSTRAP_MAX', -2);

/**
 * @deprecated
 *
 * No longer used, but 0 remains reserved. Drush always runs preflight.
 * Commands may alternatively use DRUSH_BOOTSTRAP_NONE.
 */
define('DRUSH_BOOTSTRAP_DRUSH', 0);

// TODO: Move all of the constants below to a Drupal-specific file.
// We can't do this until commands are declaring which CMS they work
// with, because right now, commands that do not declare a 'bootstrap'
// level default to DRUSH_BOOTSTRAP_DRUPAL_LOGIN, so we need this constant,
// at least, available in non-Drupal contexts.

/**
 * Set up and test for a valid drupal root, either through the -r/--root options,
 * or evaluated based on the current working directory.
 *
 * Any code that interacts with an entire Drupal installation, and not a specific
 * site on the Drupal installation should use this bootstrap phase.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_ROOT',  1);

/**
 * Set up a Drupal site directory and the correct environment variables to
 * allow Drupal to find the configuration file.
 *
 * If no site is specified with the -l / --uri options, Drush will assume the
 * site is 'default', which mimics Drupal's behaviour.
 *
 * If you want to avoid this behaviour, it is recommended that you use the
 * DRUSH_BOOTSTRAP_DRUPAL_ROOT bootstrap phase instead.
 *
 * Any code that needs to modify or interact with a specific Drupal site's
 * settings.php file should bootstrap to this phase.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_SITE', 2);

/**
 * Load the settings from the Drupal sites directory.
 *
 * This phase is analagous to the DRUPAL_BOOTSTRAP_CONFIGURATION bootstrap phase in Drupal
 * itself, and this is also the first step where Drupal specific code is included.
 *
 * This phase is commonly used for code that interacts with the Drupal install API,
 * as both install.php and update.php start at this phase.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION', 3);

/**
 * Connect to the Drupal database using the database credentials loaded
 * during the previous bootstrap phase.
 *
 * This phase is analogous to the DRUPAL_BOOTSTRAP_DATABASE bootstrap phase in
 * Drupal.
 *
 * Any code that needs to interact with the Drupal database API needs to
 * be bootstrapped to at least this phase.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_DATABASE', 4);

/**
 * Fully initialize Drupal.
 *
 * This is analogous to the DRUPAL_BOOTSTRAP_FULL bootstrap phase in
 * Drupal.
 *
 * Any code that interacts with the general Drupal API should be
 * bootstrapped to this phase.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_FULL', 5);

/**
 * Log in to the initialiased Drupal site.
 *
 * This is the default bootstrap phase all commands will try to reach,
 * unless otherwise specified.
 *
 * This bootstrap phase is used after the site has been
 * fully bootstrapped.
 *
 * This phase will log you in to the drupal site with the username
 * or user ID specified by the --user/ -u option.
 *
 * Use this bootstrap phase for your command if you need to have access
 * to information for a specific user, such as listing nodes that might
 * be different based on who is logged in.
 */
define('DRUSH_BOOTSTRAP_DRUPAL_LOGIN', 6);

/**
 * Return the list of bootstrap objects that are available for
 * initializing a CMS with Drush.  We insure that any given candidate
 * class is instantiated only once.
 *
 * @return \Drush\Boot\Boot[]
 */
function drush_get_bootstrap_candidates() {
  $candidate_classes = drush_get_bootstrap_candidate_classnames();

  $cache =& drush_get_context('DRUSH_BOOTSTRAP_CANDIDATE_OBJECTS');

  $result = array();
  foreach($candidate_classes as $candidate_class) {
    if (array_key_exists($candidate_class, $cache)) {
      $result[$candidate_class] = $cache[$candidate_class];
    }
    else {
      $result[$candidate_class] = new $candidate_class;
    }
  }

  $cache = $result;
  return $result;
}

/**
 * Find the list of bootstrap classnames available for initializing a
 * CMS with Drush.
 *
 * @return array
 */
function drush_get_bootstrap_candidate_classnames() {
  // Give all commandfiles a chance to return candidates.  They should
  // return STRINGS with the class name of the bootstrap object they provide.
  $candidates = drush_command_invoke_all('bootstrap_candidates');
  // If a bootstrap class was specified on the command line, consider it first.
  $bootstrap_class = drush_get_option('bootstrap_class', FALSE);
  if ($bootstrap_class) {
    array_unshift($candidates, $bootstrap_class);
  }
  // Add candidate bootstrap classes for Drupal
  foreach (array('8', '7', '6') as $version) {
    $drupal_bootstrap_class = 'Drush\Boot\DrupalBoot' . $version;
    $candidates[] = $drupal_bootstrap_class;
  }
  // Always consider our default bootstrap class last.
  $candidates[] = 'Drush\Boot\EmptyBoot';

  return $candidates;
}

/**
 * Look up the best bootstrap class for the given location
 * from the set of available candidates.
 */
function drush_bootstrap_class_for_root($path) {
  drush_load_bootstrap_commandfile_at_path($path);
  $candidates = drush_get_bootstrap_candidates();
  foreach ($candidates as $candidate) {
    if ($candidate->valid_root($path)) {
      return $candidate;
    }
  }
  return NULL;
}

/**
 * Check to see if there is a bootstrap class available
 * at the specified location; if there is, load it.
 */
function drush_load_bootstrap_commandfile_at_path($path) {
  static $paths = array();

  if (!empty($path) && (!array_key_exists($path, $paths))) {
    $paths[$path] = TRUE;
    // Check to see if we have any bootstrap classes in this location.
    $bootstrap_class_dir = $path . '/drush/bootstrap';
    if (is_dir($bootstrap_class_dir)) {
      _drush_add_commandfiles(array($bootstrap_class_dir), DRUSH_BOOTSTRAP_NONE);
    }
  }
}

/**
 * Select the bootstrap class to use.  If this is called multiple
 * times, the bootstrap class returned might change on subsequent
 * calls, if the root directory changes.  Once the bootstrap object
 * starts changing the state of the system, however, it will
 * be 'latched', and further calls to drush_select_bootstrap_class()
 * will always return the same object.
 */
function drush_select_bootstrap_class() {
  $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');

  // Once we have selected a Drupal root, we will reduce our bootstrap
  // candidates down to just the one used to select this site root.
  $bootstrap = drush_bootstrap_class_for_root($root);
  // If we have not found a bootstrap class by this point,
  // then take the last one and use it.  This should be our
  // default bootstrap class.  The default bootstrap class
  // should pass through all calls without doing anything that
  // changes state in a CMS-specific way.
  if ($bootstrap == NULL) {
    $candidates = drush_get_bootstrap_candidates();
    $bootstrap = array_pop($candidates);
  }

  return $bootstrap;
}

/**
 * Don't allow the bootstrap object to change once we start bootstrapping
 */
function drush_latch_bootstrap_object($bootstrap) {
  drush_set_context('DRUSH_BOOTSTRAP_OBJECT', $bootstrap);
}

/**
 * Get the appropriate bootstrap object.  We'll search for a new
 * bootstrap object every time someone asks for one until we start
 * bootstrapping; then we'll returned the same cached one every time.
 *
 * @return \Drush\Boot\Boot
 */
function drush_get_bootstrap_object() {
  $bootstrap = drush_get_context('DRUSH_BOOTSTRAP_OBJECT', FALSE);
  if (!$bootstrap) {
    $bootstrap = drush_select_bootstrap_class();
  }
  return $bootstrap;
}

/**
 * Find the URI that has been selected by the cwd
 * if it was not previously set via the --uri / -l option
 */
function _drush_bootstrap_selected_uri() {
  $uri = drush_get_context('DRUSH_SELECTED_URI');
  if (empty($uri)) {
    $site_path = drush_site_path();
    $elements = explode('/', $site_path);
    $current = array_pop($elements);
    if (!$current) {
      $current = 'default';
    }
    $uri = 'http://'. $current;
    $uri = drush_set_context('DRUSH_SELECTED_URI', $uri);
    drush_sitealias_create_self_alias();
  }

  return $uri;
}

/**
 * Helper function to store any context settings that are being validated.
 */
function drush_bootstrap_value($context, $value = null) {
  $values =& drush_get_context('DRUSH_BOOTSTRAP_VALUES', array());

  if (isset($value)) {
    $values[$context] = $value;
  }

  if (array_key_exists($context, $values)) {
    return $values[$context];
  }

  return null;
}

/**
 * Returns an array that determines what bootstrap phases
 * are necessary to bootstrap the CMS.
 *
 * @param bool $function_names
 *   (optional) If TRUE, return an array of method names index by their
 *   corresponding phase values. Otherwise return an array of phase values.
 *
 * @return array
 *
 * @see \Drush\Boot\Boot::bootstrap_phases()
 */
function _drush_bootstrap_phases($function_names = FALSE) {
  $result = array();

  if ($bootstrap = drush_get_bootstrap_object()) {
    $result = $bootstrap->bootstrap_phases();
    if (!$function_names) {
      $result = array_keys($result);
    }
  }
  return $result;
}

/**
 * Bootstrap Drush to the desired phase.
 *
 * This function will sequentially bootstrap each
 * lower phase up to the phase that has been requested.
 *
 * @param int $phase
 *   The bootstrap phase to bootstrap to.
 * @param int $phase_max
 *   (optional) The maximum level to boot to. This does not have a use in this
 *   function itself but can be useful for other code called from within this
 *   function, to know if e.g. a caller is in the process of booting to the
 *   specified level. If specified, it should never be lower than $phase.
 *
 * @return bool
 *   TRUE if the specified bootstrap phase has completed.
 *
 * @see \Drush\Boot\Boot::bootstrap_phases()
 */
function drush_bootstrap($phase, $phase_max = FALSE) {
  $bootstrap = drush_get_bootstrap_object();
  $phases = _drush_bootstrap_phases(TRUE);
  $result = TRUE;

  // If the requested phase does not exist in the list of available
  // phases, it means that the command requires bootstrap to a certain
  // level, but no site root could be found.
  if (!isset($phases[$phase])) {
    $result = drush_bootstrap_error('DRUSH_NO_SITE', dt("We could not find an applicable site for that command."));
  }

  // Once we start bootstrapping past the DRUSH_BOOTSTRAP_DRUSH phase, we
  // will latch the bootstrap object, and prevent it from changing.
  if ($phase > DRUSH_BOOTSTRAP_DRUSH) {
    drush_latch_bootstrap_object($bootstrap);
  }

  drush_set_context('DRUSH_BOOTSTRAPPING', TRUE);
  foreach ($phases as $phase_index => $current_phase) {
    $bootstrapped_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE', -1);
    if ($phase_index > $phase) {
      break;
    }
    if ($phase_index > $bootstrapped_phase) {
      if ($result = drush_bootstrap_validate($phase_index)) {
        if (method_exists($bootstrap, $current_phase) && !drush_get_error()) {
          drush_log(dt("Drush bootstrap phase : !function()", array('!function' => $current_phase)), LogLevel::BOOTSTRAP);
          $bootstrap->{$current_phase}();

          // Reset commandfile cache and find any new command files that are available during this bootstrap phase.
          drush_get_commands(TRUE);
          _drush_find_commandfiles($phase_index, $phase_max);
        }
        drush_set_context('DRUSH_BOOTSTRAP_PHASE', $phase_index);
      }
    }
  }
  drush_set_context('DRUSH_BOOTSTRAPPING', FALSE);
  if (!$result || drush_get_error()) {
    $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array());
    foreach ($errors as $code => $message) {
      drush_set_error($code, $message);
    }
  }
  return !drush_get_error();
}

/**
 * Determine whether a given bootstrap phase has been completed
 *
 * This function name has a typo which makes me laugh so we choose not to
 * fix it. Take a deep breath, and smile. See
 * http://en.wikipedia.org/wiki/HTTP_referer
 *
 *
 * @param int $phase
 *   The bootstrap phase to test
 *
 * @return bool
 *   TRUE if the specified bootstrap phase has completed.
 */
function drush_has_boostrapped($phase) {
  $phase_index = drush_get_context('DRUSH_BOOTSTRAP_PHASE');

  return isset($phase_index) && ($phase_index >= $phase);
}

/**
 * Validate whether a bootstrap phase can be reached.
 *
 * This function will validate the settings that will be used
 * during the actual bootstrap process, and allow commands to
 * progressively bootstrap to the highest level that can be reached.
 *
 * This function will only run the validation function once, and
 * store the result from that execution in a local static. This avoids
 * validating phases multiple times.
 *
 * @param int $phase
 *   The bootstrap phase to validate to.
 *
 * @return bool
 *   TRUE if bootstrap is possible, FALSE if the validation failed.
 *
 * @see \Drush\Boot\Boot::bootstrap_phases()
 */
function drush_bootstrap_validate($phase) {
  $bootstrap = drush_get_bootstrap_object();
  $phases = _drush_bootstrap_phases(TRUE);
  static $result_cache = array();

  if (!array_key_exists($phase, $result_cache)) {
    drush_set_context('DRUSH_BOOTSTRAP_ERRORS', array());
    drush_set_context('DRUSH_BOOTSTRAP_VALUES', array());

    foreach ($phases as $phase_index => $current_phase) {
      $validated_phase = drush_get_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', -1);
      if ($phase_index > $phase) {
        break;
      }
      if ($phase_index > $validated_phase) {
        $current_phase .= '_validate';
        if (method_exists($bootstrap, $current_phase)) {
          $result_cache[$phase_index] = $bootstrap->{$current_phase}();
        }
        else {
          $result_cache[$phase_index] = TRUE;
        }
        drush_set_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', $phase_index);
      }
    }
  }
  return $result_cache[$phase];
}

/**
 * Bootstrap to the specified phase.
 *
 * @param int $max_phase_index
 *   Only attempt bootstrap to the specified level.
 *
 * @return bool
 *   TRUE if the specified bootstrap phase has completed.
 */
function drush_bootstrap_to_phase($max_phase_index) {
  if ($max_phase_index == DRUSH_BOOTSTRAP_MAX) {
    // Bootstrap as far as we can without throwing an error, but log for
    // debugging purposes.
    drush_log(dt("Trying to bootstrap as far as we can."), 'debug');
    drush_bootstrap_max();
    return TRUE;
  }

  drush_log(dt("Bootstrap to phase !phase.", array('!phase' => $max_phase_index)), LogLevel::BOOTSTRAP);
  $phases = _drush_bootstrap_phases();
  $result = TRUE;

  // Try to bootstrap to the maximum possible level, without generating errors
  foreach ($phases as $phase_index) {
    if ($phase_index > $max_phase_index) {
      // Stop trying, since we achieved what was specified.
      break;
    }

    if (drush_bootstrap_validate($phase_index)) {
      if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE)) {
        $result = drush_bootstrap($phase_index, $max_phase_index);
      }
    }
    else {
      $result = FALSE;
      break;
    }
  }

  return $result;
}

/**
 * Bootstrap to the highest level possible, without triggering any errors.
 *
 * @param int $max_phase_index
 *   (optional) Only attempt bootstrap to the specified level.
 *
 * @return int
 *   The maximum phase to which we bootstrapped.
 */
function drush_bootstrap_max($max_phase_index = FALSE) {
  $phases = _drush_bootstrap_phases(TRUE);
  if (!$max_phase_index) {
    $max_phase_index = count($phases);
  }

  // Try to bootstrap to the maximum possible level, without generating errors.
  foreach ($phases as $phase_index => $current_phase) {
    if ($phase_index > $max_phase_index) {
      // Stop trying, since we achieved what was specified.
      break;
    }

    if (drush_bootstrap_validate($phase_index)) {
      if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) {
        drush_bootstrap($phase_index, $max_phase_index);
      }
    }
    else {
      // drush_bootstrap_validate() only logs successful validations. For us,
      // knowing what failed can also be important.
      $previous = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
      drush_log(dt("Bootstrap phase !function() failed to validate; continuing at !current().", array('!function' => $current_phase, '!current' => $phases[$previous])), 'debug');
      break;
    }
  }

  return drush_get_context('DRUSH_BOOTSTRAP_PHASE');
}

/**
 * Bootstrap the specified site alias.  The site alias must
 * be a valid alias to a local site.
 *
 * @param $site_record
 *   The alias record for the given site alias.
 *   @see drush_sitealias_get_record().
 * @param $max_phase_index
 *   Only attempt bootstrap to the specified level.
 * @returns TRUE if attempted to bootstrap, or FALSE
 *   if no bootstrap attempt was made.
 */
function drush_bootstrap_max_to_sitealias($site_record, $max_phase_index = NULL) {
  if ((array_key_exists('root', $site_record) && !array_key_exists('remote-host', $site_record))) {
    drush_sitealias_set_alias_context($site_record);
    drush_bootstrap_max($max_phase_index);
    return TRUE;
  }
  return FALSE;
}

/**
 * Helper function to collect any errors that occur during the bootstrap process.
 * Always returns FALSE, for convenience.
 */
function drush_bootstrap_error($code, $message = null) {
  $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS');
  $errors[$code] = $message;
  drush_set_context('DRUSH_BOOTSTRAP_ERRORS', $errors);
  return FALSE;
}

function _drush_bootstrap_output_prepare() {
  // Note that as soon as we set the DRUSH_BACKEND context, we change
  // the behavior of drush_log().  It is therefore important that we
  // should not set this context until immediately before we call ob_start
  // (i.e., in this function).
  $backend = drush_set_context('DRUSH_BACKEND', drush_get_option('backend'));
  $quiet = drush_get_context('DRUSH_QUIET');

  if ($backend) {
    // Load options passed as a JSON encoded string through STDIN.
    $stdin_options = _drush_backend_get_stdin();
    if (is_array($stdin_options)) {
      drush_set_context('stdin', $stdin_options);
    }
    // Add an output buffer handler to collect output/pass through backend
    // packets. Using a chunksize of 2 ensures that each line is flushed
    // straight away.
    if ($quiet) {
      // Pass through of backend packets, discard regular output.
      ob_start('drush_backend_output_discard', 2);
    }
    else {
      // Collect output.
      ob_start('drush_backend_output_collect', 2);
    }
  }

  // In non-backend quiet mode we start buffering and discards it on command
  // completion.
  if ($quiet && !$backend) {
    ob_start();
  }
}

/**
 * Used by a Drush extension to request that its Composer autoload
 * files be loaded by Drush, if they have not already been.
 *
 * Usage:
 *
 * function mycommandfile_drush_init() {
 *   drush_autoload(__FILE__)
 * }
 *
 */
function drush_autoload($commandfile) {
  $already_added = commandfiles_cache()->add($commandfile);

  $dir = dirname($commandfile);
  $candidates = array("vendor/autoload.php", "../../../vendor/autoload.php");
  $drush_autoload_file = drush_get_context('DRUSH_VENDOR_PATH', '');

  foreach ($candidates as $candidate) {
    $autoload = $dir . '/' . $candidate;
    if (file_exists($autoload) && (realpath($autoload) != $drush_autoload_file)) {
      include $autoload;
    }
  }
}
<?php

/**
 * @file
 * Drush cache API
 *
 * Provides a cache API for drush core and commands, forked from Drupal 7.
 *
 * The default storage backend uses the plain text files to store serialized php
 * objects, which can be extended or replaced by setting the cache-default-class
 * option in drushrc.php.
 */

use Drush\Log\LogLevel;

/**
 * Indicates that the item should never be removed unless explicitly selected.
 *
 * The item may be removed using cache_clear_all() with a cache ID.
 */
define('DRUSH_CACHE_PERMANENT', 0);

/**
 * Indicates that the item should be removed at the next general cache wipe.
 */
define('DRUSH_CACHE_TEMPORARY', -1);

/**
 * Get the cache object for a cache bin.
 *
 * By default, this returns an instance of the \Drush\Cache\FileCache class.
 * Classes implementing \Drush\Cache\CacheInterface can register themselves
 * both as a default implementation and for specific bins.
 *
 * @see \Drush\Cache\CacheInterface
 *
 * @param string $bin
 *   The cache bin for which the cache object should be returned.
 *
 * @return \Drush\Cache\CacheInterface
 *   The cache object associated with the specified bin.
 */
function _drush_cache_get_object($bin) {
  static $cache_objects;

  if (!isset($cache_objects[$bin])) {
    $class = drush_get_option('cache-class-' . $bin, NULL);
    if (!isset($class)) {
      $class = drush_get_option('cache-default-class', '\Drush\Cache\JSONCache');
    }
    $cache_objects[$bin] = new $class($bin);
  }
  return $cache_objects[$bin];
}

/**
 * Return data from the persistent cache.
 *
 * Data may be stored as either plain text or as serialized data.
 * _drush_cache_get() will automatically return unserialized
 * objects and arrays.
 *
 * @param string $cid
 *   The cache ID of the data to retrieve.
 * @param string $bin
 *   The cache bin to store the data in.
 *
 * @return
 *   The cache or FALSE on failure.
 *
 */
function drush_cache_get($cid, $bin = 'default') {
  $ret = _drush_cache_get_object($bin)->get($cid);
  $mess = $ret ? "HIT" : "MISS";
  drush_log(dt("Cache !mess cid: !cid", array('!mess' => $mess, '!cid' => $cid)), LogLevel::DEBUG);
  return $ret;
}

/**
 * Return data from the persistent cache when given an array of cache IDs.
 *
 * @param array $cids
 *   An array of cache IDs for the data to retrieve. This is passed by
 *   reference, and will have the IDs successfully returned from cache removed.
 * @param string $bin
 *   The cache bin where the data is stored.
 *
 * @return
 *   An array of the items successfully returned from cache indexed by cid.
 */
function drush_cache_get_multiple(array &$cids, $bin = 'default') {
  return _drush_cache_get_object($bin)->getMultiple($cids);
}

/**
 * Store data in the persistent cache.
 *
 * @param string $cid
 *   The cache ID of the data to store.
 *
 * @param $data
 *   The data to store in the cache.
 * @param string $bin
 *   The cache bin to store the data in.
 * @param $expire
 *   One of the following values:
 *   - DRUSH_CACHE_PERMANENT: Indicates that the item should never be removed
 *     unless explicitly told to using drush_cache_clear_all() with a cache ID.
 *   - DRUSH_CACHE_TEMPORARY: Indicates that the item should be removed at
 *     the next general cache wipe.
 *   - A Unix timestamp: Indicates that the item should be kept at least until
 *     the given time, after which it behaves like DRUSH_CACHE_TEMPORARY.
 *
 * @return bool
 */
function drush_cache_set($cid, $data, $bin = 'default', $expire = DRUSH_CACHE_PERMANENT) {
  $ret = _drush_cache_get_object($bin)->set($cid, $data, $expire);
  if ($ret) drush_log(dt("Cache SET cid: !cid", array('!cid' => $cid)), LogLevel::DEBUG);
  return $ret;
}

/**
 * Expire data from the cache.
 *
 * If called without arguments, expirable entries will be cleared from all known
 * cache bins.
 *
 * @param string $cid
 *   If set, the cache ID to delete. Otherwise, all cache entries that can
 *   expire are deleted.
 * @param string $bin
 *   If set, the bin $bin to delete from. Mandatory
 *   argument if $cid is set.
 * @param bool $wildcard
 *   If $wildcard is TRUE, cache IDs starting with $cid are deleted in
 *   addition to the exact cache ID specified by $cid.  If $wildcard is
 *   TRUE and $cid is '*' then the entire bin $bin is emptied.
 */
function drush_cache_clear_all($cid = NULL, $bin = 'default', $wildcard = FALSE) {
  if (!isset($cid) && !isset($bin)) {
    foreach (drush_cache_get_bins() as $bin) {
      _drush_cache_get_object($bin)->clear();
    }
    return;
  }
  return _drush_cache_get_object($bin)->clear($cid, $wildcard);
}

/**
 * Check if a cache bin is empty.
 *
 * A cache bin is considered empty if it does not contain any valid data for any
 * cache ID.
 *
 * @param $bin
 *   The cache bin to check.
 *
 * @return
 *   TRUE if the cache bin specified is empty.
 */
function _drush_cache_is_empty($bin) {
  return _drush_cache_get_object($bin)->isEmpty();
}

/**
 * Return drush cache bins and any bins added by hook_drush_flush_caches().
 */
function drush_cache_get_bins() {
  $drush = array('default');
  return array_merge(drush_command_invoke_all('drush_flush_caches'), $drush);
}

/**
 * Create a cache id from a given prefix, contexts, and additional parameters.
 *
 * @param prefix
 *   A human readable cid prefix that will not be hashed.
 * @param contexts
 *   Array of drush contexts that will be used to build a unique hash.
 * @param params
 *   Array of any addition parameters to be hashed.
 *
 * @return
 *   A cache id string.
 */
function drush_get_cid($prefix, $contexts = array(), $params = array()) {
  $cid = array();

  foreach ($contexts as $context) {
    $c = drush_get_context($context);
    if (!empty($c)) {
      $cid[] = is_scalar($c) ? $c : serialize($c);
    }
  }

  foreach ($params as $param) {
    $cid[] = $param;
  }

  return DRUSH_VERSION . '-' . $prefix . '-' . md5(implode("", $cid));
}
<?php

use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;
use Consolidation\AnnotatedCommand\AnnotationData;
use Drush\Command\DrushInputAdapter;
use Drush\Command\DrushOutputAdapter;
use Consolidation\AnnotatedCommand\CommandData;

/**
 * @defgroup dispatching Command dispatching functions.
 * @{
 *
 * These functions handle command dispatching, and can
 * be used to programatically invoke drush commands in
 * different ways.
 */

/**
 * Invokes a Drush API call, including all hooks.
 *
 * Executes the specified command with the specified arguments on the currently
 * bootstrapped site using the current option contexts. Note that it will not
 * bootstrap any further than the current command has already bootstrapped;
 * therefore, you should only invoke commands that have the same (or lower)
 * bootstrap requirements.
 *
 * Commands execute with the same options that the user provided on the
 * commandline. If you need to invoke another Drush command with options you
 * specify, use drush_invoke_process() instead.
 *
 * @param string $command
 *   The command to invoke.
 * @param array $arguments
 *   An array of argument to pass into the command.
 *
 * @return mixed|false
 *   The return value from drush_dispatch() or FALSE on error.
 *
 * @see drush_invoke_process()
 */
function drush_invoke($command, $arguments = array()) {
  // Convert a standalone argument to a single-element array.
  if (!is_array($arguments)) {
    $arguments = array($arguments);
  }
  $commands = drush_get_commands();
  if (array_key_exists($command, $commands)) {
    $command = $commands[$command];
    // Drush overloads the 'arguments' element, which contains the help string
    // for the allowed arguments for the command when fetched, and is fixed up
    // by _drush_prepare_command() to contain the actual commandline arguments
    // during dispatching.
    $command['arguments'] = array();
    return drush_dispatch($command, $arguments);
  }
  else {
    return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!command' could not be found.", array('!command' => $command)));
  }
}

/**
 * Invoke a command in a new process, targeting the site specified by
 * the provided site alias record.
 *
 * @param array $site_alias_record
 *  The site record to execute the command on.  Use '@self' to run on the current site.
 * @param string $command_name
 *  The command to invoke.
 * @param array $commandline_args
 *  The arguments to pass to the command.
 * @param array $commandline_options
 *  The options (e.g. --select) to provide to the command.
 * @param mixed $backend_options
 *   TRUE - integrate errors
 *   FALSE - do not integrate errors
 *   array - @see drush_backend_invoke_concurrent
 *     There are also several options that _only_ work when set in
 *     this parameter.  They include:
 *      'invoke-multiple'
 *        If $site_alias_record represents a single site, then 'invoke-multiple'
 *        will cause the _same_ command with the _same_ arguments and options
 *        to be invoked concurrently (e.g. for running concurrent batch processes).
 *      'concurrency'
 *        Limits the number of concurrent processes that will run at the same time.
 *        Defaults to '4'.
 *      'override-simulated'
 *        Forces the command to run, even in 'simulated' mode. Useful for
 *        commands that do not change any state on the machine, e.g. to fetch
 *        database information for sql-sync via sql-conf.
 *      'interactive'
 *        Overrides the backend invoke process to run commands interactively.
 *      'fork'
 *        Overrides the backend invoke process to run non blocking commands in
 *        the background. Forks a new process by adding a '&' at the end of the
 *        command. The calling process does not receive any output from the child
 *        process. The fork option is used to spawn a process that outlives its
 *        parent.
 *
 * @return
 *   If the command could not be completed successfully, FALSE.
 *   If the command was completed, this will return an associative
 *   array containing the results of the API call.
 *   @see drush_backend_get_result()
 *
 * Do not change the signature of this function!  drush_invoke_process
 * is one of the key Drush APIs.  See http://drupal.org/node/1152908
 */
function drush_invoke_process($site_alias_record, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = TRUE) {
  if (is_array($site_alias_record) && array_key_exists('site-list', $site_alias_record)) {
    list($site_alias_records, $not_found) = drush_sitealias_resolve_sitespecs($site_alias_record['site-list']);
    if (!empty($not_found)) {
      drush_log(dt("Not found: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING);
      return FALSE;
    }
    $site_alias_records = drush_sitealias_simplify_names($site_alias_records);
    foreach ($site_alias_records as $alias_name => $alias_record) {
      $invocations[] = array(
        'site' => $alias_record,
        'command' => $command_name,
        'args' => $commandline_args,
      );
    }
  }
  else {
    $invocations[] = array(
      'site' => $site_alias_record,
      'command' => $command_name,
      'args' => $commandline_args);
    $invoke_multiple = drush_get_option_override($backend_options, 'invoke-multiple', 0);
    if ($invoke_multiple) {
      $invocations = array_fill(0, $invoke_multiple, $invocations[0]);
    }
  }
  return drush_backend_invoke_concurrent($invocations, $commandline_options, $backend_options);
}

/**
 * Given a command record, dispatch it as if it were
 * the original command.  Executes in the currently
 * bootstrapped site using the current option contexts.
 * Note that drush_dispatch will not bootstrap any further than the
 * current command has already bootstrapped; therefore, you should only invoke
 * commands that have the same (or lower) bootstrap requirements.
 *
 * @param command
 *   A full $command such as returned by drush_get_commands(),
 *   or a string containing the name of the command record from
 *   drush_get_commands() to call.
 * @param arguments
 *   An array of argument values.
 *
 * @see drush_topic_docs_topic().
 */
function drush_dispatch($command, $arguments = array()) {
  drush_set_command($command);
  $return = FALSE;

  if ($command) {
    // Add arguments, if this has not already been done.
    // (If the command was fetched from drush_parse_command,
    // then you cannot provide arguments to drush_dispatch.)
    if (empty($command['arguments'])) {
      _drush_prepare_command($command, $arguments);
    }

    // Merge in the options added by hooks.  We need this
    // for validation, but this $command is just going to
    // get thrown away, so we'll have to do this again later.
    annotationcommand_adapter_add_hook_options($command);

    // Add command-specific options, if applicable.
    drush_command_default_options($command);

    // Test to see if any of the options in the 'cli' context
    // are not represented in the command structure.
    if ((_drush_verify_cli_options($command) === FALSE) || (_drush_verify_cli_arguments($command) === FALSE)) {
      return FALSE;
    }

    // Give command files an opportunity to alter the command record
    drush_command_invoke_all_ref('drush_command_alter', $command);

    // Include and validate command engines.
    if (drush_load_command_engines($command) === FALSE) {
      return FALSE;
    }

    // Do tilde expansion immediately prior to execution,
    // so that tildes are passed through unchanged for
    // remote commands and other redispatches.
    drush_preflight_tilde_expansion($command);

    // Get the arguments for this command. Add the options
    // on the end if this is that kind of command.
    $args = $command['arguments'];

    // Call the callback function of the active command.
    $return = call_user_func_array($command['callback'], $args);
  }

  // Add a final log entry, just so a timestamp appears.
  drush_log(dt('Command dispatch complete'), LogLevel::NOTICE);

  return $return;
}

/**
 * Entry point for commands into the drush_invoke() API
 *
 * If a command does not have a callback specified, this function will be called.
 *
 * This function will trigger $hook_drush_init, then if no errors occur,
 * it will call drush_invoke() with the command that was dispatch.
 *
 * If no errors have occured, it will run $hook_drush_exit.
 */
function drush_command() {
  $args = func_get_args();
  $command = drush_get_command();
  foreach (drush_command_implements("drush_init") as $name) {
    $func = $name . '_drush_init';
    if (drush_get_option('show-invoke')) {
      drush_log(dt("Calling global init hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP);
    }
    call_user_func_array($func, $args);
    _drush_log_drupal_messages();
  }

  if (!drush_get_error()) {
    $result = _drush_invoke_hooks($command, $args);
  }

  if (!drush_get_error()) {
    foreach (drush_command_implements('drush_exit') as $name) {
      $func = $name . '_drush_exit';
      if (drush_get_option('show-invoke')) {
        drush_log(dt("Calling global exit hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP);
      }
      call_user_func_array($func, $args);
      _drush_log_drupal_messages();
    }
  }
}

/**
 * Invoke Drush API calls, including all hooks.
 *
 * This is an internal function; it is called from drush_dispatch via
 * drush_command, but only if the command does not specify a 'callback'
 * function.  If a callback function is specified, it will be called
 * instead of drush_command + _drush_invoke_hooks.
 *
 * Executes the specified command with the specified arguments on the
 * currently bootstrapped site using the current option contexts.
 * Note that _drush_invoke_hooks will not bootstrap any further than the
 * current command has already bootstrapped; therefore, you should only invoke
 * commands that have the same (or lower) bootstrap requirements.
 *
 * Call the correct hook for all the modules that implement it.
 * Additionally, the ability to rollback when an error has been encountered is also provided.
 * If at any point during execution, the drush_get_error() function returns anything but 0,
 * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
 * in reverse order from how they were executed.  Rollbacks are also triggered any
 * time a hook function returns FALSE.
 *
 * This function will also trigger pre_$hook and post_$hook variants of the hook
 * and its rollbacks automatically.
 *
 * HOW DRUSH HOOK FUNCTIONS ARE NAMED:
 *
 * The name of the hook is composed from the name of the command and the name of
 * the command file that the command definition is declared in.  The general
 * form for the hook filename is:
 *
 *      drush_COMMANDFILE_COMMANDNAME
 *
 * In many cases, drush commands that are functionally part of a common collection
 * of similar commands will all be declared in the same file, and every command
 * defined in that file will start with the same command prefix.  For example, the
 * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
 * In the case of "pm-enable", the command file is "pm", and and command name is
 * "pm-enable".  When the command name starts with the same sequence of characters
 * as the command file, then the repeated sequence is dropped; thus, the command
 * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
 *
 * There is also a special Drupal-version-specific naming convention that may
 * be used.  To hide a commandfile from all versions of Drupal except for the
 * specific one named, add a ".dVERSION" after the command prefix.  For example,
 * the file "views.d8.drush.inc" defines a "views" commandfile that will only
 * load with Drupal 8.  This feature is not necessary and should not be used
 * in contrib modules (any extension with a ".module" file), since these modules
 * are already version-specific.
 *
 * @param command
 *   The drush command to execute.
 * @param args
 *   An array of arguments to the command OR a single non-array argument.
 * @return
 *   The return value will be passed along to the caller if --backend option is
 *   present. A boolean FALSE indicates failure and rollback will be intitated.
 *
 * This function should not be called directly.
 * @see drush_invoke() and @see drush_invoke_process()
 */
function _drush_invoke_hooks($command, $args) {
  $return = null;
  // If someone passed a standalone arg, convert it to a single-element array
  if (!is_array($args)) {
    $args = array($args);
  }
  // Include the external command file used by this command, if there is one.
  drush_command_include($command['command-hook']);
  // Generate the base name for the hook by converting all
  // dashes in the command name to underscores.
  $hook = str_replace("-", "_", $command['command-hook']);

  // Call the hook init function, if it exists.
  // If a command needs to bootstrap, it is advisable
  // to do so in _init; otherwise, new commandfiles
  // will miss out on participating in any stage that
  // has passed or started at the time it was discovered.
  $func = 'drush_' . $hook . '_init';
  if (function_exists($func)) {
    drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), LogLevel::BOOTSTRAP);
    call_user_func_array($func, $args);
    _drush_log_drupal_messages();
    if (drush_get_error()) {
      drush_log(dt('The command @command could not be initialized.', array('@command' => $command['command-hook'])), LogLevel::ERROR);
      return FALSE;
    }
  }

  // We will adapt and call as many of the annotated command hooks as we can.
  // The following command hooks are not supported in Drush 8.x:
  //   - Command Event: not called (requires CommandEvent object)
  //   - Option: Equivalent functionality supported in annotationcommand_adapter.inc
  //   - Interact: not called (We don't use SymfonyStyle in 8.x at the moment)
  //   - Status: not called - probably not needed?
  //   - Extract not called - probably not needed?
  // The hooks that are called include:
  //   - Pre-initialize, initialize and post-initialize
  //   - Pre-validate and validate
  //   - Pre-command, command and post-command
  //   - Pre-process, process and post-process
  //   - Pre-alter, alter and post-alter

  $names = annotationcommand_adapter_command_names($command);
  // Merge in the options added by hooks (again)
  annotationcommand_adapter_add_hook_options($command);
  $annotationData = $command['annotations'];

  $input = new DrushInputAdapter($args, annotationcommand_adapter_get_options($command), $command['command']);
  $output = new DrushOutputAdapter();
  $commandData = new CommandData(
    $annotationData,
    $input,
    $output,
    false,
    false
  );

  annotationcommand_adapter_call_initialize($names, $commandData);

  $rollback = FALSE;
  $completed = array();
  $available_rollbacks = array();
  $all_available_hooks = array();

  // Iterate through the different hook variations
  $variations = array(
    'pre_validate' => $hook . "_pre_validate",
    'validate' => $hook . "_validate",
    'pre_command' => "pre_$hook",
    'command' => $hook,
    'post_command' => "post_$hook"
  );
  foreach ($variations as $hook_phase => $var_hook) {

    $adapterHookFunction = 'annotationcommand_adapter_call_hook_' . $hook_phase;
    $adapterHookFunction($names, $commandData, $return);

    // Get the list of command files.
    // We re-fetch the list every time through
    // the loop in case one of the hook function
    // does something that will add additional
    // commandfiles to the list (i.e. bootstrapping
    // to a higher phase will do this).
    $list = drush_commandfile_list();

    // Make a list of function callbacks to call.  If
    // there is a 'primary function' mentioned, make sure
    // that it appears first in the list, but only if
    // we are running the main hook ("$hook").  After that,
    // make sure that any callback associated with this commandfile
    // executes before any other hooks defined in any other
    // commandfiles.
    $callback_list = array();
    if (($var_hook == $hook) && ($command['primary function'])) {
      $callback_list[$command['primary function']] = $list[$command['commandfile']];
    }
    else {
      $primary_func = ($command['commandfile'] . "_" == substr($var_hook . "_",0,strlen($command['commandfile']) + 1)) ? sprintf("drush_%s", $var_hook) : sprintf("drush_%s_%s", $command['commandfile'], $var_hook);
      $callback_list[$primary_func] = $list[$command['commandfile']];
    }
    // We've got the callback for the primary function in the
    // callback list; now add all of the other callback functions.
    unset($list[$command['commandfile']]);
    foreach ($list as $commandfile => $filename) {
      $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
      $callback_list[$func] = $filename;
    }
    // Run all of the functions available for this variation
    $accumulated_result = NULL;
    foreach ($callback_list as $func => $filename) {
      if (function_exists($func)) {
        $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
        $available_rollbacks[] = $func . '_rollback';
        $completed[] = $func;
        drush_log(dt("Calling hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
        try {
          $result = call_user_func_array($func, $args);
          drush_log(dt("Returned from hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
        }
        catch (Exception $e) {
          drush_set_error('DRUSH_EXECUTION_EXCEPTION', (string) $e);
        }
        // If there is an error, break out of the foreach
        // $variations and foreach $callback_list
        if (drush_get_error() || ($result === FALSE)) {
          $rollback = TRUE;
          break 2;
        }
        // If result values are arrays, then combine them all together.
        // Later results overwrite earlier results.
        if (isset($result) && is_array($accumulated_result) && is_array($result)) {
          $accumulated_result = array_merge($accumulated_result, $result);
        }
        else {
          $accumulated_result = $result;
        }
        _drush_log_drupal_messages();
      }
      else {
        $all_available_hooks[] = $func;
      }
    }
    // Process the result value from the 'main' callback hook only.
    if ($var_hook == $hook) {
      $return = $accumulated_result;
      if (isset($return)) {
        annotationcommand_adapter_call_hook_process_and_alter($names, $commandData, $return);
        drush_handle_command_output($command, $return);
      }
    }
  }

  // If no hook functions were found, print a warning.
  if (empty($completed)) {
    $default_command_hook = sprintf("drush_%s_%s", $command['commandfile'], $hook);
    if (($command['commandfile'] . "_" == substr($hook . "_",0,strlen($command['commandfile'])+ 1))) {
      $default_command_hook = sprintf("drush_%s", $hook);
    }
    $dt_args = array(
      '!command' => $command['command-hook'],
      '!default_func' => $default_command_hook,
    );
    $message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks.";
    $return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args));
  }
  if (drush_get_option('show-invoke')) {
    // We show all available hooks up to and including the one that failed (or all, if there were no failures)
    drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command['command-hook'], '!available' => "\n" . implode("\n", $all_available_hooks))), LogLevel::OK);
  }
  if (drush_get_option('show-invoke') && !empty($available_rollbacks)) {
    drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command['command-hook'], '!rollback' => "\n" . implode("\n", $available_rollbacks))), LogLevel::OK);
  }

  // Something went wrong, we need to undo.
  if ($rollback) {
    if (drush_get_option('confirm-rollback', FALSE)) {
      // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process.
      drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
      drush_set_context('DRUSH_NEGATIVE', FALSE);
      $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)'));
    }

    if ($rollback) {
      foreach (array_reverse($completed) as $func) {
        $rb_func = $func . '_rollback';
        if (function_exists($rb_func)) {
          call_user_func_array($rb_func, $args);
          _drush_log_drupal_messages();
          drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), LogLevel::DEBUG);
        }
      }
    }
    $return = FALSE;
  }

  if (isset($return)) {
    return $return;
  }
}

/**
 * Convert the structured output array provided from the Drush
 * command into formatted output.  Output is only printed for commands
 * that define 'default-format' &/or 'default-pipe-format'; all
 * other commands are expected to do their own output.
 */
function drush_handle_command_output($command, $structured_output) {
  // If the hook already called drush_backend_set_result,
  // then return that value. If it did not, then the return
  // value from the hook will be the value returned from
  // this routine.
  $return = drush_backend_get_result();
  if (empty($return)) {
    drush_backend_set_result($structured_output);
  }
  // We skip empty strings and empty arrays, but note that 'empty'
  // returns TRUE for the integer value '0', but we do want to print that.
  // Only handle output here if the command defined an output format
  // engine.  If no engine was declared, then we presume that the command
  // handled its own output.
  if ((!empty($structured_output) || ($structured_output === 0))) {
    // If the command specifies a default pipe format and
    // returned a result, then output the formatted output when
    // in --pipe mode.
    $formatter = drush_get_outputformat();
    if (!$formatter && is_string($structured_output)) {
      $formatter = drush_load_engine('outputformat', 'string');
    }
    if ($formatter) {
      if ($formatter === TRUE) {
        return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
      }
      if ((!empty($command['engines']['outputformat'])) && (!in_array($formatter->engine, $command['engines']['outputformat']['usable']))) {
        return $formatter->format_error(dt("The command '!command' does not produce output in a structure usable by this output format.", array('!command' => $command['command'])));
      }
      // Add any user-specified options to the metadata passed to the formatter.
      $metadata = array();
      $metadata['strict'] = drush_get_option('strict', FALSE);
      if (isset($formatter->engine_config['options'])) {
        $machine_parsable = $formatter->engine_config['engine-info']['machine-parsable'];
        if (drush_get_option('full', FALSE)) {
          if (isset($formatter->engine_config['fields-full'])) {
            $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-full'];
          }
          else {
            $formatter->engine_config['fields-default'] = array_keys($formatter->engine_config['field-labels']);
          }
        }
        elseif ((drush_get_context('DRUSH_PIPE') || $machine_parsable) && isset($formatter->engine_config['fields-pipe'])) {
          $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-pipe'];
        }

        // Determine the --format, and options relevant for that format.
        foreach ($formatter->engine_config['options'] as $option => $option_info) {
          $default_value = isset($formatter->engine_config[$option . '-default']) ? $formatter->engine_config[$option . '-default'] : FALSE;
          if (($default_value === FALSE) && array_key_exists('default', $option_info)) {
            $default_value = $option_info['default'];
          }
          if (isset($option_info['list'])) {
            $user_specified_value = drush_get_option_list($option, $default_value);
          }
          else {
            $user_specified_value = drush_get_option($option, $default_value);
          }
          if ($user_specified_value !== FALSE) {
            if (array_key_exists('key', $option_info)) {
              $option = $option_info['key'];
            }
            $metadata[$option] =$user_specified_value;
          }
        }
      }
      if (isset($metadata['fields']) && !empty($metadata['fields'])) {
        if (isset($formatter->engine_config['field-labels'])) {
          $formatter->engine_config['field-labels'] = drush_select_fields($formatter->engine_config['field-labels'], $metadata['fields'], $metadata['strict']);
        }
      }
      $output = $formatter->process($structured_output, $metadata);
      if (drush_get_context('DRUSH_PIPE')) {
        drush_print_pipe($output);
      }
      else {
        drush_print($output);
      }
    }
  }
}

/**
 * Fail with an error if the user specified options on the
 * command line that are not documented in the current command
 * record. Also verify that required options are present.
 */
function _drush_verify_cli_options($command) {

  // Start out with just the options in the current command record.
  $options = _drush_get_command_options($command);
  // Skip all tests if the command is marked to allow anything.
  // Also skip backend commands, which may have options on the commandline
  // that were inherited from the calling command.
  if (($command['allow-additional-options'] === TRUE)) {
    return TRUE;
  }
  // If 'allow-additional-options' contains a list of command names,
  // then union together all of the options from all of the commands.
  if (is_array($command['allow-additional-options'])) {
    $implemented = drush_get_commands();
    foreach ($command['allow-additional-options'] as $subcommand_name) {
      if (array_key_exists($subcommand_name, $implemented)) {
        $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name]));
      }
    }
  }
  // Also add in global options
  $options = array_merge($options, drush_get_global_options());

  // Add a placeholder option so that backend requests originating from prior versions of Drush are valid.
  $options += array('invoke' => '');

  // Now we will figure out which options in the cli context
  // are not represented in our options list.
  $cli_options = array_keys(drush_get_context('cli'));
  $allowed_options = _drush_flatten_options($options);
  $allowed_options = drush_append_negation_options($allowed_options);
  $disallowed_options = array_diff($cli_options, $allowed_options);
  if (!empty($disallowed_options)) {
    $unknown = count($disallowed_options) > 1 ? dt('Unknown options') : dt('Unknown option');
    if (drush_get_option('strict', TRUE)) {
      $msg = dt("@unknown: --@options.  See `drush help @command` for available options. To suppress this error, add the option --strict=0.", array('@unknown' => $unknown, '@options' => implode(', --', $disallowed_options), '@command' => $command['command']));
      return drush_set_error('DRUSH_UNKNOWN_OPTION', $msg);
    }
  }

  // Next check to see if all required options were specified,
  // and if all specified options with required values have values.
  $missing_required_options = array();
  $options_missing_required_values = array();
  foreach ($command['options'] as $option_name => $option) {
    if (is_array($option) && !empty($option['required']) && drush_get_option($option_name, NULL) === NULL) {
      $missing_required_options[] = $option_name;
    }
    // Note that drush_get_option() will return TRUE if an option
    // was specified without a value (--option), as opposed to
    // the string "1" is --option=1 was used.
    elseif (is_array($option) && !empty($option['value']) && ($option['value'] == 'required') && drush_get_option($option_name, NULL) === TRUE) {
      $options_missing_required_values[] = $option_name;
    }
  }
  if (!empty($missing_required_options) || !empty($options_missing_required_values)) {
    $missing_message = '';
    if (!empty($missing_required_options)) {
      $missing = count($missing_required_options) > 1 ? dt('Missing required options') : dt('Missing required option');
      $missing_message = dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $missing_required_options)));
    }
    if (!empty($options_missing_required_values)) {
      if (!empty($missing_message)) {
        $missing_message .= "  ";
      }
      $missing = count($options_missing_required_values) > 1 ? dt('Options used without providing required values') : dt('Option used without a value where one was required');
      $missing_message .= dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $options_missing_required_values)));
    }
    return drush_set_error(dt("!message  See `drush help @command` for information on usage.", array('!message' => $missing_message, '@command' => $command['command'])));
  }
  return TRUE;
}

function drush_append_negation_options($allowed_options) {
  $new_allowed = $allowed_options;
  foreach ($allowed_options as $option) {
    $new_allowed[] = 'no-' . $option;
  }
  return $new_allowed;
}

function _drush_verify_cli_arguments($command) {
  // Check to see if all of the required arguments
  // are specified.
  if ($command['required-arguments']) {
    $required_arg_count = $command['required-arguments'];
    if ($required_arg_count === TRUE) {
      $required_arg_count = count($command['argument-description']);
    }

    if (count($command['arguments']) < $required_arg_count) {
      $missing = $required_arg_count > 1 ? dt('Missing required arguments') : dt('Missing required argument');
      $required = array_slice(array_keys($command['argument-description']), 0, $required_arg_count);

      return drush_set_error(dt("@missing: @required.  See `drush help @command` for information on usage.", array(
        '@missing' => $missing,
        '@required' => implode(", ", $required),
        '@command' => $command['command'],
      )));
    }
  }
  return TRUE;
}

/**
 * Return the list of all of the options for the given
 * command record by merging the 'options' and 'sub-options'
 * records.
 */
function _drush_get_command_options($command) {
  drush_command_invoke_all_ref('drush_help_alter', $command);
  $options = $command['options'];
  foreach ($command['sub-options'] as $group => $suboptions) {
    $options = array_merge($options, $suboptions);
  }
  return $options;
}

/**
 * Return the list of all of the options for the given
 * command record including options provided by engines and additional-options.
 */
function drush_get_command_options_extended($command) {
  drush_merge_engine_data($command);

  // Start out with just the options in the current command record.
  $options = _drush_get_command_options($command);
  // If 'allow-additional-options' contains a list of command names,
  // then union together all of the options from all of the commands.
  if (is_array($command['allow-additional-options'])) {
    $implemented = drush_get_commands();
    foreach ($command['allow-additional-options'] as $subcommand_name) {
      if (array_key_exists($subcommand_name, $implemented)) {
        $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name]));
      }
    }
  }
  return $options;
}

/**
 * Return the array keys of $options, plus any 'short-form'
 * representations that may appear in the option's value.
 */
function _drush_flatten_options($options) {
  $flattened_options = array();

  foreach($options as $key => $value) {
    // engine sections start with 'package-handler=git_drupalorg',
    // or something similar.  Get rid of everything from the = onward.
    if (($eq_pos = strpos($key, '=')) !== FALSE) {
      $key = substr($key, 0, $eq_pos);
    }
    $flattened_options[] = $key;
    if (is_array($value)) {
      if (array_key_exists('short-form', $value)) {
        $flattened_options[] = $value['short-form'];
      }
    }
  }
  return $flattened_options;
}

/**
 * Get the options that were passed to the current command.
 *
 * This function returns an array that contains all of the options
 * that are appropriate for forwarding along to drush_invoke_process.
 *
 * @return
 *   An associative array of option key => value pairs.
 */
function drush_redispatch_get_options() {
  $options = array();

  // Add in command-specific and alias options, but for global options only.
  $options_soup = drush_get_context('specific') + drush_get_context('alias');
  $global_option_list = drush_get_global_options(FALSE);
  foreach ($options_soup as $key => $value) {
    if (array_key_exists($key, $global_option_list)) {
      $options[$key] = $value;
    }
  }

  // Local php settings should not override sitealias settings.
  $cli_context = drush_get_context('cli');
  unset($cli_context['php'], $cli_context['php-options']);
  // Pass along CLI parameters, as higher priority.
  $options = $cli_context + $options;

  $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys()));
  unset($options['command-specific']);
  unset($options['path-aliases']);
  // If we can parse the current command, then examine all contexts
  // in order for any option that is directly related to the current command
  $command = drush_parse_command();
  if (is_array($command)) {
    foreach (drush_get_command_options_extended($command) as $key => $value) {
      $value = drush_get_option($key);
      if (isset($value)) {
        $options[$key] = $value;
      }
    }
  }
  // If --bootstrap-to-first-arg is specified, do not
  // pass it along to remote commands.
  unset($options['bootstrap-to-first-arg']);

  return $options;
}

/**
 * @} End of "defgroup dispatching".
 */

/**
 * @file
 * The drush command engine.
 *
 * Since drush can be invoked independently of a proper Drupal
 * installation and commands may operate across sites, a distinct
 * command engine is needed.
 *
 * It mimics the Drupal module engine in order to economize on
 * concepts and to make developing commands as familiar as possible
 * to traditional Drupal module developers.
 */

/**
 * Parse console arguments.
 */
function drush_parse_args() {
  $args = drush_get_context('argv');
  $command_args = NULL;
  $global_options = array();
  $target_alias_name = NULL;
  // It would be nice if commandfiles could somehow extend this list,
  // but it is not possible. We need to parse args before we find commandfiles,
  // because the specified options may affect how commandfiles are located.
  // Therefore, commandfiles are loaded too late to affect arg parsing.
  // There are only a limited number of short options anyway; drush reserves
  // all for use by drush core.
  static $arg_opts = array('c', 'u', 'r', 'l', 'i');

  // Check to see if we were executed via a "#!/usr/bin/env drush" script
  drush_adjust_args_if_shebang_script($args);

  // Now process the command line arguments.  We will divide them
  // into options (starting with a '-') and arguments.
  $arguments = $options = array();

  for ($i = 1; $i < count($args); $i++) {
    $opt = $args[$i];
    // We set $command_args to NULL until the first argument that is not
    // an alias is found (the command); we put everything that follows
    // into $command_args.
    if (is_array($command_args)) {
      $command_args[] = $opt;
    }
    // Is the arg an option (starting with '-')?
    if (!empty($opt) && $opt{0} == "-" && strlen($opt) != 1) {
      // Do we have multiple options behind one '-'?
      if (strlen($opt) > 2 && $opt{1} != "-") {
        // Each char becomes a key of its own.
        for ($j = 1; $j < strlen($opt); $j++) {
          $options[substr($opt, $j, 1)] = TRUE;
        }
      }
      // Do we have a longopt (starting with '--')?
      elseif ($opt{1} == "-") {
        if ($pos = strpos($opt, '=')) {
          $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
        }
        else {
          $options[substr($opt, 2)] = TRUE;
        }
      }
      else {
        $opt = substr($opt, 1);
        // Check if the current opt is in $arg_opts (= has to be followed by an argument).
        if ((in_array($opt, $arg_opts))) {
          // Raising errors for missing option values should be handled by the
          // bootstrap or specific command, so we no longer do this here.
          $options[$opt] = $args[$i + 1];
          $i++;
        }
        else {
          $options[$opt] = TRUE;
        }
      }
    }
    // If it's not an option, it's a command.
    else {
      $arguments[] = $opt;
      // Once we find the first argument, record the command args and global options
      if (!is_array($command_args)) {
        // Remember whether we set $target_alias_name on a previous iteration,
        // then record the $target_alias_name iff this arguement references a valid site alias.
        $already_set_target = is_string($target_alias_name);
        if (!$already_set_target && drush_sitealias_valid_alias_format($opt)) {
          $target_alias_name = $opt;
        }
        // If an alias record was set on a previous iteration, then this
        // argument must be the command name.  If we set the target alias
        // record on this iteration, then this is not the command name.
        // If we've found the command name, then save $options in $global_options
        // (all options that came before the command name), and initialize
        // $command_args to an array so that we will begin storing all args
        // and options that follow the command name in $command_args.
        if ($already_set_target || (!is_string($target_alias_name))) {
          $command_args = array();
          $global_options = $options;
        }
      }
    }
  }
  // If no arguments are specified, then the command will
  // be either 'help' or 'version' (the latter if --version is specified)
  // @todo: it would be handy if one could do `drush @remote st --help` and
  // have that show help for st. Today, that shows --help for help command!
  if (!count($arguments)) {
    if (array_key_exists('version', $options)) {
      $arguments = array('version');
    }
    else {
      $arguments = array('help');
    }
  }
  if (is_array($command_args)) {
    drush_set_context('DRUSH_COMMAND_ARGS', $command_args);
  }
  drush_set_context('DRUSH_GLOBAL_CLI_OPTIONS', $global_options);

  // Handle the "@shift" alias, if present
  drush_process_bootstrap_to_first_arg($arguments);

  drush_set_arguments($arguments);
  drush_set_config_special_contexts($options);
  drush_set_context('cli', $options);
  return $arguments;
}

/**
 * Pop an argument off of drush's argument list
 */
function drush_shift() {
  $arguments = drush_get_arguments();
  $result = NULL;
  if (!empty($arguments)) {
    // The php-script command uses the DRUSH_SHIFT_SKIP
    // context to cause drush_shift to skip the 'php-script'
    // command and the script path argument when it is
    // called from the user script.
    $skip_count = drush_get_context('DRUSH_SHIFT_SKIP');
    if (is_numeric($skip_count)) {
      for ($i = 0; $i < $skip_count; $i++) {
        array_shift($arguments);
      }
      $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0);
    }
    $result = array_shift($arguments);
    drush_set_arguments($arguments);
  }
  return $result;
}

/**
 * Special checking for "shebang" script handling.
 *
 * If there is a file 'script.php' that begins like so:
 *   #!/path/to/drush
 * Then $args will be:
 *   /path/to/drush /path/to/script userArg1 userArg2 ...
 * If it instead starts like this:
 *   #!/path/to/drush --flag php-script
 * Then $args will be:
 *   /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ...
 * (Note that execve does not split the parameters from
 * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29)
 * When drush is called via one of the "shebang" lines above,
 * the first or second parameter will be the full path
 * to the "shebang" script file -- and if the path to the
 * script is in the second position, then we will expect that
 * the argument in the first position must begin with a
 * '@' (alias) or '-' (flag).  Under ordinary circumstances,
 * we do not expect that the drush command must come before
 * any argument that is the full path to a file.  We use
 * this assumption to detect "shebang" script execution.
 */
function drush_adjust_args_if_shebang_script(&$args) {
  if (drush_has_bash()) {
    // The drush.launcher script may add --php or --php-options at the
    // head of the argument list; skip past those.
    $base_arg_number = 1;
    while (substr($args[$base_arg_number], 0, 5) == '--php') {
      ++$base_arg_number;
    }
    if (_drush_is_drush_shebang_script($args[$base_arg_number])) {
      // If $args[1] is a drush "shebang" script, we will insert
      // the option "--bootstrap-to-first-arg" and the command
      // "php-script" at the beginning of  @args, so the command
      // line args become:
      //   /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ...
      drush_set_option('bootstrap-to-first-arg', TRUE);
      array_splice($args, $base_arg_number, 0, array('php-script'));
      drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
    }
    elseif (((strpos($args[$base_arg_number], ' ') !== FALSE) || (!ctype_alnum($args[$base_arg_number][0]))) && (_drush_is_drush_shebang_script($args[$base_arg_number + 1]))) {
      // If $args[2] is a drush "shebang" script, we will insert
      // the space-exploded $arg[1] in place of $arg[1], so the
      // command line args become:
      //   /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ...
      // If none of the script arguments look like a drush command,
      // then we will insert "php-script" as the default command to
      // execute.
      $script_args = explode(' ', $args[$base_arg_number]);
      $has_command = FALSE;
      foreach ($script_args as $script_arg) {
        if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) {
          $has_command = TRUE;
        }
      }
      if (!$has_command) {
        $script_args[] = 'php-script';
      }
      array_splice($args, 1, $base_arg_number, $script_args);
      drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
    }
  }
}

/**
 * Process the --bootstrap-to-first-arg option, if it is present.
 *
 * This option checks to see if the first user-provided argument is an alias
 * or site specification; if it is, it will be shifted into the first argument
 * position, where it will specify the site to bootstrap. The result of this
 * is that if your shebang line looks like this:
 *
 * #!/path/to/drush --bootstrap-to-first-arg php-script
 *
 * Then when you run that script, you can optionally provide an alias such
 * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1
 * scriptarg2). Since this is the behavior that one would usually want,
 * it is default behavior for a canonical script. That is, a script
 * with a simple shebang line, like so:
 *
 * #!/path/to/drush
 *
 * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore
 * behave exactly like the first example. To write a script that does not
 * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly
 * included, like so:
 *
 * #!/path/to/drush php-script
 */
function drush_process_bootstrap_to_first_arg(&$arguments) {
  if (drush_get_option('bootstrap-to-first-arg', FALSE)) {
    $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE);
    if (count($arguments) >= $shift_alias_pos) {
      $shifted_alias = $arguments[$shift_alias_pos];
      $alias_record = drush_sitealias_get_record($shifted_alias);
      if (!empty($alias_record)) {
        // Move the alias we shifted from its current position
        // in the argument list to the front of the list
        array_splice($arguments, $shift_alias_pos, 1);
        array_unshift($arguments, $shifted_alias);
      }
    }
  }
}

/**
 * Get a list of all implemented commands.
 * This invokes hook_drush_command().
 *
 * @return
 *   Associative array of currently active command descriptors.
 *
 */
function drush_get_commands($reset = FALSE) {
  static $commands = array();

  if ($reset) {
    $commands = array();
    return;
  }
  elseif ($commands) {
    return $commands;
  }

  $list = drush_commandfile_list();
  foreach ($list as $commandfile => $path) {
    if (drush_command_hook($commandfile, 'drush_command')) {
      $function = $commandfile . '_drush_command';
      $result = $function();
      foreach ((array)$result as $key => $command) {
        // Add some defaults and normalize the command descriptor.
        $command += drush_command_defaults($key, $commandfile, $path);

        // Add engine data.
        drush_merge_engine_data($command);

        // Translate command.
        drush_command_translate($command);

        // If the command callback is not 'drush_command', then
        // copy the callback function to an alternate element
        // of the command array that will be called when Drush
        // calls the command function hooks.  Then, set the
        // callback to drush_command so that the function hooks
        // will be called.
        if (($command['callback'] != 'drush_command') && $command['invoke hooks']) {
          $command['primary function'] = $command['callback'];
          $command['callback'] = 'drush_command';
        }

        $commands[$key] = $command;
      }
    }
  }
  $commands = array_merge($commands, annotationcommand_adapter_commands());
  foreach ($commands as $command) {
    // For every alias, make a copy of the command and store it in the command list
    // using the alias as a key
    if (isset($command['aliases']) && count($command['aliases'])) {
      foreach ($command['aliases'] as $alias) {
        $commands[$alias] = $command;
        $commands[$alias]['is_alias'] = TRUE;
      }
    }
  }
  return $commands;
}

/**
 * Organize commands into categories. Used by help listing and core-cli.
 *
 * @param array $commands
 *   A commands array as per drush_get_commands().
 *
 * @return array $command_categories
 *   A categorized associative array of commands.
 */
function drush_commands_categorize($commands) {
  $command_categories = array();
  $category_map = array();
  foreach ($commands as $key => $candidate) {
    if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) {
      $category = $candidate['category'];
      // If we have decided to remap a category, remap every command
      if (array_key_exists($category, $category_map)) {
        $category = $category_map[$category];
      }
      if (!array_key_exists($category, $command_categories)) {
        $title = drush_command_invoke_all('drush_help', "meta:$category:title");
        $alternate_title = '';
        if (!$title) {
          // If there is no title, then check to see if the
          // command file is stored in a folder with the same
          // name as some other command file (e.g. 'core') that
          // defines a title.
          $alternate = basename($candidate['path']);
          $alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title");
        }
        if (!empty($alternate_title)) {
          $category_map[$category] = $alternate;
          $category = $alternate;
          $title = $alternate_title;
        }
        $command_categories[$category]['title'] = empty($title) ? '' : $title[0];
        $summary = drush_command_invoke_all('drush_help', "meta:$category:summary");
        if ($summary) {
          $command_categories[$category]['summary'] = $summary[0];
        }
      }
      $candidate['category'] = $category;
      $command_categories[$category]['commands'][$key] = $candidate;
    }
  }

  // Make sure that 'core' is always first in the list
  $core_category = array('core' => $command_categories['core']);
  unset($command_categories['core']);

  // Post-process the categories that have no title.
  // Any that have fewer than 4 commands go into a section called "other".
  $processed_categories = array();
  $misc_categories = array();
  $other_commands = array();
  $other_categories = array();
  foreach ($command_categories as $key => $info) {
    if (empty($info['title'])) {
      $one_category = $key;
      if (count($info['commands']) < 4) {
        $other_commands = array_merge($other_commands, $info['commands']);
        $other_categories[] = $one_category;
      }
      else {
        $info['title'] = dt("All commands in !category", array('!category' => $key));
        $misc_categories[$one_category] = $info;
      }
    }
    else {
      $processed_categories[$key] = $info;
    }
  }
  $other_category = array();
  if (!empty($other_categories)) {
    $other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands);
  }
  asort($processed_categories);
  asort($misc_categories);
  $command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category);

  // If the user specified --sort, then merge all of the remaining
  // categories together
  if (drush_get_option('sort', FALSE)) {
    $combined_commands = array();
    foreach ($command_categories as $key => $info) {
      $combined_commands = array_merge($combined_commands, $info['commands']);
    }
    $command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:")));
  }

  return $command_categories;
}

function drush_command_defaults($key, $commandfile, $path) {
  $defaults =  array(
    'command' => $key,
    'command-hook' => $key,
    'invoke hooks' => TRUE,
    'callback arguments' => array(),
    'commandfile' => $commandfile,
    'path' => dirname($path),
    'engines' => array(), // Helpful for drush_show_help().
    'callback' => 'drush_command',
    'primary function' => FALSE,
    'description' => NULL,
    'sections' => array(
      'examples' => 'Examples',
      'arguments' => 'Arguments',
      'options' => 'Options',
    ),
    'arguments' => array(),
    'required-arguments' => FALSE,
    'options' => array(),
    'sub-options' => array(),
    'allow-additional-options' => FALSE,
    'global-options' => array(),
    'examples' => array(),
    'aliases' => array(),
    'core' => array(),
    'scope' => 'site',
    'drush dependencies' => array(),
    'handle-remote-commands' => FALSE,
    'remote-tty' => FALSE,
    'strict-option-handling' => FALSE,
    'tilde-expansion' => TRUE,
    'bootstrap_errors' => array(),
    'topics' => array(),
    'hidden' => FALSE,
    'category' => $commandfile,
    'add-options-to-arguments' => FALSE,
    'consolidation-output-formatters' => FALSE,
    'annotated-command-callback' => '',
    'annotations' => new AnnotationData(['command' => $key]),
  );
  // We end up here, setting the defaults for a command, when
  // called from drush_get_global_options().  Early in the Drush
  // bootstrap, there will be no bootstrap object, because we
  // need to get the list of global options when loading config
  // files, and config files are loaded before the bootstrap object
  // is created.  In this early stage, we just use the core global
  // options list.  Later, the bootstrap object can also provide
  // additional defaults if needed.  The bootstrap command defaults
  // will be merged into the command object again just before
  // running it in bootstrap_and_dispatch().
  if ($bootstrap = drush_get_bootstrap_object()) {
    $defaults = array_merge($defaults, $bootstrap->command_defaults());
  }
  return $defaults;
}

/**
 * Translates description and other keys of a command definition.
 *
 * @param $command
 *   A command definition.
 */
function drush_command_translate(&$command) {
  $command['description'] = _drush_command_translate($command['description']);
  $keys = array('arguments', 'options', 'examples', 'sections');
  foreach ($keys as $key) {
    foreach ($command[$key] as $k => $v) {
      if (is_array($v)) {
        $v['description'] = _drush_command_translate($v['description']);
      }
      else {
        $v = _drush_command_translate($v);
      }
      $command[$key][$k] = $v;
    }
  }
}

/**
 * Helper function for drush_command_translate().
 *
 * @param $source
 *   String or array.
 */
function _drush_command_translate($source) {
  return is_array($source) ? call_user_func_array('dt', $source) : dt($source);
}

/**
 * Matches a commands array, as returned by drush_get_arguments, with the
 * current command table.
 *
 * Note that not all commands may be discoverable at the point-of-call,
 * since Drupal modules can ship commands as well, and they are
 * not available until after bootstrapping.
 *
 * drush_parse_command returns a normalized command descriptor, which
 * is an associative array. Some of its entries are:
 * - callback arguments: an array of arguments to pass to the calback.
 * - callback: the function to run. Usually, this is 'drush_command', which
 *   will determine the primary hook for the function automatically.  Only
 *   specify a callback function if you need many commands to call the same
 *   function (e.g. drush_print_file).
 * - invoke hooks: If TRUE (the default), Drush will invoke all of the pre and
 *   post hooks for this command.  Set to FALSE to suppress hooks.  This setting
 *   is ignored unless the command 'callback' is also set.
 * - primary function: Drush will copy the 'callback' parameter here if
 *   necessary.  This value should not be set explicitly; use 'callback' instead.
 * - description: description of the command.
 * - arguments: an array of arguments that are understood by the command. for help texts.
 * - required-arguments: The minimum number of arguments that are required, or TRUE if all are required.
 * - options: an array of options that are understood by the command. for help texts.
 * - global-options: a list of options from the set of Drush global options (@see:
 *   drush_get_global_options()) that relate to this command.  The help for these
 *   options will be included in the help output for this command.
 * - examples: an array of examples that are understood by the command. for help texts.
 * - scope: one of 'system', 'project', 'site'.
 * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
 * - core: Drupal major version required.
 * - drupal dependencies: drupal modules required for this command.
 * - drush dependencies: other drush command files required for this command.
 * - handle-remote-commands: set to TRUE if `drush @remote mycommand` should be executed
 *   locally rather than remotely dispatched.  When this mode is set, the target site
 *   can be obtained via:
 *     drush_get_context('DRUSH_TARGET_SITE_ALIAS')
 * - remote-tty: set to TRUE if Drush should force ssh to allocate a pseudo-tty
 *   when this command is being called remotely.  Important for interactive commands.
 *   Remote commands that allocate a psedo-tty always print "Connection closed..." when done.
 * - strict-option-handling: set to TRUE if drush should strictly separate local command
 *   cli options from the global options.  Usually, drush allows global cli options and
 *   command cli options to be interspersed freely on the commandline.  For commands where
 *   this flag is set, options are separated, with global options comming before the
 *   command names, and command options coming after, like so:
 *     drush --global-options command --command-options
 *   In this mode, the command options are no longer available via drush_get_option();
 *   instead, they can be retrieved via:
 *     $args = drush_get_original_cli_args_and_options();
 *     $args = drush_get_context('DRUSH_COMMAND_ARGS', array());
 *   In this case, $args will contain the command args and options literally, exactly as they
 *   were entered on the command line, and in the same order as they appeared.
 * - 'outputformat': declares the data format to be used to render the
 *   command result.  In addition to the output format engine options
 *   listed below, each output format type can take additional metadata
 *   items that control the way that the output is rendered.  See the
 *   comment in each particular output format class for information. The
 *   Drush core output format engines can be found in commands/core/outputformat.
 *     - 'default': The default type to render output as. If declared, the
 *       command should not print any output on its own, but instead should
 *       return a data structure (usually an associative array) that can
 *       be rendered by the output type selected.
 *     - 'pipe-format': When the command is executed in --pipe mode, the
 *       command output will be rendered by the format specified by the
 *       pipe-format item instead of the default format.  Note that in
 *       either event, the user may specify the format to use via the
 *       --format command-line option.
 *     - 'formatted-filter': specifies a function callback that will be
 *       used to filter the command result if the selected output formatter
 *       is NOT declared to be machine-parsable.  "table" is an example of
 *       an output format that is not machine-parsable.
 *     - 'parsable-filter': function callback that will be used to filter the
 *       command result if the selected output formatter is declared to be
 *       machine-parsable. "var_export" is an example of an output format that
 *       is machine-parsable.
 *     - 'output-data-type': An identifier representing the data structure that
 *       the command returns.  @see outputformat_drush_engine_outputformat() for
 *       a description of the supported values.
 *     - 'field-labels': A mapping from machine name to human-readable name
 *       for all of the fields in a table-format command result.  All
 *       possible field names should appear in this list.
 *     - 'fields-default': A list of the machine names of the fields that
 *       should be displayed by default in tables.
 *     - 'private-fields': A list of any fields that contain sensitive
 *       information, such as passwords.  By default, Drush will hide private
 *       fields before printing the results to the console, but will include
 *       them in backend invoke results. Use --show-passwords to display.
 *     - 'column-widths': A mapping from field machine name to the column width
 *       that should be used in table output.  Drush will automatically
 *       calculate the width of any field not listed here based on the length
 *       of the data items in it.
 * - engines: declares information on Drush engines the command will load.
 *   Available engines can vary by command type.
 *
 * @return bool|array
 *   A command definition.
 */
function drush_parse_command() {
  $args = drush_get_arguments();
  $command = FALSE;

  // Get a list of all implemented commands.
  $implemented = drush_get_commands();
  if (!empty($args) && isset($implemented[$args[0]])) {
    $command = $implemented[$args[0]];
    $arguments = array_slice($args, 1);
  }

  // We have found a command that matches. Set the appropriate values.
  if ($command) {
    // Special case. Force help command if --help option was specified.
    if (drush_get_option('help')) {
      $arguments = array($command['command']);
      $command = $implemented['helpsingle'];
      $command['arguments'] = $arguments;
      $command['allow-additional-options'] = TRUE;
    }
    else {
      _drush_prepare_command($command, $arguments);
    }
    drush_set_command($command);
  }
  return $command;
}

/**
 * Called by drush_parse_command().  If a command is dispatched
 * directly by drush_dispatch(), then drush_dispatch() will call
 * this function.
 */
function _drush_prepare_command(&$command, $arguments = array()) {
  // Drush overloads $command['arguments']; save the argument description
  if (!isset($command['argument-description'])) {
    $command['argument-description'] = $command['arguments'];
  }
  // Merge specified callback arguments, which precede the arguments passed on the command line.
  if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
    $arguments = array_merge($command['callback arguments'], $arguments);
  }
  $command['arguments'] = $arguments;
}

/**
 * Invoke a hook in all available command files that implement it.
 *
 * @see drush_command_invoke_all_ref()
 *
 * @param $hook
 *   The name of the hook to invoke.
 * @param ...
 *   Arguments to pass to the hook.
 * @return
 *   An array of return values of the hook implementations. If commands return
 *   arrays from their implementations, those are merged into one array.
 */
function drush_command_invoke_all() {
  $args = func_get_args();
  if (count($args) == 1) {
    $args[] = NULL;
  }
  $reference_value = $args[1];
  $args[1] = &$reference_value;

  return call_user_func_array('drush_command_invoke_all_ref', $args);
}

/**
 * A drush_command_invoke_all() that wants the first parameter to be passed by reference.
 *
 * @see drush_command_invoke_all()
 */
function drush_command_invoke_all_ref($hook, &$reference_parameter) {
  $args = func_get_args();
  array_shift($args);
  // Insure that call_user_func_array can alter first parameter
  $args[0] = &$reference_parameter;
  $return = array();
  $modules = drush_command_implements($hook);
  if ($hook != 'drush_invoke_alter') {
    // Allow modules to control the order of hook invocations
    drush_command_invoke_all_ref('drush_invoke_alter', $modules, $hook);
  }
  foreach ($modules as $module) {
    $function = $module .'_'. $hook;
    $result = call_user_func_array($function, $args);
    if (isset($result) && is_array($result)) {
      $return = array_merge_recursive($return, $result);
    }
    else if (isset($result)) {
      $return[] = $result;
    }
  }
  return $return;
}

/**
 * Determine which command files are implementing a hook.
 *
 * @param $hook
 *   The name of the hook (e.g. "drush_help" or "drush_command").
 *
 * @return
 *   An array with the names of the command files which are implementing this hook.
 */
function drush_command_implements($hook) {
  $implementations[$hook] = array();
  $list = drush_commandfile_list();
  foreach ($list as $commandfile => $file) {
    if (drush_command_hook($commandfile, $hook)) {
      $implementations[$hook][] = $commandfile;
    }
  }
  return (array)$implementations[$hook];
}

/**
 * @param string
 *   name of command to check.
 *
 * @return boolean
 *   TRUE if the given command has an implementation.
 */
function drush_is_command($command) {
  $commands = drush_get_commands();
  return isset($commands[$command]);
}

/**
 * @param string
 *   name of command or command alias.
 *
 * @return string
 *   Primary name of command.
 */
function drush_command_normalize_name($command_name) {
  $commands = drush_get_commands();
  return isset($commands[$command_name]) ? $commands[$command_name]['command'] : $command_name;
}

/**
 * Collect a list of all available drush command files.
 *
 * Scans the following paths for drush command files:
 *
 * - The "/path/to/drush/commands" folder.
 * - Folders listed in the 'include' option (see example.drushrc.php).
 * - The system-wide drush commands folder, e.g. /usr/share/drush/commands
 * - The ".drush" folder in the user's HOME folder.
 * - /drush and sites/all/drush in current Drupal site.
 * - Folders belonging to enabled modules in the current Drupal site.
 *
 * A Drush command file is a file that matches "*.drush.inc".
 *
 * @see drush_scan_directory()
 *
 * @return
 *   An associative array whose keys and values are the names of all available
 *   command files.
 */
function drush_commandfile_list() {
  return commandfiles_cache()->get();
}

function _drush_find_commandfiles($phase, $phase_max = FALSE) {
  drush_log(dt("Find command files for phase !phase (max=!max)", array('!phase' => $phase, '!max' => (string)$phase_max)), LogLevel::DEBUG);
  if ($bootstrap = drush_get_bootstrap_object()) {
    $searchpath = $bootstrap->commandfile_searchpaths($phase, $phase_max);
    _drush_add_commandfiles($searchpath, $phase);
    annotationcommand_adapter_discover($searchpath, $phase, $phase_max);
  }
}

function _drush_add_commandfiles($searchpath, $phase = NULL, $reset = FALSE) {
  static $evaluated = array();
  $needs_sort = FALSE;

  if (count($searchpath)) {
    if (!$reset) {
      // Assemble a cid specific to the bootstrap phase and searchpaths.
      // Bump $cf_version when making a change to a dev version of Drush
      // that invalidates the commandfile cache.
      $cf_version = 8;
      $cid = drush_get_cid('commandfiles-' . $phase, array(), array_merge($searchpath, array($cf_version)));
      $command_cache = drush_cache_get($cid);
      if (isset($command_cache->data)) {
        $cached_list = $command_cache->data;
        // If we want to temporarily ignore modules via 'ignored-modules',
        // then we need to take these out of the cache as well.
        foreach (drush_get_option_list('ignored-modules') as $ignored) {
          unset($cached_list[$ignored]);
        }
      }
    }

    // Build a list of all of the modules to attempt to load.
    // Start with any modules deferred from a previous phase.
    $list = commandfiles_cache()->deferred();
    if (isset($cached_list)) {
      $list = array_merge($list, $cached_list);
    }
    else {
      // Scan for drush command files; add to list for consideration if found.
      foreach (array_unique($searchpath) as $path) {
        if (is_dir($path)) {
          $nomask = array_merge(drush_filename_blacklist(), drush_get_option_list('ignored-modules'));
          $dmv = DRUSH_MAJOR_VERSION;
          $files = drush_scan_directory($path, "/\.drush($dmv|)\.inc$/", $nomask);
          foreach ($files as $filename => $info) {
            $module = basename($filename);
            $module = preg_replace('/\.*drush[0-9]*\.inc/', '', $module);
            // Only try to bootstrap modules that we have never seen before.
            if (!array_key_exists($module, $evaluated) && file_exists($filename)) {
              $evaluated[$module] = TRUE;
              $list[$module] = Path::canonicalize($filename);
            }
          }
        }
      }
      if (isset($cid)) {
        drush_cache_set($cid, $list);
      }
    }
    // Check to see if the commandfile is valid for this version of Drupal
    // and is still present on filesystem (in case of cached commandfile list).
    foreach ($list as $module => $filename) {
      // Only try to require if the file exists. If not, a file from the
      // command file cache may not be available anymore, in which case
      // we rebuild the cache for this phase.
      if (file_exists($filename)) {
        // Avoid realpath() here as Drush commandfiles can have phar:// locations.
        $load_command = commandfiles_cache()->add($filename);
        if ($load_command) {
          $needs_sort = TRUE;
        }
      }
      elseif (!$reset) {
        _drush_add_commandfiles($searchpath, $phase, TRUE);
        $needs_sort = FALSE;
      }
    }

    if ($needs_sort) {
      commandfiles_cache()->sort();
    }
  }
}

/**
 * Substrings to ignore during commandfile and site alias searching.
 */
function drush_filename_blacklist() {
  $blacklist = array('.', '..', 'drush_make', 'examples', 'tests', 'disabled', 'gitcache', 'cache');
  for ($v=4; $v<=(DRUSH_MAJOR_VERSION)+3; ++$v) {
    if ($v != DRUSH_MAJOR_VERSION) {
      $blacklist[] = 'drush' . $v;
    }
  }
  $blacklist = array_merge($blacklist, drush_get_option_list('exclude'));
  return $blacklist;
}

/**
 * Conditionally include files based on the command used.
 *
 * Steps through each of the currently loaded commandfiles and
 * loads an optional commandfile based on the key.
 *
 * When a command such as 'pm-enable' is called, this
 * function will find all 'enable.pm.inc' files that
 * are present in each of the commandfile directories.
 */
function drush_command_include($command) {
  $include_files = drush_command_get_includes($command);
  foreach($include_files as $filename => $commandfile) {
    drush_log(dt('Including !filename', array('!filename' => $filename)), LogLevel::BOOTSTRAP);
    include_once($filename);
  }
}

function drush_command_get_includes($command) {
  $include_files = array();
  $parts = explode('-', $command);
  $command = implode(".", array_reverse($parts));

  $commandfiles = drush_commandfile_list();
  $options = array();
  foreach ($commandfiles as $commandfile => $file) {
    $filename = sprintf("%s/%s.inc", dirname($file), $command);
    if (file_exists($filename)) {
      $include_files[$filename] = $commandfile;
    }
  }
  return $include_files;
}

/**
 * Conditionally include default options based on the command used.
 */
function drush_command_default_options($command = NULL) {
  $command_default_options = drush_get_context('command-specific');
  drush_command_set_command_specific($command_default_options, $command);
}

function drush_sitealias_command_default_options($site_record, $prefix, $command = NULL) {
  if (isset($site_record) && array_key_exists($prefix . 'command-specific', $site_record)) {
    drush_command_set_command_specific($site_record[$prefix . 'command-specific'], $command);
  }
  return FALSE;
}

function drush_command_set_command_specific_options($prefix, $command = NULL) {
  $command_default_options = drush_get_option($prefix . 'command-specific', array());
  drush_command_set_command_specific($command_default_options, $command);
}

function drush_command_set_command_specific($command_default_options, $command = NULL) {
  if (!$command) {
    $command = drush_get_command();
  }
  if ($command) {
    // Look for command-specific options for this command
    // keyed both on the command's primary name, and on each
    // of its aliases.
    $options_were_set = _drush_command_set_default_options($command_default_options, $command['command']);
    if (isset($command['aliases']) && count($command['aliases'])) {
      foreach ($command['aliases'] as $alias) {
        $options_were_set += _drush_command_set_default_options($command_default_options, $alias);
      }
    }
    // If we set or cleared any options, go back and re-bootstrap any global
    // options such as -y and -v.
    if (!empty($options_were_set)) {
      _drush_preflight_global_options();
    }
    // If the command uses strict option handling, back out any global
    // options that were set.
    if ($command['strict-option-handling']) {
      $global_options = drush_get_global_options();
      foreach ($options_were_set as $key) {
        if (array_key_exists($key, $global_options)) {
          if (!array_key_exists('context', $global_options[$key])) {
            $strict_options_warning =& drush_get_context('DRUSH_STRICT_OPTIONS_WARNING', array());
            if (!array_key_exists($key, $strict_options_warning)) {
              drush_log(dt("Global option --!option not supported in command-specific options for command !command due to a limitation in strict option handling.", array('!option' => $key, '!command' => $command['command'])), LogLevel::WARNING);
              $strict_options_warning[$key] = TRUE;
            }
          }
          drush_unset_option($key, 'specific');
        }
      }
    }
  }
}

function _drush_command_set_default_options($command_default_options, $command) {
  $options_were_set = array();
  if (array_key_exists($command, $command_default_options)) {
    foreach ($command_default_options[$command] as $key => $value) {
      // We set command-specific options in their own context
      // that is higher precedence than the various config file
      // context, but lower than command-line options.
      if (!drush_get_option('no-' . $key, FALSE)) {
        drush_set_option($key, $value, 'specific');
        $options_were_set[] = $key;
      }
    }
  }
  return $options_were_set;
}

/**
 * Return all of the command-specific options defined in the given
 * options set for the specified command name.  Note that it is valid
 * to use the command name alias rather than the primary command name,
 * both in the parameter to this function, and in the options set.
 */
function drush_command_get_command_specific_options($options, $command_name, $prefix = '') {
  $result = array();
  $command_name = drush_command_normalize_name($command_name);
  if (isset($options[$prefix . 'command-specific'])) {
    foreach ($options[$prefix . 'command-specific'] as $options_for_command => $values) {
      if ($command_name == drush_command_normalize_name($options_for_command)) {
        $result = array_merge($result, $values);
      }
    }
  }
  return $result;
}

/**
 * Return the original cli args and options, exactly as they
 * appeared on the command line, and in the same order.
 * Any command-specific options that were set will also
 * appear in this list, appended at the very end.
 *
 * The args and options returned are raw, and must be
 * escaped as necessary before use.
 */
function drush_get_original_cli_args_and_options($command = NULL) {
  $args = drush_get_context('DRUSH_COMMAND_ARGS', array());
  $command_specific_options = drush_get_context('specific');
  if ($command == NULL) {
    $command = drush_get_command();
  }
  $command_options = ($command == NULL) ? array() : _drush_get_command_options($command);
  foreach ($command_specific_options as $key => $value) {
    if (!array_key_exists($key, $command_options)) {
      if (($value === TRUE) || (!isset($value))) {
        $args[] = "--$key";
      }
      else {
        $args[] = "--$key=$value";
      }
    }
  }
  return $args;
}

/**
 * Determine whether a command file implements a hook.
 *
 * @param $module
 *   The name of the module (without the .module extension).
 * @param $hook
 *   The name of the hook (e.g. "help" or "menu").
 * @return
 *   TRUE if the the hook is implemented.
 */
function drush_command_hook($commandfile, $hook) {
  return function_exists($commandfile . '_' . $hook);
}

/**
 * Check that a command is valid for the current bootstrap phase.
 *
 * @param $command
 *   Command to check. Any errors will be added to the 'bootstrap_errors' element.
 *
 * @return
 *   TRUE if command is valid.
 */
function drush_enforce_requirement_bootstrap_phase(&$command) {
  $valid = array();
  $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  if ($command['bootstrap'] <= $current_phase) {
    return TRUE;
  }
  // TODO: provide description text for each bootstrap level so we can give
  // the user something more helpful and specific here.
  $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need to invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command']));
}

/**
 * Check that a command has its declared drush dependencies available or have no
 * dependencies. Drush dependencies are helpful when a command is invoking
 * another command, or implementing its API.
 *
 * @param $command
 *   Command to check. Any errors  will be added to the 'bootstrap_errors' element.
 * @return
 *   TRUE if dependencies are met.
 */
function drush_enforce_requirement_drush_dependencies(&$command) {
  // If there are no drush dependencies, then do nothing.
  if (!empty($command['drush dependencies'])) {
    $commandfiles = drush_commandfile_list();
    foreach ($command['drush dependencies'] as $dependency) {
      if (!isset($commandfiles[$dependency])) {
        $dt_args = array(
          '!command' => $command['command'],
          '!dependency' => "$dependency.drush.inc",
        );
        $command['bootstrap_errors']['DRUSH_COMMANDFILE_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args);
        return FALSE;
      }
    }
  }
  return TRUE;
}

/**
 * Check that a command is valid for the current major version of core. Handles
 * explicit version numbers and 'plus' numbers like 7+ (compatible with 7,8 ...).
 *
 * @param $command
 *   Command to check. Any errors  will be added to the 'bootstrap_errors' element.
 *
 * @return
 *   TRUE if command is valid.
 */
function drush_enforce_requirement_core(&$command) {
  $major = drush_drupal_major_version();
  if (!$core = $command['core']) {
    return TRUE;
  }
  foreach ($core as $compat) {
    if ($compat == $major) {
      return TRUE;
    }
    elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) {
      return TRUE;
    }
  }
  $versions = array_pop($core);
  if (!empty($core)) {
    $versions = implode(', ', $core) . dt(' or ') . $versions;
  }
  $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
}

/**
 * Check if a shell alias exists for current request. If so, re-route to
 * core-execute and pass alias value along with rest of CLI arguments.
 */
function drush_shell_alias_replace($target_site_alias) {
  $escape = TRUE;
  $args = drush_get_arguments();
  $argv = drush_get_context('argv');
  $first = current($args);
  // @todo drush_get_option is awkward here.
  $shell_aliases = drush_get_context('shell-aliases', array());
  if (isset($shell_aliases[$first])) {
    // Shell alias found for first argument in the request.
    $alias_value = $shell_aliases[$first];
    if (!is_array($alias_value)) {
      // Shell aliases can have embedded variables such as {{@target}} and {{%root}}
      // that are replaced with the name of the target site alias, or the value of a
      // path alias defined in the target site alias record.  We only support replacements
      // when the alias value is a string; if it is already broken out into an array,
      // then the values therein are used literally.
      $alias_variables = array( '{{@target}}' => '@none' );
      if ($target_site_alias) {
        $alias_variables = array( '{{@target}}' => $target_site_alias );
        $target = drush_sitealias_get_record($target_site_alias);
        foreach ($target as $key => $value) {
          if (!is_array($value)) {
            $alias_variables['{{' . $key . '}}'] = $value;
          }
        }
        if (array_key_exists('path-aliases', $target)) {
          foreach ($target['path-aliases'] as $key => $value) {
            // n.b. $key will contain something like "%root" or "%files".
            $alias_variables['{{' . $key . '}}'] = $value;
          }
        }
      }
      $alias_value = str_replace(array_keys($alias_variables), array_values($alias_variables), $alias_value);
      // Check for unmatched replacements
      $matches = array();
      $match_result = preg_match('/{{[%@#]*[a-z0-9.]*}}/', $alias_value, $matches);
      if ($match_result) {
        $unmatched_replacements = implode(', ', $matches);
        $unmatched_replacements = preg_replace('/[{}]/', '', $unmatched_replacements);
        return drush_set_error('DRUSH_SHELL_ALIAS_UNMATCHED_REPLACEMENTS', dt('The shell alias @alias-name uses replacements "@unmatched". You must use this command with a site alias (e.g. `drush @myalias @alias-name ...`) that defines all of these variables.', array('@alias-name' => $first, '@unmatched' => $unmatched_replacements)));
      }
      if (substr($alias_value, 0, 1) == '!') {
        $alias_value = ltrim($alias_value, '!');
        $alias_value = array('core-execute', $alias_value);
        $escape = FALSE;
      }
      else {
        // Respect quoting. See http://stackoverflow.com/questions/2202435/php-ex
        $alias_value = str_getcsv($alias_value, ' ');
      }
    }
    drush_log(dt('Shell alias found: !key => !value', array('!key' => $first, '!value' => implode(' ', $alias_value))), LogLevel::DEBUG);
    $pos = array_search($first, $argv);
    $number = 1;
    if ($target_site_alias && ($argv[$pos - 1] == $target_site_alias)) {
      --$pos;
      ++$number;
    }
    array_splice($argv, $pos, $number, $alias_value);
    if (!$escape) {
      drush_set_option('escape', FALSE);
    }
    drush_set_context('argv', $argv);
    drush_parse_args();
    _drush_preflight_global_options();
  }
}

function commandfiles_cache() {
  static $commandfiles_cache = NULL;

  if (!isset($commandfiles_cache)) {
    $commandfiles_cache = new Drush\Command\Commandfiles();
  }
  return $commandfiles_cache;
}
<?php

use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Drush\Log\LogLevel;

/**
 * @file
 *
 * Provide completion output for shells.
 *
 * This is not called directly, but by shell completion scripts specific to
 * each shell (bash, csh etc). These run whenever the user triggers completion,
 * typically when pressing <tab>. The shell completion scripts should call
 * "drush complete <text>", where <text> is the full command line, which we take
 * as input and use to produce a list of possible completions for the
 * current/next word, separated by newlines. Typically, when multiple
 * completions are returned the shell will display them to the user in a concise
 * format - but when a single completion is returned it will autocomplete.
 *
 * We provide completion for site aliases, commands, shell aliases, options,
 * engines and arguments. Displaying all of these when the last word has no
 * characters yet is not useful, as there are too many items. Instead we filter
 * the possible completions based on position, in a similar way to git.
 * For example:
 * - We only display site aliases and commands if one is not already present.
 * - We only display options if the user has already entered a hyphen.
 * - We only display global options before a command is entered, and we only
 *   display command specific options after the command (Drush itself does not
 *   care about option placement, but this approach keeps things more concise).
 *
 * Below is typical output of complete in different situations. Tokens in square
 * brackets are optional, and [word] will filter available options that start
 * with the same characters, or display all listed options if empty.
 * drush --[word] : Output global options
 * drush [word] : Output site aliases, sites, commands and shell aliases
 * drush [@alias] [word] : Output commands
 * drush [@alias] command [word] : Output command specific arguments
 * drush [@alias] command --[word] : Output command specific options
 *
 * Because the purpose of autocompletion is to make the command line more
 * efficient for users we need to respond quickly with the list of completions.
 * To do this, we call drush_complete() early in the Drush bootstrap, and
 * implement a simple caching system.
 *
 * To generate the list of completions, we set up the Drush environment as if
 * the command was called on it's own, parse the command using the standard
 * Drush functions, bootstrap the site (if any) and collect available
 * completions from various sources. Because this can be somewhat slow, we cache
 * the results. The cache strategy aims to balance accuracy and responsiveness:
 * - We cache per site, if a site is available.
 * - We generate (and cache) everything except arguments at the same time, so
 *   subsequent completions on the site don't need any bootstrap.
 * - We generate and cache arguments on-demand, since these can often be
 *   expensive to generate. Arguments are also cached per-site.
 *
 * For argument completions, commandfiles can implement
 * COMMANDFILE_COMMAND_complete() returning an array containing a key 'values'
 * containing an array of all possible argument completions for that command.
 * For example, return array('values' => array('aardvark', 'aardwolf')) offers
 * the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the
 * letters 'aardw' are already present. Since command arguments are cached,
 * commandfiles can bootstrap a site or perform other somewhat time consuming
 * activities to retrieve the list of possible arguments. Commands can also
 * clear the cache (or just the "arguments" cache for their command) when the
 * completion results have likely changed - see drush_complete_cache_clear().
 *
 * Commandfiles can also return a special optional element in their array with
 * the key 'files' that contains an array of patterns/flags for the glob()
 * function. These are used to produce file and directory completions (the
 * results of these are not cached, since this is a fast operation).
 * See http://php.net/glob for details of valid patterns and flags.
 * For example the following will complete the command arguments on all
 * directories, as well as files ending in tar.gz:
 *   return array(
 *         'files' => array(
 *           'directories' => array(
 *             'pattern' => '*',
 *             'flags' => GLOB_ONLYDIR,
 *           ),
 *           'tar' => array(
 *             'pattern' => '*.tar.gz',
 *           ),
 *         ),
 *       );
 *
 * To check completion results without needing to actually trigger shell
 * completion, you can call this manually using a command like:
 *
 * drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]...
 *
 * If you want to simulate the results of pressing tab after a space (i.e.
 * and empty last word, include '' on the end of your command:
 *
 * drush --early=includes/complete.inc [--complete-debug] drush ''
 */

/**
 * Produce autocomplete output.
 *
 * Determine position (is there a site-alias or command set, and are we trying
 * to complete an option). Then produce a list of completions for the last word
 * and output them separated by newlines.
 */
function drush_early_complete() {
  // We use a distinct --complete-debug option to avoid unwanted debug messages
  // being printed when users use this option for other purposes in the command
  // they are trying to complete.
  drush_set_option(LogLevel::DEBUG, FALSE);
  if (drush_get_option('complete-debug', FALSE)) {
    drush_set_context('DRUSH_DEBUG', TRUE);
  }
  // Set up as if we were running the command, and attempt to parse.
  $argv = drush_complete_process_argv();
  if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
    $set_sitealias_name = $alias;
    $set_sitealias = drush_sitealias_get_record($alias);
  }

  // Arguments have now had site-aliases and options removed, so we take the
  // first item as our command. We need to know if the command is valid, so that
  // we know if we are supposed to complete an in-progress command name, or
  // arguments for a command. We do this by checking against our per-site cache
  // of command names (which will only bootstrap if the cache needs to be
  // regenerated), rather than drush_parse_command() which always requires a
  // site bootstrap.
  $arguments = drush_get_arguments();
  $set_command_name = NULL;
  if (isset($arguments[0]) && in_array($arguments[0] . ' ', drush_complete_get('command-names'))) {
    $set_command_name = $arguments[0];
  }
  // We unset the command if it is "help" but that is not explicitly found in
  // args, since Drush sets the command to "help" if no command is specified,
  // which prevents completion of global options.
  if ($set_command_name == 'help' && !array_search('help', $argv)) {
    $set_command_name = NULL;
  }

  // Determine the word we are trying to complete, and if it is an option.
  $last_word = end($argv);
  $word_is_option = FALSE;
  if (!empty($last_word) && $last_word[0] == '-') {
    $word_is_option = TRUE;
    $last_word = ltrim($last_word, '-');
  }

  $completions = array();
  if (!$set_command_name) {
    // We have no command yet.
    if ($word_is_option) {
      // Include global option completions.
      $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options')));
    }
    else {
      if (empty($set_sitealias_name)) {
        // Include site alias completions.
        $completions += drush_complete_match($last_word, drush_complete_get('site-aliases'));
      }
      // Include command completions.
      $completions += drush_complete_match($last_word, drush_complete_get('command-names'));
    }
  }
  else {
    if ($last_word == $set_command_name) {
      // The user just typed a valid command name, but we still do command
      // completion, as there may be other commands that start with the detected
      // command (e.g. "make" is a valid command, but so is "make-test").
      // If there is only the single matching command, this will include in the
      // completion list so they get a space inserted, confirming it is valid.
      $completions += drush_complete_match($last_word, drush_complete_get('command-names'));
    }
    else if ($word_is_option) {
      // Include command option completions.
      $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options', $set_command_name)));
    }
    else {
      // Include command argument completions.
      $argument_completion = drush_complete_get('arguments', $set_command_name);
      if (isset($argument_completion['values'])) {
        $completions += drush_complete_match($last_word, $argument_completion['values']);
      }
      if (isset($argument_completion['files'])) {
        $completions += drush_complete_match_file($last_word, $argument_completion['files']);
      }
    }
  }

  if (!empty($completions)) {
    sort($completions);
    return implode("\n", $completions);
  }
  return TRUE;
}

/**
 * This function resets the raw arguments so that Drush can parse the command as
 * if it was run directly. The shell complete command passes the
 * full command line as an argument, and the --early and --complete-debug
 * options have to come before that, and the "drush" bash script will add a
 * --php option on the end, so we end up with something like this:
 *
 * /path/to/drush.php --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... --php=/usr/bin/php
 *
 * Note that "drush" occurs twice, and also that the second occurrence could be
 * an alias, so we can't easily use it as to detect the start of the actual
 * command. Hence our approach is to remove the initial "drush" and then any
 * options directly following that - what remains is then the command we need
 * to complete - i.e.:
 *
 * drush [@alias] [command]...
 *
 * Note that if completion is initiated following a space an empty argument is
 * added to argv. So in that case argv looks something like this:
 * array (
 *  '0' => '/path/to/drush.php',
 *  '1' => '--early=includes/complete.inc',
 *  '2' => 'drush',
 *  '3' => 'topic',
 *  '4' => '',
 *  '5' => '--php=/usr/bin/php',
 * );
 *
 * @return $args
 *   Array of arguments (argv), excluding the initial command and options
 *   associated with the complete call.
 *   array (
 *    '0' => 'drush',
 *    '1' => 'topic',
 *    '2' => '',
 *   );
 */
function drush_complete_process_argv() {
  $argv = drush_get_context('argv');
  // Remove the first argument, which will be the "drush" command.
  array_shift($argv);
  while (substr($arg = array_shift($argv), 0, 2) == '--') {
    // We remove all options, until we get to a non option, which
    // marks the start of the actual command we are trying to complete.
  }
  // Replace the initial argument.
  array_unshift($argv, $arg);
  // Remove the --php option at the end if exists (added by the "drush" shell
  // script that is called when completion is requested).
  if (substr(end($argv), 0, 6) == '--php=') {
    array_pop($argv);
  }
  drush_set_context('argv', $argv);
  drush_set_command(NULL);
  // Reparse arguments, site alias, and command.
  drush_parse_args();
  // Ensure the base environment is configured, so tests look in the correct
  // places.
  _drush_preflight_base_environment();
  // Check for and record any site alias.
  drush_sitealias_check_arg();
  drush_sitealias_check_site_env();
  // We might have just changed our root--run drush_select_bootstrap_class() again.
  $bootstrap = drush_select_bootstrap_class();

  // Return the new argv for easy reference.
  return $argv;
}

/**
 * Retrieves the appropriate list of candidate completions, then filters this
 * list using the last word that we are trying to complete.
 *
 * @param string $last_word
 *   The last word in the argument list (i.e. the subject of completion).
 * @param array $values
 *   Array of possible completion values to filter.
 *
 * @return array
 *   Array of candidate completions that start with the same characters as the
 *   last word. If the last word is empty, return all candidates.
 */
function drush_complete_match($last_word, $values) {
  // Using preg_grep appears to be faster that strpos with array_filter/loop.
  return preg_grep('/^' . preg_quote($last_word, '/') . '/', $values);
}

/**
 * Retrieves the appropriate list of candidate file/directory completions,
 * filtered by the last word that we are trying to complete.
 *
 * @param string $last_word
 *   The last word in the argument list (i.e. the subject of completion).
 * @param array $files
 *   Array of file specs, each with a pattern and flags subarray.
 *
 * @return array
 *   Array of candidate file/directory completions that start with the same
 *   characters as the last word. If the last word is empty, return all
 *   candidates.
 */
function drush_complete_match_file($last_word, $files) {
  $return = array();
  if ($last_word[0] == '~') {
    // Complete does not do tilde expansion, so we do it here.
    // We shell out (unquoted) to expand the tilde.
    drush_shell_exec('echo ' . $last_word);
    return drush_shell_exec_output();
  }

  $dir = '';
  if (substr($last_word, -1) == '/' && is_dir($last_word)) {
    // If we exactly match a trailing directory, then we use that as the base
    // for the listing. We only do this if a trailing slash is present, since at
    // this stage it is still possible there are other directories that start
    // with this string.
    $dir = $last_word;
  }
  else {
    // Otherwise we discard the last part of the path (this is matched against
    // the list later), and use that as our base.
    $dir = dirname($last_word);
    if (empty($dir) || $dir == '.' && $last_word != '.' && substr($last_word, 0, 2) != './') {
      // We are looking at the current working directory, so unless the user is
      // actually specifying a leading dot we leave the path empty.
      $dir = '';
    }
    else {
      // In all other cases we need to add a trailing slash.
      $dir .= '/';
    }
  }

  foreach ($files as $spec) {
    // We always include GLOB_MARK, as an easy way to detect directories.
    $flags = GLOB_MARK;
    if (isset($spec['flags'])) {
      $flags = $spec['flags'] | GLOB_MARK;
    }
    $listing = glob($dir . $spec['pattern'], $flags);
    $return = array_merge($return, drush_complete_match($last_word, $listing));
  }
  // If we are returning a single item (which will become part of the final
  // command), we need to use the full path, and we need to escape it
  // appropriately.
  if (count($return) == 1) {
    // Escape common shell metacharacters (we don't use escapeshellarg as it
    // single quotes everything, even when unnecessary).
    $item = array_pop($return);
    $item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item);
    if (substr($item, -1) !== '/') {
      // Insert a space after files, since the argument is complete.
      $item = $item . ' ';
    }
    $return = array($item);
  }
  else {
    $firstchar = TRUE;
    if ($last_word[0] == '/') {
      // If we are working with absolute paths, we need to check if the first
      // character of all the completions matches. If it does, then we pass a
      // full path for each match, so the shell completes as far as it can,
      // matching the behaviour with relative paths.
      $pos = strlen($last_word);
      foreach ($return as $id => $item) {
        if ($item[$pos] !== $return[0][$pos]) {
          $firstchar = FALSE;
          continue;
        }
      }
    }
    foreach ($return as $id => $item) {
      // For directories we leave the path alone.
      $slash_pos = strpos($last_word, '/');
      if ($slash_pos === 0 && $firstchar) {
        // With absolute paths where completions share initial characters, we
        // pass in a resolved path.
        $return[$id] = realpath($item);
      }
      else if ($slash_pos !== FALSE && $dir != './') {
        // For files, we pass only the file name, ignoring the false match when
        // the user is using a single dot relative path.
        $return[$id] = basename($item);
      }
    }
  }
  return $return;
}

/**
 * Simple helper function to ensure options are properly hyphenated before we
 * return them to the user (we match against the non-hyphenated versions
 * internally).
 *
 * @param array $options
 *   Array of unhyphenated option names.
 *
 * @return array
 *   Array of hyphenated option names.
 */
function drush_hyphenate_options($options) {
  foreach ($options as $key => $option) {
    $options[$key] = '--' . ltrim($option, '--');
  }
  return $options;
}

/**
 * Retrieves from cache, or generates a listing of completion candidates of a
 * specific type (and optionally, command).
 *
 * @param string $type
 *   String indicating type of completions to return.
 *   See drush_complete_rebuild() for possible keys.
 * @param string $command
 *   An optional command name if command specific completion is needed.
 *
 * @return array
 *   List of candidate completions.
 */
function drush_complete_get($type, $command = NULL) {
  static $complete;
  if (empty($command)) {
    // Quick return if we already have a complete static cache.
    if (!empty($complete[$type])) {
      return $complete[$type];
    }
    // Retrieve global items from a non-command specific cache, or rebuild cache
    // if needed.
    $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete');
    if (isset($cache->data)) {
      return $cache->data;
    }
    $complete = drush_complete_rebuild();
    return $complete[$type];
  }
  // Retrieve items from a command specific cache.
  $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete');
  if (isset($cache->data)) {
    return $cache->data;
  }
  // Build argument cache - built only on demand.
  if ($type == 'arguments') {
    return drush_complete_rebuild_arguments($command);
  }
  // Rebuild cache of general command specific items.
  if (empty($complete)) {
    $complete = drush_complete_rebuild();
  }
  if (!empty($complete['commands'][$command][$type])) {
    return $complete['commands'][$command][$type];
  }
  return array();
}

/**
 * Rebuild and cache completions for everything except command arguments.
 *
 * @return array
 *   Structured array of completion types, commands and candidate completions.
 */
function drush_complete_rebuild() {
  $complete = array();
  // Bootstrap to the site level (if possible) - commands may need to check
  // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  $commands = drush_get_commands();
  foreach ($commands as $command_name => $command) {
    // Add command options and suboptions.
    $options = array_keys($command['options']);
    foreach ($command['sub-options'] as $option => $sub_options) {
      $options = array_merge($options, array_keys($sub_options));
    }
    $complete['commands'][$command_name]['options'] = $options;
  }
  // We treat shell aliases as commands for the purposes of completion.
  $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_context('shell-aliases', array())));
  $site_aliases = _drush_sitealias_all_list();
  // TODO: Figure out where this dummy @0 alias is introduced.
  unset($site_aliases['@0']);
  $complete['site-aliases'] = array_keys($site_aliases);
  $complete['options'] = array_keys(drush_get_global_options());

  // We add a space following all completes. Eventually there may be some
  // items (e.g. options that we know need values) where we don't add a space.
  array_walk_recursive($complete, 'drush_complete_trailing_space');
  drush_complete_cache_set($complete);
  return $complete;
}

/**
 * Helper callback function that adds a trailing space to completes in an array.
 */
function drush_complete_trailing_space(&$item, $key) {
  if (!is_array($item)) {
    $item = (string)$item . ' ';
  }
}

/**
 * Rebuild and cache completions for command arguments.
 *
 * @param string $command
 *   A specific command to retrieve and cache arguments for.
 *
 * @return array
 *   Structured array of candidate completion arguments, keyed by the command.
 */
function drush_complete_rebuild_arguments($command) {
  // Bootstrap to the site level (if possible) - commands may need to check
  // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  $commands = drush_get_commands();
  $command_info = $commands[$command];
  if ($callback = $command_info['annotated-command-callback']) {
    list($classname, $method) = $callback;
    $commandInfo = new CommandInfo($classname, $method);
    if ($callable = $commandInfo->getAnnotation('complete')) {
      $result = call_user_func($callable);
    }
  }
  else {
    $hook = str_replace("-", "_", $command_info['command-hook']);
    $result = drush_command_invoke_all($hook . '_complete');
  }
  if (isset($result['values'])) {
    // We add a space following all completes. Eventually there may be some
    // items (e.g. comma separated arguments) where we don't add a space.
    array_walk($result['values'], 'drush_complete_trailing_space');
  }

  $complete = array(
    'commands' => array(
      $command => array(
        'arguments' => $result,
      )
    )
  );
  drush_complete_cache_set($complete);
  return $complete['commands'][$command]['arguments'];
}

/**
 * Stores caches for completions.
 *
 * @param $complete
 *   A structured array of completions, keyed by type, including a 'commands'
 *   type that contains all commands with command specific completions keyed by
 *   type. The array does not need to include all types - used by
 *   drush_complete_rebuild_arguments().
 */
function drush_complete_cache_set($complete) {
  foreach ($complete as $type => $values) {
    if ($type == 'commands') {
      foreach ($values as $command_name => $command) {
        foreach ($command as $command_type => $command_values) {
          drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY);
        }
      }
    }
    else {
      drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY);
    }
  }
}

/**
 * Generate a cache id.
 *
 * @param $type
 *   The completion type.
 * @param $command
 *   The command name (optional), if completions are command specific.
 *
 * @return string
 *   Cache id.
 */
function drush_complete_cache_cid($type, $command = NULL) {
  // For per-site caches, we include the site root and uri/path in the cache id
  // hash. These are quick to determine, and prevents a bootstrap to site just
  // to get a validated root and URI. Because these are not validated, there is
  // the possibility of cache misses/ but they should be rare, since sites are
  // normally referred to the same way (e.g. a site alias, or using the current
  // directory), at least within a single command completion session.
  // We also static cache them, since we may get differing results after
  // bootstrap, which prevents the caches from being found on the next call.
  static $root, $site;
  if (empty($root)) {
    $root = drush_get_option(array('r', 'root'), drush_locate_root());
    $site = drush_get_option(array('l', 'uri'), drush_site_path());
  }
  return drush_get_cid('complete', array(), array($type, $command, $root, $site));
}
<?php
/**
 * @file
 * The Drush context API implementation.
 *
 * This API acts as a storage mechanism for all options, arguments and
 * configuration settings that are loaded into drush.
 *
 * This API also acts as an IPC mechanism between the different drush commands,
 * and provides protection from accidentally overriding settings that are
 * needed by other parts of the system.
 *
 * It also avoids the necessity to pass references through the command chain
 * and allows the scripts to keep track of whether any settings have changed
 * since the previous execution.
 *
 * This API defines several contexts that are used by default.
 *
 * Argument contexts :
 *   These contexts are used by Drush to store information on the command.
 *   They have their own access functions in the forms of
 *   drush_set_arguments(), drush_get_arguments(), drush_set_command(),
 *   drush_get_command().
 *
 *     command : The drush command being executed.
 *     arguments : Any additional arguments that were specified.
 *
 * Setting contexts :
 *   These contexts store options that have been passed to the drush.php
 *   script, either through the use of any of the config files, directly from
 *   the command line through --option='value' or through a JSON encoded string
 *   passed through the STDIN pipe.
 *
 *   These contexts are accessible through the drush_get_option() and
 *   drush_set_option() functions.  See drush_context_names() for a description
 *   of all of the contexts.
 *
 *   Drush commands may also choose to save settings for a specific context to
 *   the matching configuration file through the drush_save_config() function.
 */

use Drush\Log\LogLevel;


/**
 * Return a list of the valid drush context names.
 *
 * These context names are carefully ordered from
 * highest to lowest priority.
 *
 * These contexts are evaluated in a certain order, and the highest priority value
 * is returned by default from drush_get_option. This allows scripts to check whether
 * an option was different before the current execution.
 *
 *   Specified by the script itself :
 *     process  : Generated in the current process.
 *     cli      : Passed as --option=value to the command line.
 *     stdin    : Passed as a JSON encoded string through stdin.
 *     specific : Defined in a command-specific option record, and
 *                set in the command context whenever that command is used.
 *     alias    : Defined in an alias record, and set in the
 *                alias context whenever that alias is used.
 *
 *   Specified by config files :
 *     custom   : Loaded from the config file specified by --config or -c
 *     site     : Loaded from the drushrc.php file in the Drupal site directory.
 *     drupal   : Loaded from the drushrc.php file in the Drupal root directory.
 *     user     : Loaded from the drushrc.php file in the user's home directory.
 *     home.drush Loaded from the drushrc.php file in the $HOME/.drush directory.
 *     system   : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory.
 *     drush    : Loaded from the drushrc.php file in the same directory as drush.php.
 *
 *   Specified by the script, but has the lowest priority :
 *     default  : The script might provide some sensible defaults during init.
 */
function drush_context_names() {
  static $contexts = array(
    'process', 'cli', 'stdin', 'specific', 'alias',
    'custom', 'site', 'drupal', 'user', 'home.drush', 'system',
    'drush', 'default');

  return $contexts;
}

/**
 * Return a list of possible drushrc file locations.
 *
 * @context
 *   A valid drush context from drush_context_names().
 * @prefix
 *   Optional. Specify a prefix to prepend to ".drushrc.php" when looking
 *   for config files. Most likely used by contrib commands.
 * @return
 *   An associative array containing possible config files to load
 *   The keys are the 'context' of the files, the values are the file
 *   system locations.
 */
function _drush_config_file($context, $prefix = NULL, $version = '') {
  $configs = array();
  $base_name = 'drush' . $version . 'rc.php';
  $config_file = $prefix ? $prefix . '.' . $base_name : $base_name;

  // Did the user explicitly specify a config file?
  if ($config_list = (array)drush_get_context('DRUSH_CONFIG')) {
    foreach ($config_list as $config) {
      if (is_dir($config)) {
        $config = $config . '/' . $config_file;
      }
      $configs['custom'][] = $config;
    }
  }

  if ($drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT')) {
    $configs['drupal'] = array(
      $drupal_root . '/../drush/' . $config_file,
      $drupal_root . '/sites/all/drush/' . $config_file,
      $drupal_root . '/drush/' . $config_file,
    );

    if ($conf_path = drush_get_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH', 'sites/default')) {
      $site_path = $drupal_root . '/' . $conf_path;
      $configs['site'] = $site_path . "/" . $config_file;
    }
  }

  // in the user home directory
  $server_home = drush_server_home();
  if (isset($server_home)) {
    $configs['user'] = $server_home . '/.' . $config_file;
  }

  // in $HOME/.drush directory
  $per_user_config_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION');
  if (!empty($per_user_config_dir)) {
    $configs['home.drush'] = $per_user_config_dir . '/' . $config_file;
  }

  // In the system wide configuration folder.
  $configs['system'] = drush_get_context('DRUSH_SITE_WIDE_CONFIGURATION') . '/' . $config_file;

  // in the drush installation folder
  $configs['drush'] = dirname(__FILE__) . '/../' . $config_file;

  return empty($configs[$context]) ? '' : $configs[$context];
}


/**
 * Load drushrc files (if available) from several possible locations.
 */
function drush_load_config($context) {
  drush_load_config_file($context, _drush_config_file($context));
  drush_load_config_file($context, _drush_config_file($context, '', DRUSH_MAJOR_VERSION));
}

function drush_load_config_file($context, $config_list) {
  foreach ((array)$config_list as $config) {
    if (file_exists($config)) {
      $options = $aliases = $command_specific = $override = array();
      drush_log(dt('Loading drushrc "!config" into "!context" scope.', array('!config' => realpath($config), '!context' => $context)), LogLevel::BOOTSTRAP);
      $ret = @include_once($config);
      if ($ret === FALSE) {
        drush_log(dt('Cannot open drushrc "!config", ignoring.', array('!config' => realpath($config))), LogLevel::WARNING);
        return FALSE;
      }
      if (!empty($options) || !empty($aliases) || !empty($command_specific) || !empty($override)) {
        $options = array_merge(drush_get_context($context), $options);
        $options['config-file'] = realpath($config);

        unset($options['site-aliases']);
        $options['command-specific'] = array_merge(isset($command_specific) ? $command_specific : array(), isset($options['command-specific']) ? $options['command-specific'] : array());

        drush_set_config_options($context, $options, $override);
      }
    }
  }
}

function drush_set_config_options($context, $options, $override = array()) {
  // Copy 'config-file' into 'context-path', converting to an array to hold multiple values if necessary
  if (isset($options['config-file'])) {
    if (isset($options['context-path'])) {
      $options['context-path'] = array_merge(array($options['config-file']), is_array($options['context-path']) ? $options['context-path'] : array($options['context-path']));
    }
    else {
      $options['context-path'] = $options['config-file'];
    }
  }

  // Take out $aliases and $command_specific options
  drush_set_config_special_contexts($options);

  drush_set_context($context, $options);
}

/**
 * For all global options with a short form, convert all options in the option
 * array that use the short form into the long form.
 */
function drush_expand_short_form_options(&$options) {
  foreach (drush_get_global_options() as $name => $info) {
    if (is_array($info)) {
      // For any option with a short form, check to see if the short form was set in the
      // options.  If it was, then rename it to its long form.
      if (array_key_exists('short-form', $info) && array_key_exists($info['short-form'], $options)) {
        if (!array_key_exists($name, $options) || !array_key_exists('merge-pathlist', $info)) {
          $options[$name] = $options[$info['short-form']];
        }
        else {
          $options[$name] = array_merge((array)$options[$name], (array)$options[$info['short-form']]);
        }
        unset($options[$info['short-form']]);
      }
    }
  }
}

/**
 * There are certain options such as 'site-aliases' and 'command-specific'
 * that must be merged together if defined in multiple drush configuration
 * files.  If we did not do this merge, then the last configuration file
 * that defined any of these properties would overwrite all of the options
 * that came before in previously-loaded configuration files.  We place
 * all of them into their own context so that this does not happen.
 */
function drush_set_config_special_contexts(&$options) {
  if (isset($options) && is_array($options)) {
    $has_command_specific = array_key_exists('command-specific', $options);
    // Change the keys of the site aliases from 'alias' to '@alias'
    if (array_key_exists('site-aliases', $options)) {
      $user_aliases = $options['site-aliases'];
      $options['site-aliases'] = array();
      foreach ($user_aliases as $alias_name => $alias_value) {
        if (substr($alias_name,0,1) != '@') {
          $alias_name = "@$alias_name";
        }
        $options['site-aliases'][$alias_name] = $alias_value;
      }
    }
    // Expand -s into --simulate, etc.
    drush_expand_short_form_options($options);

    foreach (drush_get_global_options() as $name => $info) {
      if (is_array($info)) {
        // For any global option with the 'merge-pathlist' or 'merge-associative' flag, set its
        // value in the specified context.  The option is 'merged' because we
        // load $options with the value from the context prior to including the
        // configuration file.  If the configuration file sets $option['special'][] = 'value',
        // then the configuration will be merged.  $option['special'] = array(...), on the
        // other hand, will replace rather than merge the values together.
        if ((array_key_exists($name, $options)) && (array_key_exists('merge', $info) || (array_key_exists('merge-pathlist', $info) || array_key_exists('merge-associative', $info)))) {
          $context = array_key_exists('context', $info) ? $info['context'] : $name;
          $cache =& drush_get_context($context);
          $value = $options[$name];
          if (!is_array($value) && array_key_exists('merge-pathlist', $info)) {
            $value = explode(PATH_SEPARATOR, $value);
          }
          if (array_key_exists('merge-associative', $info)) {
            foreach ($value as $subkey => $subvalue) {
              $cache[$subkey] = array_merge(isset($cache[$subkey]) ? $cache[$subkey] : array(), $subvalue);
            }
          }
          else {
            $cache = array_unique(array_merge($cache, $value));
          }
          // Once we have moved the option to its special context, we
          // can remove it from its option context -- unless 'propagate-cli-value'
          // is set, in which case we need to let it stick around in options
          // in case it is needed in backend invoke.
          if (!array_key_exists('propagate-cli-value', $info)) {
            unset($options[$name]);
          }
        }
      }
    }

    // If command-specific options were set and if we already have
    // a command, then apply the command-specific options immediately.
    if ($has_command_specific) {
      drush_command_default_options();
    }
  }
}

/**
 * Set a specific context.
 *
 * @param context
 *   Any of the default defined contexts.
 * @param value
 *   The value to store in the context
 *
 * @return
 *   An associative array of the settings specified in the request context.
 */
function drush_set_context($context, $value) {
  $cache =& drush_get_context($context);
  $cache = $value;
  return $value;
}


/**
 * Return a specific context, or the whole context cache
 *
 * This function provides a storage mechanism for any information
 * the currently running process might need to communicate.
 *
 * This avoids the use of globals, and constants.
 *
 * Functions that operate on the context cache, can retrieve a reference
 * to the context cache using :
 *     $cache = &drush_get_context($context);
 *
 * This is a private function, because it is meant as an internal
 * generalized API for writing static cache functions, not as a general
 * purpose function to be used inside commands.
 *
 * Code that modifies the reference directly might have unexpected consequences,
 * such as modifying the arguments after they have already been parsed and dispatched
 * to the callbacks.
 *
 * @param context
 *   Optional. Any of the default defined contexts.
 *
 * @return
 *   If context is not supplied, the entire context cache will be returned.
 *   Otherwise only the requested context will be returned.
 *   If the context does not exist yet, it will be initialized to an empty array.
 */
function &drush_get_context($context = NULL, $default = NULL) {
  static $cache = array();
  if (isset($context)) {
    if (!isset($cache[$context])) {
      $default = !isset($default) ? array() : $default;
      $cache[$context] = $default;
    }
    return $cache[$context];
  }
  return $cache;
}

/**
 * Set the arguments passed to the drush.php script.
 *
 * This function will set the 'arguments' context of the current running script.
 *
 * When initially called by drush_parse_args, the entire list of arguments will
 * be populated. Once the command is dispatched, this will be set to only the remaining
 * arguments to the command (i.e. the command name is removed).
 *
 * @param arguments
 *   Command line arguments, as an array.
 */
function drush_set_arguments($arguments) {
  drush_set_context('arguments', $arguments);
}

/**
 * Gets the command line arguments passed to Drush.
 *
 * @return array
 *   An indexed array of arguments. Until Drush has dispatched the command, the
 *   array will include the command name as the first element. After that point
 *   the array will not include the command name.
 *
 * @see drush_set_arguments()
 */
function drush_get_arguments() {
  return drush_get_context('arguments');
}

/**
 * Set the command being executed.
 *
 * Drush_dispatch will set the correct command based on it's
 * matching of the script arguments retrieved from drush_get_arguments
 * to the implemented commands specified by drush_get_commands.
 *
 * @param
 *   A numerically indexed array of command components.
 */
function drush_set_command($command) {
  drush_set_context('command', $command);
}

/**
 * Return the command being executed.
 */
function drush_get_command() {
  return drush_get_context('command');
}

/**
 * Get the value for an option.
 *
 * If the first argument is an array, then it checks whether one of the options
 * exists and return the value of the first one found. Useful for allowing both
 * -h and --host-name
 *
 * @param option
 *   The name of the option to get
 * @param default
 *   Optional. The value to return if the option has not been set
 * @param context
 *   Optional. The context to check for the option. If this is set, only this context will be searched.
 */
function drush_get_option($option, $default = NULL, $context = NULL) {
  $value = NULL;

  if ($context) {
    // We have a definite context to check for the presence of an option.
    $value = _drush_get_option($option, drush_get_context($context));
  }
  else {
    // We are not checking a specific context, so check them in a predefined order of precedence.
    $contexts = drush_context_names();

    foreach ($contexts as $context) {
      $value = _drush_get_option($option, drush_get_context($context));

      if ($value !== NULL) {
        return $value;
      }
    }
  }

  if ($value !== NULL) {
    return $value;
  }

  return $default;
}

/**
 * Get the value for an option and return it as a list.  If the
 * option in question is passed on the command line, its value should
 * be a comma-separated list (e.g. --flag=1,2,3).  If the option
 * was set in a drushrc.php file, then its value may be either a
 * comma-separated list or an array of values (e.g. $option['flag'] = array('1', '2', '3')).
 *
 * @param option
 *   The name of the option to get
 * @param default
 *   Optional. The value to return if the option has not been set
 * @param context
 *   Optional. The context to check for the option. If this is set, only this context will be searched.
 */
function drush_get_option_list($option, $default = array(), $context = NULL) {
  $result = drush_get_option($option, $default, $context);

  if (!is_array($result)) {
    $result = array_map('trim', array_filter(explode(',', $result)));
  }

  return $result;
}

/**
 * Get the value for an option, but first checks the provided option overrides.
 *
 * The feature of drush_get_option that allows a list of option names
 * to be passed in an array is NOT supported.
 *
 * @param option_overrides
 *   An array to check for values before calling drush_get_option.
 * @param option
 *   The name of the option to get.
 * @param default
 *   Optional. The value to return if the option has not been set.
 * @param context
 *   Optional. The context to check for the option. If this is set, only this context will be searched.
 *
 */
function drush_get_option_override($option_overrides, $option, $default = NULL, $context = NULL) {
  return drush_sitealias_get_option($option_overrides, $option, $default, '', $context);
}

/**
 * Get an option out of the specified alias.  If it has not been
 * set in the alias, then get it via drush_get_option.
 *
 * @param site_alias_record
 *   An array of options for an alias record.
 * @param option
 *   The name of the option to get.
 * @param default
 *   Optional. The value to return if the option does not exist in the site record and has not been set in a context.
 * @param context
 *   Optional. The context to check for the option. If this is set, only this context will be searched.
 */
function drush_sitealias_get_option($site_alias_record, $option, $default = NULL, $prefix = '', $context = NULL) {
  if (is_array($site_alias_record) && array_key_exists($option, $site_alias_record)) {
    return $site_alias_record[$option];
  }
  else {
    return drush_get_option($prefix . $option, $default, $context);
  }
}

/**
 * Get all of the values for an option in every context.
 *
 * @param option
 *   The name of the option to get
 * @return
 *   An array whose key is the context name and value is
 *   the specific value for the option in that context.
 */
function drush_get_context_options($option, $flatten = FALSE) {
  $result = array();

  $contexts = drush_context_names();
  foreach ($contexts as $context) {
    $value = _drush_get_option($option, drush_get_context($context));

    if ($value !== NULL) {
      if ($flatten && is_array($value)) {
        $result = array_merge($value, $result);
      }
      else {
        $result[$context] = $value;
      }
    }
  }

  return $result;
}

/**
 * Retrieves a collapsed list of all options.
 */
function drush_get_merged_options() {
  $contexts = drush_context_names();
  $cache = drush_get_context();
  $result = array();
  foreach (array_reverse($contexts) as $context) {
    if (array_key_exists($context, $cache)) {
      $result = array_merge($result, $cache[$context]);
    }
  }

  return $result;
}

/**
 * Retrieves a collapsed list of all options
 * with a specified prefix.
 */
function drush_get_merged_prefixed_options($prefix) {
  $merged_options = drush_get_merged_options();
  $result = array();
  foreach ($merged_options as $key => $value) {
    if ($prefix == substr($key, 0, strlen($prefix))) {
      $result[substr($key, strlen($prefix))] = $value;
    }
  }

  return $result;
}

/**
 * Helper function to recurse through possible option names
 */
function _drush_get_option($option, $context) {
  if (is_array($option)) {
    foreach ($option as $current) {
      $current_value = _drush_get_option($current, $context);
      if (isset($current_value)) {
        return $current_value;
      }
    }
  }
  elseif (array_key_exists('no-' . $option, $context)) {
    return FALSE;
  }
  elseif (array_key_exists($option, $context)) {
    return $context[$option];
  }

  return NULL;
}

/**
 * Set an option in one of the option contexts.
 *
 * @param option
 *   The option to set.
 * @param value
 *   The value to set it to.
 * @param context
 *   Optional. Which context to set it in.
 * @return
 *   The value parameter. This allows for neater code such as
 *     $myvalue = drush_set_option('http_host', $_SERVER['HTTP_HOST']);
 *   Without having to constantly type out the value parameter.
 */
function drush_set_option($option, $value, $context = 'process') {
  $cache =& drush_get_context($context);
  $cache[$option] = $value;
  return $value;
}

/**
 * A small helper function to set the value in the default context
 */
function drush_set_default($option, $value) {
  return drush_set_option($option, $value, 'default');
}

/**
 * Remove a setting from a specific context.
 *
 * @param
 *   Option to be unset
 * @param
 *   Context in which to unset the value in.
 */
function drush_unset_option($option, $context = NULL) {
  if ($context != NULL) {
    $cache =& drush_get_context($context);
    if (array_key_exists($option, $cache)) {
      unset($cache[$option]);
    }
  }
  else {
    $contexts = drush_context_names();

    foreach ($contexts as $context) {
      drush_unset_option($option, $context);
    }
  }
}

/**
 * Save the settings in a specific context to the applicable configuration file
 * This is useful is you want certain settings to be available automatically the next time a command is executed.
 *
 * @param $context
 *   The context to save
 */
function drush_save_config($context) {
  $filename = _drush_config_file($context);
  if (is_array($filename)) {
    $filename = $filename[0];
  }

  if ($filename) {
    $cache = drush_get_context($context);

    $fp = fopen($filename, "w+");
    if (!$fp) {
      return drush_set_error('DRUSH_PERM_ERROR', dt('Drushrc (!filename) could not be written', array('!filename' => $filename)));
    }
    else {
      fwrite($fp, "<?php\n");
      foreach ($cache as $key => $value) {
        $line = "\n\$options['$key'] = ". var_export($value, TRUE) .';';
        fwrite($fp, $line);
      }
      fwrite($fp, "\n");
      fclose($fp);
      drush_log(dt('Drushrc file (!filename) was written successfully', array('!filename' => $filename)));
      return TRUE;
    }

  }
  return FALSE;
}
<?php

/**
 * @file
 * Wrappers to abstract database operations from Drupal version.
 */

/**
 * @defgroup dbfunctions Database convenience functions.
 * @{
 */

/**
 * Replace named placeholders in a WHERE snippet.
 *
 * Helper function to allow the usage of Drupal 7+ WHERE snippets
 * with named placeholders in code for Drupal 6.
 *
 * @param $where
 *   String with a WHERE snippet using named placeholders.
 * @param $args
 *   Array of placeholder values.
 * @return
 *   String. $where filled with literals from $args.
 */
function _drush_replace_query_placeholders($where, $args) {
  foreach ($args as $key => $data) {
    if (is_array($data)) {
      $new_keys = array();
      // $data can't have keys that are a prefix of other keys to
      // prevent a corrupted result in the below calls to str_replace().
      // To avoid this we will use a zero padded indexed array of the values of $data.
      $pad_length = strlen((string)count(array_values($data)));
      foreach (array_values($data) as $i => $value) {
        if (!is_numeric($value)) {
          $value = "'".$value."'";
        }
        $new_keys[$key . '_' . str_pad($i, $pad_length, '0', STR_PAD_LEFT)] = $value;
      }
      $where = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $where);
      unset($args[$key]);
      $args += $new_keys;
    }
    else if (!is_numeric($data)) {
      $args[$key] = "'".$data."'";
    }
  }

  foreach ($args as $key => $data) {
    $where = str_replace($key, $data, $where);
  }

  return $where;
}

/**
 * A db_select() that works for any version of Drupal.
 *
 * @param $table
 *   String. The table to operate on.
 * @param $fields
 *   Array or string. Fields affected in this operation. Valid string values are '*' or a single column name.
 * @param $where
 *   String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders()
 * @param $args
 *   Array. Arguments for the WHERE snippet.
 * @param $start
 *   Int. Value for OFFSET.
 * @param $length
 *   Int. Value for LIMIT.
 * @param $order_by_field
 *   String. Database column to order by.
 * @param $order_by_direction
 *   ('ASC', 'DESC'). Ordering direction.
 * @return
 *   A database resource.
 */
function drush_db_select($table, $fields = '*', $where = NULL, $args = NULL, $start = NULL, $length = NULL, $order_by_field = NULL, $order_by_direction = 'ASC') {
  if (drush_drupal_major_version() >= 7) {
    if (!is_array($fields)) {
      if ($fields == '*') {
        $fields = array();
      }
      else {
        $fields = array($fields);
      }
    }
    $query = db_select($table, $table)
      ->fields($table, $fields);
    if (!empty($where)) {
      $query = $query->where($where, $args);
    }
    if (isset($order_by_field)) {
      $query = $query->orderBy($order_by_field, $order_by_direction);
    }
    if (isset($length)) {
      $query = $query->range($start, $length);
    }
    return $query->execute();
  }
  else {
    if (is_array($fields)) {
      $fields = implode(', ', $fields);
    }
    $query = "SELECT $fields FROM {{$table}}";
    if (!empty($where)) {
      $where = _drush_replace_query_placeholders($where, $args);
      $query .= " WHERE ".$where;
    }
    if (isset($order_by_field)) {
      $query .= " ORDER BY $order_by_field $order_by_direction";
    }
    if (isset($length)) {
      $sql = drush_sql_get_class();
      $db_scheme = $sql->scheme();
      if ($db_scheme == 'oracle')
        return db_query_range($query, $start, $length);
      else {
	      $limit = " LIMIT $length";
	      if (isset($start)) {
          $limit .= " OFFSET $start";
	      }
	      $query .= $limit;
      }
    }

    return db_query($query, $args);
  }
}

/**
 * A db_delete() that works for any version of Drupal.
 *
 * @param $table
 *   String. The table to operate on.
 * @param $where
 *   String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders()
 * @param $args
 *   Array. Arguments for the WHERE snippet.
 * @return
 *   Affected rows (except on D7+mysql without a WHERE clause - returns TRUE) or FALSE.
 */
function drush_db_delete($table, $where = NULL, $args = NULL) {
  if (drush_drupal_major_version() >= 7) {
    if (!empty($where)) {
      $query = db_delete($table)->where($where, $args);
      return $query->execute();
    }
    else {
      return db_truncate($table)->execute();
    }
  }
  else {
    $query = "DELETE FROM {{$table}}";
    if (!empty($where)) {
      $where = _drush_replace_query_placeholders($where, $args);
      $query .= ' WHERE '.$where;
    }
    if (!db_query($query, $args)) {
      return FALSE;
    }
    return db_affected_rows();
  }
}

/**
 * A db_result() that works consistently for any version of Drupal.
 *
 * @param
 *   A Database result object.
 */
function drush_db_result($result) {
  switch (drush_drupal_major_version()) {
    case 6:
      return db_result($result);
    case 7:
    default:
      return $result->fetchField();
  }
}

/**
 * A db_fetch_object() that works for any version of Drupal.
 *
 * @param
 *   A Database result object.
 */
function drush_db_fetch_object($result) {
  return drush_drupal_major_version() >= 7 ? $result->fetchObject() : db_fetch_object($result);
}

/**
 * @} End of "defgroup dbfunctions".
 */
<?php

/**
 * @file
 * Utility functions related to Drupal.
 */

use Drush\Log\LogLevel;

/**
 * Loads the Drupal autoloader and returns the instance.
 */
function drush_drupal_load_autoloader($drupal_root) {
  static $autoloader = FALSE;

  if (!$autoloader) {
    $autoloader = require $drupal_root .'/autoload.php';
    if ($autoloader === TRUE) {
      // The autoloader was already require(). Assume that Drush and Drupal share an autoloader per
      // "Point autoload.php to the proper vendor directory" - https://www.drupal.org/node/2404989
      $autoloader = drush_get_context('DRUSH_CLASSLOADER');
    }
  }
  return $autoloader;
}

/**
 * Detects the version number of the current Drupal installation,
 * if any. Returns FALSE if there is no current Drupal installation,
 * or it is somehow broken.
 *
 * @return
 *   A string containing the version number of the current
 *   Drupal installation, if any. Otherwise, return FALSE.
 */
function drush_drupal_version($drupal_root = NULL) {
  static $version = FALSE;

  if (!$version) {
    if (($drupal_root != NULL) || ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'))) {
      $bootstrap = drush_bootstrap_class_for_root($drupal_root);
      if ($bootstrap) {
        $version = $bootstrap->get_version($drupal_root);
      }
    }
  }
  return $version;
}

function drush_drupal_cache_clear_all() {
  if (drush_drupal_major_version() >= 8) {
    drush_invoke_process('@self', 'cache-rebuild');
  }
  else {
    drush_invoke_process('@self', 'cache-clear', array('all'));
  }
}

/**
 * Returns the Drupal major version number (6, 7, 8 ...)
 */
function drush_drupal_major_version($drupal_root = NULL) {
  $major_version = FALSE;
  if ($version = drush_drupal_version($drupal_root)) {
    $version_parts = explode('.', $version);
    if (is_numeric($version_parts[0])) {
      $major_version = (integer)$version_parts[0];
    }
  }
  return $major_version;
}

/**
 * Log Drupal watchdog() calls.
 *
 * A sneaky implementation of hook_watchdog(), for D6/D7.
 */
function system_watchdog($log_entry) {
  // Transform non informative severity levels to 'error' for compatibility with _drush_print_log.
  // Other severity levels are coincident with the ones we use in drush.
  if (drush_drupal_major_version() >= 6 && $log_entry['severity'] <= 2) {
    $severity = 'error';
  }
  else {
    drush_include_engine('drupal', 'environment');
    $levels = drush_watchdog_severity_levels();
    $severity = $levels[$log_entry['severity']];
  }
  // Format the message.
  if (is_array($log_entry['variables'])) {
    $message = strtr($log_entry['message'], $log_entry['variables']);
  }
  else {
    $message = $log_entry['message'];
  }

  // decode_entities() only loaded after FULL bootstrap.
  if (function_exists('decode_entities')) {
    $message = decode_entities($message);
  }
  $message = strip_tags($message);

  // Log or print or ignore. Just printing saves memory but thats rarely needed.
  switch (drush_get_option('watchdog', 'log')) {
    case 'log':
      drush_log('WD '. $log_entry['type'] . ': ' . $message, $severity);
      break;
    case 'print':
      // Disable in backend mode since it logs output and the goal is to conserve memory.
      // @see _drush_bootstrap_drush().
      if (ob_get_length() === FALSE) {
        drush_print('WD '. $severity . ' ' . $log_entry['type'] . ': ' . $message);
      }
      break;
    default:
      // Do nothing.
  }
}

/**
 * Log the return value of Drupal hook_update_n functions.
 *
 * This is used during install and update to log the output
 * of the update process to the logging system.
 */
function _drush_log_update_sql($ret) {
  if (count($ret)) {
    foreach ($ret as $info) {
      if (is_array($info)) {
        if (!$info['success']) {
          drush_set_error('DRUPAL_UPDATE_FAILED', $info['query']);
        }
        else {
          drush_log($info['query'], ($info['success']) ? LogLevel::SUCCESS : LogLevel::ERROR);
        }
      }
    }
  }
}

function drush_find_profiles($drupal_root , $key = 'name') {
  return drush_scan_directory($drupal_root . '/profiles', "/.*\.profile$/", array('.', '..', 'CVS', 'tests'), 0, 2, $key);
}

/**
 * Parse Drupal info file format.
 *
 * Copied with modifications from includes/common.inc.
 *
 * @see drupal_parse_info_file
 */
function drush_drupal_parse_info_file($filename) {
  if (!file_exists($filename)) {
    return array();
  }

  $data = file_get_contents($filename);
  return _drush_drupal_parse_info_file($data);
}

/**
 * Parse the info file.
 */
function _drush_drupal_parse_info_file($data, $merge_item = NULL) {
  if (!$data) {
    return FALSE;
  }

  if (preg_match_all('
    @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
    ((?:
      [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
      \[[^\[\]]*\]                  # unless they are balanced and not nested
    )+?)
    \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
    (?:
      ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
      (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
      ([^\r\n]*?)                   # Non-quoted string
    )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
    @msx', $data, $matches, PREG_SET_ORDER)) {
    $info = array();
    foreach ($matches as $match) {
      // Fetch the key and value string.
      $i = 0;
      foreach (array('key', 'value1', 'value2', 'value3') as $var) {
        $$var = isset($match[++$i]) ? $match[$i] : '';
      }
      $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;

      // Parse array syntax.
      $keys = preg_split('/\]?\[/', rtrim($key, ']'));
      $last = array_pop($keys);
      $parent = &$info;

      // Create nested arrays.
      foreach ($keys as $key) {
        if ($key == '') {
          $key = count($parent);
        }
        if (isset($merge_item) && isset($parent[$key]) && !is_array($parent[$key])) {
          $parent[$key] = array($merge_item => $parent[$key]);
        }
        if (!isset($parent[$key]) || !is_array($parent[$key])) {
          $parent[$key] = array();
        }
        $parent = &$parent[$key];
      }

      // Handle PHP constants.
      if (defined($value)) {
        $value = constant($value);
      }

      // Insert actual value.
      if ($last == '') {
        $last = count($parent);
      }
      if (isset($merge_item) && isset($parent[$last]) && is_array($parent[$last])) {
        $parent[$last][$merge_item] = $value;
      }
      else {
        $parent[$last] = $value;
      }
    }
    return $info;
  }
  return FALSE;
}

/**
 * Build a cache id to store the install_profile for a given site.
 */
function drush_cid_install_profile() {
  return drush_get_cid('install_profile', array(), array(drush_get_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH')));
}

/*
 * An array of options shared by sql-sanitize and sql-sync commands.
 */
function drupal_sanitize_options() {
  return array(
    'sanitize-password' => array(
      'description' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged.',
      'example-value' => 'password',
      'value' => 'required',
    ),
    'sanitize-email' => array(
      'description' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged.  May contain replacement patterns %uid, %mail or %name.',
      'example-value' => 'user+%uid@localhost',
      'value' => 'required',
    ),
  );
}
<?php

/**
 * @file
 * The drush API implementation and helpers.
 */

use Drush\Log\Logger;
use Drush\Log\LogLevel;
use Psr\Log\LoggerInterface;

/**
 * @name Error status definitions
 * @{
 * Error code definitions for interpreting the current error status.
 * @see drush_set_error(), drush_get_error(), drush_get_error_log(), drush_cmp_error()
 */

/** The command completed successfully. */
define('DRUSH_SUCCESS', 0);
/** The command could not be completed because the framework has specified errors that have occured. */
define('DRUSH_FRAMEWORK_ERROR', 1);
/** 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 meaningful. */
define('DRUSH_EXITCODE_USER_ABORT', 75);
/** The command that was executed resulted in an application error,
The most commom causes for this is invalid PHP or a broken SSH
pipe when using drush_backend_invoke in a distributed manner. */
define('DRUSH_APPLICATION_ERROR', 255);

/**
 * @} End of "name Error status defintions".
 */

/**
 * The number of bytes in a kilobyte. Copied from Drupal.
 */
define('DRUSH_KILOBYTE', 1024);

/**
 * Default amount of time, in seconds, to cache downloads via
 * drush_download_file(). One day is 86400 seconds.
 */
define('DRUSH_CACHE_LIFETIME_DEFAULT', 86400);

/**
 * Include a file, selecting a version specific file if available.
 *
 * For example, if you pass the path "/var/drush" and the name
 * "update" when bootstrapped on a Drupal 6 site it will first check for
 * the presence of "/var/drush/update_6.inc" in include it if exists. If this
 * file does NOT exist it will proceed and check for "/var/drush/update.inc".
 * If neither file exists, it will return FALSE.
 *
 * @param $path
 *   The path you want to search.
 * @param $name
 *   The file base name you want to include (not including a version suffix
 *   or extension).
 * @param $version
 *   The version suffix you want to include (could be specific to the software
 *   or platform your are connecting to) - defaults to the current Drupal core
 *   major version.
 * @param $extension
 *   The extension - defaults to ".inc".
 *
 * @return
 *   TRUE if the file was found and included.
 */
function drush_include($path, $name, $version = NULL, $extension = 'inc') {
  $name = str_replace('-', '_', $name);
  $version = ($version) ? $version : drush_drupal_major_version();

  $file = sprintf("%s/%s_%s.%s", $path, $name, $version, $extension);
  if (file_exists($file)) {
    include_once($file);
    return TRUE;
  }
  $file = sprintf("%s/%s.%s", $path, $name, $extension);
  if (file_exists($file)) {
    include_once($file);
    return TRUE;
  }

  return drush_set_error('DRUSH_INCLUDE_NO_PATH', dt('Unable to include file !name!version!extension or !name!extension from !path.', array('!name' => $name, '!version' => $version, '!extension' => $extension)));
}

/**
 * Provide a version-specific class instance.
 *
 * @param $class_name
 *   The name of the class to instantiate.  Appends the Drupal
 *   major version number to the end of the class name before instantiation.
 * @param $constructor_args
 *   An array of arguments to pass to the class constructor.
 *
 * Example wrapper class to instantiate a widget, called with the
 * arguments for the WIDGET_CLASS constructor:
 *
 *  function drush_WIDGET_CLASS_get_class($widgetName, $widgetStyle) {
 *    retrun drush_get_class('Widget_Class', func_get_args()));
 *  }
 */

function drush_get_class($class_name, $constructor_args = array(), $variations = array()) {
  if (empty($variations)) {
    $variations[] = drush_drupal_major_version();
  }
  $class_names = is_array($class_name) ? $class_name : array($class_name);
  foreach ($class_names as $class_name) {
    for ($i=count($variations); $i >= 0; $i--) {
      $variant_class_name = $class_name . implode('', array_slice($variations, 0, $i));
      if (class_exists($variant_class_name)) {
        $reflectionClass = new ReflectionClass($variant_class_name);
        return !empty($constructor_args) ? $reflectionClass->newInstanceArgs($constructor_args) : $reflectionClass->newInstanceArgs();
      }
    }
  }
  // Something bad happenned. TODO Exception?
  return drush_set_error('DRUSH_GET_CLASS_ERROR', dt('Unable to load class !class', array('!class' => $class_name)));
}

/**
 * Generate an .ini file. used by archive-dump."
 *
 * @param array $ini
 *   A two dimensional associative array where top level are sections and
 *   second level are key => value pairs.
 *
 * @return string
 *   .ini formatted text.
 */
function drush_export_ini($ini) {
  $output = '';
  foreach ($ini as $section => $pairs) {
    if ($section) {
      $output .= "[$section]\n";
    }

    foreach ($pairs as $k => $v) {
      if ($v) {
        $output .= "$k = \"$v\"\n";
      }
    }
  }
  return $output;
}

/**
 * Generate code friendly to the Drupal .info format from a structured array.
 * Mostly copied from http://drupalcode.org/viewvc/drupal/contributions/modules/features/features.export.inc.
 *
 * @param $info
 *   An array or single value to put in a module's .info file.
 *
 * @param boolean $integer_keys
 *   Use integer in keys.
 *
 * @param $parents
 *   Array of parent keys (internal use only).
 *
 * @return
 *   A code string ready to be written to a module's .info file.
 */
function drush_export_info($info, $integer_keys = FALSE, $parents = array()) {
  $output = '';
  if (is_array($info)) {
    foreach ($info as $k => $v) {
      $child = $parents;
      $child[] = $k;
      $output .= drush_export_info($v, $integer_keys, $child);
    }
  }
  else if (!empty($info) && count($parents)) {
    $line = array_shift($parents);
    foreach ($parents as $key) {
      $line .= (!$integer_keys && is_numeric($key)) ? "[]" : "[{$key}]";
    }
    $line .=  " = \"{$info}\"\n";
    return $line;
  }
  return $output;
}

/**
 * Convert a csv string, or an array of items which
 * may contain csv strings, into an array of items.
 *
 * @param $args
 *   A simple csv string; e.g. 'a,b,c'
 *   or a simple list of items; e.g. array('a','b','c')
 *   or some combination; e.g. array('a,b','c') or array('a,','b,','c,')
 *
 * @returns array
 *   A simple list of items (e.g. array('a','b','c')
 */
function _convert_csv_to_array($args) {
  //
  // Step 1: implode(',',$args) converts from, say, array('a,','b,','c,') to 'a,,b,,c,'
  // Step 2: explode(',', ...) converts to array('a','','b','','c','')
  // Step 3: array_filter(...) removes the empty items
  // Step 4: array_map(...) trims extra whitespace from each item
  // (handles csv strings with extra whitespace, e.g. 'a, b, c')
  //
  return array_map('trim', array_filter(explode(',', is_array($args) ? implode(',',$args) : $args)));
}

/**
 * Convert a nested array into a flat array.  Thows away
 * the array keys, returning only the values.
 *
 * @param $args
 *   An array that may potentially be nested.
 *   e.g. array('a', array('b', 'c'))
 *
 * @returns array
 *   A simple list of items (e.g. array('a','b','c')
 */
function drush_flatten_array($a) {
  $result = array();
  if (!is_array($a)) {
    return array($a);
  }
  foreach ($a as $value) {
    $result = array_merge($result, drush_flatten_array($value));
  }
  return $result;
}

/**
 * Get the available global options. Used by help command. Command files may
 * modify this list using hook_drush_help_alter().
 *
 * @param boolean $brief
 *   Return a reduced set of important options. Used by help command.
 *
 * @return
 *   An associative array containing the option definition as the key,
 *   and a descriptive array for each of the available options.  The array
 *   elements for each item are:
 *
 *     - short-form: The shortcut form for specifying the key on the commandline.
 *     - propagate: backend invoke will use drush_get_option to propagate this
 *       option, when set, to any other Drush command that is called.
 *     - context: The drush context where the value of this item is cached.  Used
 *       by backend invoke to propagate values set in code.
 *     - never-post: If TRUE, backend invoke will never POST this item's value
 *       on STDIN; it will always be sent as a commandline option.
 *     - never-propagate: If TRUE, backend invoke will never pass this item on
 *       to the subcommand being executed.
 *     - local-context-only: Backend invoke will only pass this value on for local calls.
 *     - merge: For options such as $options['shell-aliases'] that consist of an array
 *       of items, make a merged array that contains all of the values specified for
 *       all of the contexts (config files) where the option is defined.  The value is stored in
 *       the specified 'context', or in a context named after the option itself if the
 *       context flag is not specified.
 *       IMPORTANT: When the merge flag is used, the option value must be obtained via
 *       drush_get_context('option') rather than drush_get_option('option').
 *     - merge-pathlist: For options such as --include and --config, make a merged list
 *       of options from all contexts; works like the 'merge' flag, but also handles string
 *       values separated by the PATH_SEPARATOR.
 *     - merge-associative: Like 'merge-pathlist', but key values are preserved.
 *     - propagate-cli-value: Used to tell backend invoke to include the value for
 *       this item as specified on the cli.  This can either override 'context'
 *       (e.g., propagate --include from cli value instead of DRUSH_INCLUDE context),
 *       or for an independent global setting (e.g. --user)
 *     - description: The help text for this item. displayed by `drush help`.
 */
function drush_get_global_options($brief = FALSE) {
  $options['root']               = array('short-form' => 'r', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => 'path');
  $options['uri']                = array('short-form' => 'l', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888');
  $options['verbose']            = array('short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.');
  $options['debug']              = array('short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information, including internal messages.');
  $options['yes']                = array('short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts.");
  $options['no']                 = array('short-form' => 'n', 'context' => 'DRUSH_NEGATIVE', 'description' => "Assume 'no' as answer to all prompts.");
  $options['simulate']           = array('short-form' => 's', 'context' => 'DRUSH_SIMULATE', 'never-propagate' => TRUE, 'description' => "Simulate all relevant actions (don't actually change the system).");
  $options['pipe']               = array('short-form' => 'p', 'hidden' => TRUE, 'description' => "Emit a compact representation of the command for scripting.");
  $options['help']               = array('short-form' => 'h', 'description' => "This help system.");

  if (!$brief) {
    $options['version']            = array('description' => "Show drush version.");
    $options['php']                = array('description' => "The absolute path to your PHP interpreter, if not 'php' in the path.", 'example-value' => '/path/to/file', 'never-propagate' => TRUE);
    $options['interactive']        = array('short-form' => 'ia', 'description' => "Force interactive mode for commands run on multiple targets (e.g. `drush @site1,@site2 cc --ia`).", 'never-propagate' => TRUE);
    $options['tty']                = array('hidden' => TRUE, 'description' => "Force allocation of tty for remote commands", 'never-propagate' => TRUE);
    $options['quiet']               = array('short-form' => 'q', 'description' => 'Suppress non-error messages.');
    $options['include']             = array('short-form' => 'i', 'short-has-arg' => TRUE, 'context' => 'DRUSH_INCLUDE', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of additional directory paths to search for drush commands.", 'example-value' => '/path/dir');
    $options['exclude']             = array('propagate-cli-value' => TRUE, 'never-post' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of files and directory paths to exclude from consideration when searching for drush commandfiles.", 'example-value' => '/path/dir');
    $options['config']              = array('short-form' => 'c', 'short-has-arg' => TRUE, 'context' => 'DRUSH_CONFIG', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "Specify an additional config file to load. See example.drushrc.php.", 'example-value' => '/path/file');
    $options['user']                = array('short-form' => 'u', 'short-has-arg' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specify a Drupal user to login with. May be a name or a number.", 'example-value' => 'name_or_number');
    $options['backend']             = array('short-form' => 'b', 'never-propagate' => TRUE, 'description' => "Hide all output and return structured data.");
    $options['choice']              = array('description' => "Provide an answer to a multiple-choice prompt.", 'example-value' => 'number');
    $options['variables']           = array('description' => "Comma delimited list of name=value pairs. These values take precedence even over settings.php variable overrides.", 'example-value' => 'foo=bar,baz=yaz');
    $options['search-depth']        = array('description' => "Control the depth that drush will search for alias files.", 'example-value' => 'number');
    $options['ignored-modules']     = array('description' => "Exclude some modules from consideration when searching for drush command files.", 'example-value' => 'token,views');
    $options['no-label']            = array('description' => "Remove the site label that drush includes in multi-site command output (e.g. `drush @site1,@site2 status`).");
    $options['label-separator']     = array('description' => "Specify the separator to use in multi-site command output (e.g. `drush @sites pm-list --label-separator=',' --format=csv`).");
    $options['nocolor']             = array('context' => 'DRUSH_NOCOLOR', 'propagate-cli-value' => TRUE, 'description' => "Suppress color highlighting on log messages.");
    $options['show-passwords']      = array('description' => "Show database passwords in commands that display connection information.");
    $options['show-invoke']         = array('description' => "Show all function names which could have been called for the current command. See drush_invoke().");
    $options['watchdog']            = array('description' => "Control logging of Drupal's watchdog() to drush log. Recognized values are 'log', 'print', 'disabled'. Defaults to log. 'print' shows calls to admin but does not add them to the log.", 'example-value' => 'print');
    $options['cache-default-class'] = array('description' => "A cache backend class that implements CacheInterface. Defaults to JSONCache.", 'example-value' => 'JSONCache');
    $options['cache-class-<bin>']   = array('description' => "A cache backend class that implements CacheInterface to use for a specific cache bin.", 'example-value' => 'className');
    $options['early']               = array('description' => "Include a file (with relative or full path) and call the drush_early_hook() function (where 'hook' is the filename). The function is called pre-bootstrap and offers an opportunity to alter the drush bootstrap environment or process (returning FALSE from the function will continue the bootstrap), or return output very rapidly (e.g. from caches). See includes/complete.inc for an example.");
    $options['alias-path']          = array('context' => 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files.", 'example-value' => '/path/alias1:/path/alias2');
    $options['backup-location']     = array('description' => "Specifies the directory where drush will store backups.", 'example-value' => '/path/to/dir');
    $options['confirm-rollback']    = array('description' => 'Wait for confirmation before doing a rollback when something goes wrong.');
    $options['complete-debug']      = array('hidden' => TRUE, 'description' => "Turn on debug mode forf completion code");
    $options['php-options']         = array('description' => "Options to pass to `php` when running drush.  Only effective when using the drush.launcher script.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"');
    $options['halt-on-error']       = array('propagate-cli-value' => TRUE, 'description' => "Controls error handling of recoverable errors (E_RECOVERABLE_ERROR). --halt-on-error=1: Execution is halted. --halt-on-error=0: Drush execution continues");
    $options['deferred-sanitization'] = array('hidden' => TRUE, 'description' => "Defer calculating the sanitization operations until after the database has been copied. This is done automatically if the source database is remote.");
    $options['remote-host']         = array('hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.');
    $options['remote-user']         = array('hidden' => TRUE, 'description' => 'User account to use with a remote drush command. Managed by site alias.');
    $options['remote-os']           = array('hidden' => TRUE, 'description' => 'The operating system used on the remote host. Managed by site alias.');
    $options['site-list']           = array('hidden' => TRUE, 'description' => 'List of sites to run commands on. Managed by site alias.');
    $options['reserve-margin']      = array('hidden' => TRUE, 'description' => 'Remove columns from formatted opions. Managed by multi-site command handling.');
    $options['strict']              = array('propagate' => TRUE, 'description' => 'Return an error on unrecognized options. --strict=0: Allow unrecognized options.  --strict=2: Also return an error on any "warning" log messages.  Optional.  Default is 1.');
    $options['command-specific']    = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'Command-specific options.');
    $options['site-aliases']        = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'List of site aliases.');
    $options['shell-aliases']       = array('hidden' => TRUE, 'merge' => TRUE, 'never-propagate' => TRUE, 'description' => 'List of shell aliases.');
    $options['path-aliases']        = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Path aliases from site alias.');
    $options['ssh-options']         = array('never-propagate' => TRUE, 'description' => 'A string of extra options that will be passed to the ssh command', 'example-value' => '-p 100');
    $options['editor']              = array('never-propagate' => TRUE, 'description' => 'A string of bash which launches user\'s preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.', 'example-value' => 'vi');
    $options['bg']                  = array('never-propagate' => TRUE, 'description' => 'Run editor in the background. Does not work with editors such as `vi` that run in the terminal. Supresses config-import at the end.');
    $options['db-url']              = array('hidden' => TRUE, 'description' => 'A Drupal 6 style database URL. Used by various commands.', 'example-value' => 'mysql://root:pass@127.0.0.1/db');
    $options['drush-coverage']      = array('hidden' => TRUE, 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'description' => 'File to save code coverage data into.');
    $options['redirect-port']       = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Used by the user-login command to specify the redirect port on the local machine; it therefore would not do to pass this to the remote machines.');
    $options['cache-clear']         = array('propagate' => TRUE, 'description' => 'If 0, Drush skips normal cache clearing; the caller should then clear if needed.', 'example-value' => '0', );
    $options['local']               = array('propagate' => TRUE, 'description' => 'Don\'t look in global locations for commandfiles, config, and site aliases');
    $options['ignored-directories'] = array('propagate' => TRUE, 'description' => "Exclude directories when searching for files in drush_scan_directory().", 'example-value' => 'node_modules,bower_components');

    $command = array(
        'options' => $options,
        '#brief' => FALSE,
      ) + drush_command_defaults('global-options', 'global_options', __FILE__);
    drush_command_invoke_all_ref('drush_help_alter', $command);

    $options = $command['options'];
  }
  return $options;
}

/**
 * Exits with a message. In general, you should use drush_set_error() instead of
 * this function. That lets drush proceed with other tasks.
 * TODO: Exit with a correct status code.
 */
function drush_die($msg = NULL, $status = NULL) {
  die($msg ? "drush: $msg\n" : '');
}

/**
 * Check to see if the provided line is a "#!/usr/bin/env drush"
 * "shebang" script line.
 */
function _drush_is_drush_shebang_line($line) {
  return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE));
}

/**
 * Check to see if the provided script file is a "#!/usr/bin/env drush"
 * "shebang" script line.
 */
function _drush_is_drush_shebang_script($script_filename) {
  $result = FALSE;

  if (file_exists($script_filename)) {
    $fp = fopen($script_filename, "r");
    if ($fp !== FALSE) {
      $line = fgets($fp);
      $result = _drush_is_drush_shebang_line($line);
      fclose($fp);
    }
  }

  return $result;
}

/**
 * @defgroup userinput Get input from the user.
 * @{
 */

/**
 * Asks the user a basic yes/no question.
 *
 * @param string $msg
 *   The question to ask.
 * @param int $indent
 *   The number of spaces to indent the message.
 *
 * @return bool
 *   TRUE if the user enters "y" or FALSE if "n".
 */
function drush_confirm($msg, $indent = 0) {
  drush_print_prompt((string)$msg . " (y/n): ", $indent);

  // Automatically accept confirmations if the --yes argument was supplied.
  if (drush_get_context('DRUSH_AFFIRMATIVE')) {
    drush_print("y");
    return TRUE;
  }
  // Automatically cancel confirmations if the --no argument was supplied.
  elseif (drush_get_context('DRUSH_NEGATIVE')) {
    drush_print("n");
    return FALSE;
  }
  // See http://drupal.org/node/499758 before changing this.
  $stdin = fopen("php://stdin","r");

  while ($line = fgets($stdin)) {
    $line = trim($line);
    if ($line == 'y') {
      return TRUE;
    }
    if ($line == 'n') {
      return FALSE;
    }
    drush_print_prompt((string)$msg . " (y/n): ", $indent);
  }
}

/**
 * Ask the user to select an item from a list.
 * From a provided associative array, drush_choice will
 * display all of the questions, numbered from 1 to N,
 * and return the item the user selected. "0" is always
 * cancel; entering a blank line is also interpreted
 * as cancelling.
 *
 * @param $options
 *   A list of questions to display to the user.  The
 *   KEYS of the array are the result codes to return to the
 *   caller; the VALUES are the messages to display on
 *   each line. Special keys of the form '-- something --' can be
 *   provided as separator between choices groups. Separator keys
 *    don't alter the numbering.
 * @param $prompt
 *   The message to display to the user prompting for input.
 * @param $label
 *   Controls the display of each line.  Defaults to
 *   '!value', which displays the value of each item
 *   in the $options array to the user.  Use '!key' to
 *   display the key instead.  In some instances, it may
 *   be useful to display both the key and the value; for
 *   example, if the key is a user id and the value is the
 *   user name, use '!value (uid=!key)'.
 */
function drush_choice($options, $prompt = 'Enter a number.', $label = '!value', $widths = array()) {
  drush_print(dt($prompt));

  // Preflight so that all rows will be padded out to the same number of columns
  $array_pad = 0;
  foreach ($options as $key => $option) {
    if (is_array($option) && (count($option) > $array_pad)) {
      $array_pad = count($option);
    }
  }

  $rows[] = array_pad(array('[0]', ':', 'Cancel'), $array_pad + 2, '');
  $selection_number = 0;
  foreach ($options as $key => $option) {
    if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) {
      $rows[] = array_pad(array('', '', $option), $array_pad + 2, '');
    }
    else {
      $selection_number++;
      $row = array("[$selection_number]", ':');
      if (is_array($option)) {
        $row = array_merge($row, $option);
      }
      else {
        $row[] = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option));
      }
      $rows[] = $row;
      $selection_list[$selection_number] = $key;
    }
  }
  drush_print_table($rows, FALSE, $widths);
  drush_print_pipe(array_keys($options));

  // If the user specified --choice, then make an
  // automatic selection.  Cancel if the choice is
  // not an available option.
  if (($choice = drush_get_option('choice', FALSE)) !== FALSE) {
    // First check to see if $choice is one of the symbolic options
    if (array_key_exists($choice, $options)) {
      return $choice;
    }
    // Next handle numeric selections
    elseif (array_key_exists($choice, $selection_list)) {
      return $selection_list[$choice];
    }
    return FALSE;
  }

  // If the user specified --no, then cancel; also avoid
  // getting hung up waiting for user input in --pipe and
  // backend modes.  If none of these apply, then wait,
  // for user input and return the selected result.
  if (!drush_get_context('DRUSH_NEGATIVE') && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_PIPE')) {
    while ($line = trim(fgets(STDIN))) {
      if (array_key_exists($line, $selection_list)) {
        return $selection_list[$line];
      }
    }
  }
  // We will allow --yes to confirm input if there is only
  // one choice; otherwise, --yes will cancel to avoid ambiguity
  if (drush_get_context('DRUSH_AFFIRMATIVE')  && (count($options) == 1)) {
    return $selection_list[1];
  }
  return FALSE;
}

/**
 * Ask the user to select multiple items from a list.
 * This is a wrapper around drush_choice, that repeats the selection process,
 * allowing users to toggle a number of items in a list. The number of values
 * that can be constrained by both min and max: the user will only be allowed
 * finalize selection once the minimum number has been selected, and the oldest
 * selected value will "drop off" the list, if they exceed the maximum number.
 *
 * @param $options
 *   Same as drush_choice() (see above).
 * @param $defaults
 *   This can take 3 forms:
 *   - FALSE: (Default) All options are unselected by default.
 *   - TRUE: All options are selected by default.
 *   - Array of $options keys to be selected by default.
 * @param $prompt
 *   Same as drush_choice() (see above).
 * @param $label
 *   Same as drush_choice() (see above).
 * @param $mark
 *   Controls how selected values are marked.  Defaults to '!value (selected)'.
 * @param $min
 *   Constraint on minimum number of selections. Defaults to zero. When fewer
 *   options than this are selected, no final options will be available.
 * @param $max
 *   Constraint on minimum number of selections. Defaults to NULL (unlimited).
 *   If the a new selection causes this value to be exceeded, the oldest
 *   previously selected value is automatically unselected.
 * @param $final_options
 *   An array of additional options in the same format as $options.
 *   When the minimum number of selections is met, this array is merged into the
 *   array of options. If the user selects one of these values and the
 *   selection process will complete (the key for the final option is included
 *   in the return value). If this is an empty array (default), then a built in
 *   final option of "Done" will be added to the available options (in this case
 *   no additional keys are added to the return value).
 */
function drush_choice_multiple($options, $defaults = FALSE, $prompt = 'Select some numbers.', $label = '!value', $mark = '!value (selected)', $min = 0, $max = NULL, $final_options = array()) {
  $selections = array();
  // Load default selections.
  if (is_array($defaults)) {
    $selections = $defaults;
  }
  elseif ($defaults === TRUE) {
    $selections = array_keys($options);
  }
  $complete = FALSE;
  $final_builtin = array();
  if (empty($final_options)) {
    $final_builtin['done'] = dt('Done');
  }
  $final_options_keys = array_keys($final_options);
  while (TRUE) {
    $current_options = $options;
    // Mark selections.
    foreach ($selections as $selection) {
      $current_options[$selection] = dt($mark, array('!key' => $selection, '!value' => $options[$selection]));
    }
    // Add final options, if the minimum number of selections has been reached.
    if (count($selections) >= $min) {
      $current_options = array_merge($current_options, $final_options, $final_builtin);
    }
    $toggle = drush_choice($current_options, $prompt, $label);
    if ($toggle === FALSE) {
      return FALSE;
    }
    // Don't include the built in final option in the return value.
    if (count($selections) >= $min && empty($final_options) && $toggle == 'done') {
      return $selections;
    }
    // Toggle the selected value.
    $item = array_search($toggle, $selections);
    if ($item === FALSE) {
      array_unshift($selections, $toggle);
    }
    else {
      unset($selections[$item]);
    }
    // If the user selected one of the final options, return.
    if (count($selections) >= $min && in_array($toggle, $final_options_keys)) {
      return $selections;
    }
    // If the user selected too many options, drop the oldest selection.
    if (isset($max) && count($selections) > $max) {
      array_pop($selections);
    }
  }
}

/**
 * Prompt the user for input
 *
 * The input can be anything that fits on a single line (not only y/n),
 * so we can't use drush_confirm()
 *
 * @param $prompt
 *   The text which is displayed to the user.
 * @param $default
 *   The default value of the input.
 * @param $required
 *   If TRUE, user may continue even when no value is in the input.
 * @param $password
 *   If TRUE, surpress printing of the input.
 *
 * @see drush_confirm()
 */
function drush_prompt($prompt, $default = NULL, $required = TRUE, $password = FALSE) {
  if (isset($default)) {
    $prompt .= " [" . $default . "]";
  }
  $prompt .= ": ";

  drush_print_prompt($prompt);

  if (drush_get_context('DRUSH_AFFIRMATIVE')) {
    return $default;
  }

  $stdin = fopen('php://stdin', 'r');

  if ($password) drush_shell_exec("stty -echo");

  stream_set_blocking($stdin, TRUE);
  while (($line = fgets($stdin)) !== FALSE) {
    $line = trim($line);
    if ($line === "") {
      $line = $default;
    }
    if ($line || !$required) {
      break;
    }
    drush_print_prompt($prompt);
  }
  fclose($stdin);
  if ($password) {
    drush_shell_exec("stty echo");
    print "\n";
  }
  return $line;
}

/**
 * @} End of "defgroup userinput".
 */

/**
 * Calls a given function, passing through all arguments unchanged.
 *
 * This should be used when calling possibly mutative or destructive functions
 * (e.g. unlink() and other file system functions) so that can be suppressed
 * if the simulation mode is enabled.
 *
 * Important:  Call @see drush_op_system() to execute a shell command,
 * or @see drush_shell_exec() to execute a shell command and capture the
 * shell output.
 *
 * @param $callable
 *   The name of the function. Any additional arguments are passed along.
 * @return
 *   The return value of the function, or TRUE if simulation mode is enabled.
 *
 */
function drush_op($callable) {
  $args_printed = array();
  $args = func_get_args();
  array_shift($args); // Skip function name
  foreach ($args as $arg) {
    $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object');
  }

  if (!is_array($callable)) {
    $callable_string = $callable;
  }
  else {
    if (is_object($callable[0])) {
      $callable_string = get_class($callable[0]) . '::' . $callable[1];
    }
    else {
      $callable_string = implode('::', $callable);
    }
  }

  // Special checking for drush_op('system')
  if ($callable == 'system') {
    drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), LogLevel::DEBUG);
  }

  if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
    drush_log(sprintf("Calling %s(%s)", $callable_string, implode(", ", $args_printed)), LogLevel::DEBUG);
  }

  if (drush_get_context('DRUSH_SIMULATE')) {
    return TRUE;
  }

  return drush_call_user_func_array($callable, $args);
}

/**
 * Mimic cufa but still call function directly. See http://drupal.org/node/329012#comment-1260752
 */
function drush_call_user_func_array($function, $args = array() ) {
  if (is_array($function)) {
    // $callable is a method so always use CUFA.
    return call_user_func_array($function, $args);
  }

  switch (count($args)) {
    case 0: return $function(); break;
    case 1: return $function($args[0]); break;
    case 2: return $function($args[0], $args[1]); break;
    case 3: return $function($args[0], $args[1], $args[2]); break;
    case 4: return $function($args[0], $args[1], $args[2], $args[3]); break;
    case 5: return $function($args[0], $args[1], $args[2], $args[3], $args[4]); break;
    case 6: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break;
    case 7: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break;
    case 8: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break;
    case 9: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break;
    default: return call_user_func_array($function,$args);
  }
}

/**
 * Download a file using wget, curl or file_get_contents, or via download cache.
 *
 * @param string $url
 *   The url of the file to download.
 * @param string $destination
 *   The name of the file to be saved, which may include the full path.
 *   Optional, if omitted the filename will be extracted from the url and the
 *   file downloaded to the current working directory (Drupal root if
 *   bootstrapped).
 * @param integer $cache_duration
 *   The acceptable age of a cached file. If cached file is too old, a fetch
 *   will occur and cache will be updated. Optional, if ommitted the file will
 *   be fetched directly.
 *
 * @return string
 *   The path to the downloaded file, or FALSE if the file could not be
 *   downloaded.
 */
function drush_download_file($url, $destination = FALSE, $cache_duration = 0) {
  // Generate destination if omitted.
  if (!$destination) {
    $file = basename(current(explode('?', $url, 2)));
    $destination = getcwd() . '/' . basename($file);
  }

  // Simply copy local files to the destination
  if (!_drush_is_url($url)) {
    return copy($url, $destination) ? $destination : FALSE;
  }

  if ($cache_duration !== 0 && $cache_file = drush_download_file_name($url)) {
    // Check for cached, unexpired file.
    if (file_exists($cache_file) && filectime($cache_file) > ($_SERVER['REQUEST_TIME']-$cache_duration)) {
      drush_log(dt('!name retrieved from cache.', array('!name' => $cache_file)));
    }
    else {
      if (_drush_download_file($url, $cache_file, TRUE)) {
        // Cache was set just by downloading file to right location.
      }
      elseif (file_exists($cache_file)) {
        drush_log(dt('!name retrieved from an expired cache since refresh failed.', array('!name' => $cache_file)), LogLevel::WARNING);
      }
      else {
        $cache_file = FALSE;
      }
    }

    if ($cache_file && copy($cache_file, $destination)) {
      // Copy cached file to the destination
      return $destination;
    }
  }
  elseif ($return = _drush_download_file($url, $destination)) {
    drush_register_file_for_deletion($return);
    return $return;
  }

  // Unable to retrieve from cache nor download.
  return FALSE;
}

/**
 * Helper function to determine name of cached file.
 */
function drush_download_file_name($url) {
  if ($cache_dir = drush_directory_cache('download')) {
    $cache_name = str_replace(array(':', '/', '?', '=', '\\'), '-', $url);
    return $cache_dir . "/" . $cache_name;
  }
  else {
    return FALSE;
  }
}

/**
 * Check whether the given path is just a url or a local path
 * @param string $url
 * @return boolean
 *   TRUE if the path does not contain a schema:// part.
 */
function _drush_is_url($url) {
  return parse_url($url, PHP_URL_SCHEME) !== NULL;
}

/**
 * Download a file using wget, curl or file_get_contents. Does not use download
 * cache.
 *
 * @param string $url
 *   The url of the file to download.
 * @param string $destination
 *   The name of the file to be saved, which may include the full path.
 * @param boolean $overwrite
 *   Overwrite any file thats already at the destination.
 * @return string
 *   The path to the downloaded file, or FALSE if the file could not be
 *   downloaded.
 */
function _drush_download_file($url, $destination, $overwrite = TRUE) {
  static $use_wget;
  if ($use_wget === NULL) {
    $use_wget = drush_shell_exec('wget --version');
  }

  $destination_tmp = drush_tempnam('download_file');
  if ($use_wget) {
    drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url);
  }
  else {
    // Force TLS1+ as per https://github.com/drush-ops/drush/issues/894.
    drush_shell_exec("curl --tlsv1 --fail -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url);
  }
  if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) {
    @file_put_contents($destination_tmp, $file);
  }
  if (!drush_file_not_empty($destination_tmp)) {
    // Download failed.
    return FALSE;
  }

  drush_move_dir($destination_tmp, $destination, $overwrite);
  return $destination;
}

/**
 * Determines the MIME content type of the specified file.
 *
 * The power of this function depends on whether the PHP installation
 * has either mime_content_type() or finfo installed -- if not, only tar,
 * gz, zip and bzip2 types can be detected.
 *
 * If mime type can't be obtained, an error will be set.
 *
 * @return mixed
 *   The MIME content type of the file or FALSE.
 */
function drush_mime_content_type($filename) {
  $content_type = drush_attempt_mime_content_type($filename);
  if ($content_type) {
    drush_log(dt('Mime type for !file is !mt', array('!file' => $filename, '!mt' => $content_type)), LogLevel::NOTICE);
    return $content_type;
  }
  return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', array('!file' => $filename)));
}

/**
 * Works like drush_mime_content_type, but does not set an error
 * if the type is unknown.
 */
function drush_attempt_mime_content_type($filename) {
  $content_type = FALSE;
  if (class_exists('finfo')) {
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $content_type = $finfo->file($filename);
    if ($content_type == 'application/octet-stream') {
      drush_log(dt('Mime type for !file is application/octet-stream.', array('!file' => $filename)), LogLevel::DEBUG);
      $content_type = FALSE;
    }
  }
  // If apache is configured in such a way that all files are considered
  // octet-stream (e.g with mod_mime_magic and an http conf that's serving all
  // archives as octet-stream for other reasons) we'll detect mime types on our
  //  own by examing the file's magic header bytes.
  if (!$content_type) {
    drush_log(dt('Examining !file headers.', array('!file' => $filename)), LogLevel::DEBUG);
    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;

          default:
            drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', array('!hex' => dechex($data[1]), '!file' => $filename,), LogLevel::DEBUG));
        }
      }
      else {
        drush_log(dt('Unable to read !file.', array('!file' => $filename)), LogLevel::WARNING);
      }
    }
    else {
      drush_log(dt('Unable to open !file.', array('!file' => $filename)), LogLevel::WARNING);
    }
  }

  // 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) {
    drush_log(dt('Examining !file extension.', array('!file' => $filename)), LogLevel::DEBUG);

    // Remove querystring from the filename, if present.
    $filename = basename(current(explode('?', $filename, 2)));
    $extension_mimetype = array(
      '.tar'     => 'application/x-tar',
      '.sql'     => 'application/octet-stream',
    );
    foreach ($extension_mimetype as $extension => $ct) {
      if (substr($filename, -strlen($extension)) === $extension) {
        $content_type = $ct;
        break;
      }
    }
  }
  return $content_type;
}

/**
 * Check whether a file is a supported tarball.
 *
 * @return mixed
 *   The file content type if it's a tarball. FALSE otherwise.
 */
function drush_file_is_tarball($path) {
  $content_type = drush_attempt_mime_content_type($path);
  $supported = array(
    'application/x-bzip2',
    'application/x-gzip',
    'application/x-tar',
    'application/x-zip',
    'application/zip',
  );
  if (in_array($content_type, $supported)) {
    return $content_type;
  }
  return FALSE;
}

/**
 * Extract a tarball.
 *
 * @param string $path
 *   Path to the archive to be extracted.
 * @param string $destination
 *   The destination directory the tarball should be extracted into.
 *   Optional, if ommitted the tarball directory will be used as destination.
 * @param boolean $listing
 *   If TRUE, a listing of the tar contents will be returned on success.
 * @param string $tar_extra_options
 *   Extra options to be passed to the tar command.
 *
 * @return mixed
 *   TRUE on success, FALSE on fail. If $listing is TRUE, a file listing of the
 *   tarball is returned if the extraction reported success, instead of TRUE.
 */
function drush_tarball_extract($path, $destination = FALSE, $listing = FALSE, $tar_extra_options = '') {
  // Check if tarball is supported.
  if (!($mimetype = drush_file_is_tarball($path))) {
    return drush_set_error('TARBALL_EXTRACT_UNKNOWN_FORMAT', dt('Unable to extract !path. Unknown archive format.', array('!path' => $path)));
  }

  // Check if destination is valid.
  if (!$destination) {
    $destination = dirname($path);
  }
  if (!drush_mkdir($destination)) {
    // drush_mkdir already set an error.
    return FALSE;
  }

  // Perform the extraction of a zip file.
  if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
    $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination);
    if (!$return) {
      return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to unzip !filename.', array('!filename' => $path)));
    }
    if ($listing) {
      // unzip prefixes the file listing output with a header line,
      // and prefixes each line with a verb representing the compression type.
      $output = drush_shell_exec_output();
      // Remove the header line.
      array_shift($output);
      // Remove the prefix verb from each line.
      $output = array_map(
        function ($str) use ($destination) {
          return substr($str, strpos($str, ":") + 3 + strlen($destination));
        },
        $output
      );
      // Remove any remaining blank lines.
      $return = array_filter(
        $output,
        function ($str) {
          return $str != "";
        }
      );
    }
  }
  // Otherwise we have a possibly-compressed Tar file.
  // If we are not on Windows, then try to do "tar" in a single operation.
  elseif (!drush_is_windows()) {
    $tar = drush_get_tar_executable();
    $tar_compression_flag = '';
    if ($mimetype == 'application/x-gzip') {
      $tar_compression_flag = 'z';
    }
    elseif ($mimetype == 'application/x-bzip2') {
      $tar_compression_flag = 'j';
    }

    $return = drush_shell_cd_and_exec(dirname($path), "$tar {$tar_extra_options} -C %s -x%sf %s", $destination, $tar_compression_flag, basename($path));
    if (!$return) {
      return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $path)));
    }
    if ($listing) {
      // We use a separate tar -tf instead of -xvf above because
      // the output is not the same in Mac.
      drush_shell_cd_and_exec(dirname($path), "$tar -t%sf %s", $tar_compression_flag, basename($path));
      $return = drush_shell_exec_output();
    }
  }
  // In windows, do the extraction by its primitive steps.
  else {
    // 1. copy the source tarball to the destination directory. Rename to a
    // temp name in case the destination directory == dirname($path)
    $tmpfile = drush_tempnam(basename($path), $destination);
    drush_copy_dir($path, $tmpfile, FILE_EXISTS_OVERWRITE);

    // 2. uncompress the tarball, if compressed.
    if (($mimetype == 'application/x-gzip') || ($mimetype == 'application/x-bzip2')) {
      if ($mimetype == 'application/x-gzip') {
        $compressed = $tmpfile . '.gz';
        // We used to use gzip --decompress in --stdout > out, but the output
        // redirection sometimes failed on Windows for some binary output.
        $command = 'gzip --decompress %s';
      }
      elseif ($mimetype == 'application/x-bzip2') {
        $compressed = $tmpfile . '.bz2';
        $command = 'bzip2 --decompress %s';
      }
      drush_op('rename', $tmpfile, $compressed);
      $return = drush_shell_cd_and_exec(dirname($compressed), $command, $compressed);
      if (!$return || !file_exists($tmpfile)) {
        return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to decompress !filename.', array('!filename' => $compressed)));
      }
    }

    // 3. Untar.
    $tar = drush_get_tar_executable();
    $return = drush_shell_cd_and_exec(dirname($tmpfile), "$tar {$tar_extra_options} -xvf %s", basename($tmpfile));
    if (!$return) {
      return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $tmpfile)));
    }
    if ($listing) {
      $return = drush_shell_exec_output();
      // Cut off the 'x ' prefix for the each line of the tar output
      // See http://drupal.org/node/1775520
      foreach($return as &$line) {
        if(strpos($line, "x ") === 0)
          $line = substr($line, 2);
      }
    }

    // Remove the temporary file so the md5 hash is accurate.
    unlink($tmpfile);
  }

  return $return;
}

/**
 * @defgroup commandprocessing Command processing functions.
 * @{
 *
 * These functions manage command processing by the
 * main function in drush.php.
 */

/**
 * Determine whether or not an argument should be removed from the
 * DRUSH_COMMAND_ARGS context.  This method is used when a Drush
 * command has set the 'strict-option-handling' flag indicating
 * that it will pass through all commandline arguments and any
 * additional options (not known to Drush) to some shell command.
 *
 * Take as an example the following call to core-rsync:
 *
 *   drush --yes core-rsync -v -az --exclude-paths='.git:.svn' local-files/ @site:%files
 *
 * In this instance:
 *
 *   --yes is a global Drush option
 *
 *   -v is an rsync option.  It will make rsync run in verbose mode,
 *     but will not make Drush run in verbose mode due to the fact that
 *     core-rsync sets the 'strict-option-handling' flag.
 *
 *   --exclude-paths is a local Drush option.  It will be converted by
 *     Drush into --exclude='.git' and --exclude='.svn', and then passed
 *     on to the rsync command.
 *
 * The parameter $arg passed to this function is one of the elements
 * of DRUSH_COMMAND_ARGS.  It will have values such as:
 *   -v
 *   -az
 *   --exclude-paths='.git:.svn'
 *   local-files/
 *   @site:%files
 *
 * Our job in this function is to determine if $arg should be removed
 * by virtue of appearing in $removal_list.  $removal_list is an array
 * that will contain values such as 'exclude-paths'.  Both the key and
 * the value of $removal_list is the same.
 */
function _drush_should_remove_command_arg($arg, $removal_list) {
  foreach ($removal_list as $candidate) {
    if (($arg == "-$candidate") ||
      ($arg == "--$candidate") ||
      (substr($arg,0,strlen($candidate)+3) == "--$candidate=") ) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Redispatch the specified command using the same
 * options that were passed to this invocation of drush.
 */
function drush_do_command_redispatch($command, $args = array(), $remote_host = NULL, $remote_user = NULL, $drush_path = NULL, $user_interactive = FALSE, $aditional_options = array()) {
  $additional_global_options = array();
  $command_options = drush_redispatch_get_options();
  $command_options = $aditional_options + $command_options;
  if (is_array($command)) {
    $command_name = $command['command'];
    // If we are executing a remote command that uses strict option handling,
    // then mark all of the options in the alias context as global, so that they
    // will appear before the command name.
    if (!empty($command['strict-option-handling'])) {
      foreach(drush_get_context('alias') as $alias_key => $alias_value) {
        if (array_key_exists($alias_key, $command_options) && !array_key_exists($alias_key, $command['options'])) {
          $additional_global_options[$alias_key] = $alias_value;
        }
      }
    }
  }
  else {
    $command_name = $command;
  }
  // If the path to drush was supplied, then use it to invoke the new command.
  if ($drush_path == NULL) {
    $drush_path = drush_get_option('drush-script');
    if (!isset($drush_path)) {
      $drush_folder = drush_get_option('drush');
      if (isset($drush)) {
        $drush_path = $drush_folder . '/drush';
      }
    }
  }
  $backend_options = array('drush-script' => $drush_path, 'remote-host' => $remote_host, 'remote-user' => $remote_user, 'integrate' => TRUE, 'additional-global-options' => $additional_global_options);
  // Set the tty if requested, if the command necessitates it,
  // or if the user explicitly asks for interactive mode, but
  // not if interactive mode is forced.  tty implies interactive
  if (drush_get_option('tty') || $user_interactive || !empty($command['remote-tty'])) {
    $backend_options['#tty'] = TRUE;
    $backend_options['interactive'] = TRUE;
  }
  elseif (drush_get_option('interactive')) {
    $backend_options['interactive'] = TRUE;
  }

  // Run the command in a new process.
  drush_log(dt('Begin redispatch via drush_invoke_process().'));
  $values = drush_invoke_process('@self', $command_name, $args, $command_options, $backend_options);
  drush_log(dt('End redispatch via drush_invoke_process().'));

  return $values;
}


/**
 * @} End of "defgroup commandprocessing".
 */

/**
 * @defgroup logging Logging information to be provided as output.
 * @{
 *
 * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken
 * by drush.
 */

/**
 * Add a log message to the log history.
 *
 * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with
 * the resulting entry at the end of execution.
 *
 * This allows you to replace it with custom logging implementations if needed,
 * such as logging to a file or logging to a database (drupal or otherwise).
 *
 * The default callback is the Drush\Log\Logger class with prints the messages
 * to the shell.
 *
 * @param message
 *   String containing the message to be logged.
 * @param type
 *   The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'.
 *   A type of 'ok' or 'success' can also be supplied to flag something that worked.
 *   If you want your log messages to print to screen without the user entering
 *   a -v or --verbose flag, use type 'ok', this prints log messages out to
 *   STDERR, which prints to screen (unless you have redirected it). All other
 *   types of messages will be assumed to be notices.
 */
function drush_log($message, $type = LogLevel::NOTICE, $error = null) {
  $entry = array(
    'type' => $type,
    'message' => $message,
    'timestamp' => microtime(TRUE),
    'memory' => memory_get_usage(),
  );
  $entry['error'] = $error;

  return _drush_log($entry);
}

/**
 * Future: add some sort of dependency injection to Drush.
 */
function _drush_create_default_logger() {
  $drush_logger = new Logger();
  drush_set_context('DRUSH_LOGGER', $drush_logger);
  drush_set_context('DRUSH_LOG_CALLBACK', $drush_logger);
}

/**
 * Call the default logger, or the user's log callback, as
 * appropriate.
 */
function _drush_log($entry) {
  $callback = drush_get_context('DRUSH_LOG_CALLBACK');

  if ($callback instanceof LoggerInterface) {
    _drush_log_to_logger($callback, $entry);
  }
  elseif ($callback) {
    $log =& drush_get_context('DRUSH_LOG', array());
    $log[] = $entry;
    drush_backend_packet('log', $entry);
    return $callback($entry);
  }
}

// Maintain compatibility with extensions that hook into
// DRUSH_LOG_CALLBACK (e.g. drush_ctex_bonus)
function _drush_print_log($entry) {
  $drush_logger = drush_get_context('DRUSH_LOGGER');
  if ($drush_logger) {
    _drush_log_to_logger($drush_logger, $entry);
  }
}

function _drush_log_to_logger($logger, $entry) {
    $context = $entry;
    $log_level = $entry['type'];
    $message = $entry['message'];
    unset($entry['type']);
    unset($entry['message']);

    $logger->log($log_level, $message, $context);
}

function drush_log_has_errors($types = array(LogLevel::WARNING, LogLevel::ERROR, LogLevel::FAILED)) {
  $log =& drush_get_context('DRUSH_LOG', array());
  foreach ($log as $entry) {
    if (in_array($entry['type'], $types)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Backend command callback. Add a log message to the log history.
 *
 * @param entry
 *   The log entry.
 */
function drush_backend_packet_log($entry, $backend_options) {
  if (!$backend_options['log']) {
    return;
  }
  if (!is_string($entry['message'])) {
    $entry['message'] = implode("\n", (array)$entry['message']);
  }
  $entry['message'] = $entry['message'];
  if (array_key_exists('#output-label', $backend_options)) {
    $entry['message'] = $backend_options['#output-label'] . $entry['message'];
  }

  // If integrate is FALSE, then log messages are stored in DRUSH_LOG,
  // but are -not- printed to the console.
  if ($backend_options['integrate']) {
    _drush_log($entry);
  }
  else {
    $log =& drush_get_context('DRUSH_LOG', array());
    $log[] = $entry;
    // Yes, this looks odd, but we might in fact be a backend command
    // that ran another backend command.
    drush_backend_packet('log', $entry);
  }
}

/**
 * Retrieve the log messages from the log history
 *
 * @return
 *   Entire log history
 */
function drush_get_log() {
  return drush_get_context('DRUSH_LOG', array());
}

/**
 * Run print_r on a variable and log the output.
 */
function dlm($object) {
  drush_log(print_r($object, TRUE));
}

/**
 * Display the pipe output for the current request.
 */
function drush_pipe_output() {
  $pipe = drush_get_context('DRUSH_PIPE_BUFFER');
  if (!empty($pipe)) {
    drush_print_r($pipe, NULL, FALSE);
  }
}

// Print all timers for the request.
function drush_print_timers() {
  global $timers;
  $temparray = array();
  foreach ((array)$timers as $name => $timerec) {
    // We have to use timer_read() for active timers, and check the record for others
    if (isset($timerec['start'])) {
      $temparray[$name] = timer_read($name);
    }
    else {
      $temparray[$name] = $timerec['time'];
    }
  }
  // Go no farther if there were no timers
  if (count($temparray) > 0) {
    // Put the highest cumulative times first
    arsort($temparray);
    $table = array();
    $table[] = array('Timer', 'Cum (sec)', 'Count', 'Avg (msec)');
    foreach ($temparray as $name => $time) {
      $cum = round($time/1000, 3);
      $count = $timers[$name]['count'];
      if ($count > 0) {
        $avg = round($time/$count, 3);
      }
      else {
        $avg = 'N/A';
      }
      $table[] = array($name, $cum, $count, $avg);
    }
    drush_print_table($table, TRUE, array(), STDERR);
  }
}

/**
 * Turn drupal_set_message errors into drush_log errors
 */
function _drush_log_drupal_messages() {
  if (function_exists('drupal_get_messages')) {

    $messages = drupal_get_messages(NULL, TRUE);

    if (array_key_exists('error', $messages)) {
      //Drupal message errors.
      foreach ((array) $messages['error'] as $error) {
        $error = strip_tags($error);
        $header = preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error);
        $session = preg_match('/^warning: session_start\(\): Cannot send session /i', $error);
        if ($header || $session) {
          //These are special cases for an unavoidable warnings
          //that are generated by generating output before Drupal is bootstrapped.
          //or sending a session cookie (seems to affect d7 only?)
          //Simply ignore them.
          continue;
        }
        elseif (preg_match('/^warning:/i', $error)) {
          drush_log(preg_replace('/^warning: /i', '', $error), LogLevel::WARNING);
        }
        elseif (preg_match('/^notice:/i', $error)) {
          drush_log(preg_replace('/^notice: /i', '', $error), LogLevel::NOTICE);
        }
        elseif (preg_match('/^user warning:/i', $error)) {
          // This is a special case. PHP logs sql errors as 'User Warnings', not errors.
          drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error));
        }
        else {
          drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error);
        }
      }
    }
    unset($messages['error']);

    // Log non-error messages.
    foreach ($messages as $type => $items) {
      foreach ($items as $item) {
        drush_log(strip_tags($item), $type);
      }
    }
  }
}

// Copy of format_size() in Drupal.
function drush_format_size($size) {
  if ($size < DRUSH_KILOBYTE) {
    // format_plural() not always available.
    return dt('@count bytes', array('@count' => $size));
  }
  else {
    $size = $size / DRUSH_KILOBYTE; // Convert bytes to kilobytes.
    $units = array(
      dt('@size KB', array()),
      dt('@size MB', array()),
      dt('@size GB', array()),
      dt('@size TB', array()),
      dt('@size PB', array()),
      dt('@size EB', array()),
      dt('@size ZB', array()),
      dt('@size YB', array()),
    );
    foreach ($units as $unit) {
      if (round($size, 2) >= DRUSH_KILOBYTE) {
        $size = $size / DRUSH_KILOBYTE;
      }
      else {
        break;
      }
    }
    return str_replace('@size', round($size, 2), $unit);
  }
}

/**
 * @} End of "defgroup logging".
 */

/**
 * @defgroup errorhandling Managing errors that occur in the Drush framework.
 * @{
 * Functions that manage the current error status of the Drush framework.
 *
 * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an
 * error has occurred.
 * This error code is returned at the end of program execution, and provide the shell or calling application with
 * more information on how to diagnose any problems that may have occurred.
 */

/**
 * Set an error code for the error handling system.
 *
 * @param \Drupal\Component\Render\MarkupInterface|string $error
 *   A text string identifying the type of error.
 * @param null|string $message
 *   Optional. Error message to be logged. If no message is specified,
 *   hook_drush_help will be consulted, using a key of 'error:MY_ERROR_STRING'.
 * @param null|string $output_label
 *   Optional. Label to prepend to the error message.
 *
 * @return bool
 *   Always returns FALSE, to allow returning false in the calling functions,
 *   such as <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code>.
 */
function drush_set_error($error, $message = null, $output_label = "") {
  $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
  $error_code = DRUSH_FRAMEWORK_ERROR;

  $error_log =& drush_get_context('DRUSH_ERROR_LOG', array());

  if (is_numeric($error)) {
    $error = 'DRUSH_FRAMEWORK_ERROR';
  }
  elseif (!is_string($error)) {
    // Typical case: D8 TranslatableMarkup, implementing MarkupInterface.
    $error = "$error";
  }

  $message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error);

  if (is_array($message)) {
    $message = implode("\n", $message);
  }

  $error_log[$error][] = $message;
  if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) {
    drush_log(($message) ? $output_label . $message : $output_label . $error, LogLevel::ERROR, $error);
  }

  return FALSE;
}

/**
 * Return the current error handling status
 *
 * @return
 *   The current aggregate error status
 */
function drush_get_error() {
  return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
}

/**
 * Return the current list of errors that have occurred.
 *
 * @return
 *   An associative array of error messages indexed by the type of message.
 */
function drush_get_error_log() {
  return drush_get_context('DRUSH_ERROR_LOG', array());
}

/**
 * Check if a specific error status has been set.
 *
 * @param error
 *   A text string identifying the error that has occurred.
 * @return
 *   TRUE if the specified error has been set, FALSE if not
 */
function drush_cmp_error($error) {
  $error_log = drush_get_error_log();

  if (is_numeric($error)) {
    $error = 'DRUSH_FRAMEWORK_ERROR';
  }

  return array_key_exists($error, $error_log);
}

/**
 * Clear error context.
 */
function drush_clear_error() {
  drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
}

/**
 * Exit due to user declining a confirmation prompt.
 *
 * Usage:  return drush_user_abort();
 */
function drush_user_abort($msg = NULL) {
  drush_set_context('DRUSH_USER_ABORT', TRUE);
  drush_set_context('DRUSH_EXIT_CODE', DRUSH_EXITCODE_USER_ABORT);
  drush_log($msg ? $msg : dt('Cancelled.'), LogLevel::CANCEL);
  return FALSE;
}

/**
 * Turn PHP error handling off.
 *
 * This is commonly used while bootstrapping Drupal for install
 * or updates.
 *
 * This also records the previous error_reporting setting, in
 * case it wasn't recorded previously.
 *
 * @see drush_errors_off()
 */
function drush_errors_off() {
  drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0));
  ini_set('display_errors', FALSE);
}

/**
 * Turn PHP error handling on.
 *
 * We default to error_reporting() here just in
 * case drush_errors_on() is called before drush_errors_off() and
 * the context is not yet set.
 *
 * @arg $errors string
 *   The default error level to set in drush. This error level will be
 *   carried through further drush_errors_on()/off() calls even if not
 *   provided in later calls.
 *
 * @see error_reporting()
 * @see drush_errors_off()
 */
function drush_errors_on($errors = null) {
  if (!isset($errors)) {
    $errors = error_reporting();
  }
  else {
    drush_set_context('DRUSH_ERROR_REPORTING', $errors);
  }
  error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors));
  ini_set('display_errors', TRUE);
}

/**
 * @} End of "defgroup errorhandling".
 */

/**
 * Get the PHP memory_limit value in bytes.
 */
function drush_memory_limit() {
  $value = trim(ini_get('memory_limit'));
  $last = strtolower($value[strlen($value)-1]);
  switch ($last) {
    case 'g':
      $value *= DRUSH_KILOBYTE;
    case 'm':
      $value *= DRUSH_KILOBYTE;
    case 'k':
      $value *= DRUSH_KILOBYTE;
  }

  return $value;
}

/**
 * Unset the named key anywhere in the provided
 * data structure.
 */
function drush_unset_recursive(&$data, $unset_key) {
  if (!empty($data) && is_array($data)) {
    unset($data[$unset_key]);
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        drush_unset_recursive($data[$key], $unset_key);
      }
    }
  }
}

/**
 * Return a list of VCSs reserved files and directories.
 */
function drush_version_control_reserved_files() {
  static $files = FALSE;

  if (!$files) {
    // Also support VCSs that are not drush vc engines.
    $files = array('.git', '.gitignore', '.hg', '.hgignore', '.hgrags');
    $engine_info = drush_get_engines('version_control');
    $vcs = array_keys($engine_info['engines']);
    foreach ($vcs as $name) {
      $version_control = drush_include_engine('version_control', $name);
      $files = array_merge($files, $version_control->reserved_files());
    }
  }

  return $files;
}

/**
 * Generate a random alphanumeric password.  Copied from user.module.
 */
function drush_generate_password($length = 10) {
  // This variable contains the list of allowable characters for the
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
  // of 'I', 1, and 'l'.
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';

  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;

  // Declare the password as a blank string.
  $pass = '';

  // Loop the number of times specified by $length.
  for ($i = 0; $i < $length; $i++) {

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
    $pass .= $allowable_characters[mt_rand(0, $len)];
  }

  return $pass;
}

/**
 * Form an associative array from a linear array.
 *
 * This function walks through the provided array and constructs an associative
 * array out of it. The keys of the resulting array will be the values of the
 * input array. The values will be the same as the keys unless a function is
 * specified, in which case the output of the function is used for the values
 * instead.
 *
 * @param $array
 *   A linear array.
 * @param $function
 *   A name of a function to apply to all values before output.
 *
 * @return
 *   An associative array.
 */
function drush_map_assoc($array, $function = NULL) {
  // array_combine() fails with empty arrays:
  // http://bugs.php.net/bug.php?id=34857.
  $array = !empty($array) ? array_combine($array, $array) : array();
  if (is_callable($function)) {
    $array = array_map($function, $array);
  }
  return $array;
}
/**
 * Clears completion caches.
 *
 * If called with no parameters the entire complete cache will be cleared.
 * If called with just the $type parameter the global cache for that type will
 * be cleared (in the site context, if any). If called with both $type and
 * $command parameters the command cache of that type will be cleared  (in the
 * site context, if any).
 *
 * This is included in drush.inc as complete.inc is only loaded conditionally.
 *
 * @param $type
 *   The completion type (optional).
 * @param $command
 *   The command name (optional), if command specific cache is to be cleared.
 *   If specifying a command, $type is not optional.
 */
function drush_complete_cache_clear($type = NULL, $command = NULL) {
  require_once DRUSH_BASE_PATH . '/includes/complete.inc';
  if ($type) {
    drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete');
    return;
  }
  // No type or command, so clear the entire complete cache.
  drush_cache_clear_all('*', 'complete', TRUE);
}

/*
 * Cast a value according to the provided format
 *
 * @param mixed $value.
 * @param string $format
 *   Allowed values: auto, integer, float, bool, boolean, json, yaml.
 *
 * @return $value
 *  The value, re-typed as needed.
 */
function drush_value_format($value, $format) {
  if ($format == 'auto') {
    if (is_numeric($value)) {
      $value = $value + 0; // http://php.net/manual/en/function.is-numeric.php#107326
      $format = gettype($value);
    }
    elseif (($value == 'TRUE') || ($value == 'FALSE')) {
      $format = 'bool';
    }
  }

  // Now, we parse the object.
  switch ($format) {
    case 'integer':
      $value = (integer)$value;
      break;
    // from: http://php.net/gettype
    // for historical reasons "double" is returned in case of a float, and not simply "float"
    case 'double':
    case 'float':
      $value = (float)$value;
      break;
    case 'bool':
    case 'boolean':
      if ($value == 'TRUE') {
        $value = TRUE;
      }
      elseif ($value == 'FALSE') {
        $value = FALSE;
      }
      else {
        $value = (bool)$value;
      }
      break;

    case 'json':
      $value = drush_json_decode($value);
      break;

    case 'yaml':
      $value = \Symfony\Component\Yaml\Yaml::parse($value, FALSE, TRUE);
      break;
  }
  return $value;
}
<?php

/**
 * @file
 * The drush engines API implementation and helpers.
 */

use Drush\Log\LogLevel;

/**
 * Obtain all engine types info and normalize with defaults.
 *
 * @see hook_drush_engine_type_info().
 */
function drush_get_engine_types_info() {
  $info = drush_command_invoke_all('drush_engine_type_info');
  foreach ($info as $type => $data) {
    $info[$type] += array(
      'description' => '',
      'option' => FALSE,
      'default' => NULL,
      'options' => array(),
      'sub-options' => array(),
      'config-aliases' => array(),
      'add-options-to-command' => FALSE,
      'combine-help' => FALSE,
    );
  }

  return $info;
}

/**
 * Return a structured array of engines of a specific type.
 *
 * Engines are pluggable subsystems. Each engine of a specific type will
 * implement the same set of API functions and perform the same high-level
 * task using a different backend or approach.
 *
 * This function/hook is useful when you have a selection of several mutually
 * exclusive options to present to a user to select from.
 *
 * Other commands are able to extend this list and provide their own engines.
 * The hook can return useful information to help users decide which engine
 * they need, such as description or list of available engine options.
 *
 * The engine path element will automatically default to a subdirectory (within
 * the directory of the commandfile that implemented the hook) with the name of
 * the type of engine - e.g. an engine "wget" of type "handler" provided by
 * the "pm" commandfile would automatically be found if the file
 * "pm/handler/wget.inc" exists and a specific path is not provided.
 *
 * @param $engine_type
 *   The type of engine.
 *
 * @return
 *   A structured array of engines.
 */
function drush_get_engines($engine_type) {
  $info = drush_get_engine_types_info();
  if (!isset($info[$engine_type])) {
    return drush_set_error('DRUSH_UNKNOWN_ENGINE_TYPE', dt('Unknown engine type !engine_type', array('!engine_type' => $engine_type)));
  }

  $engines = array(
    'info' => $info[$engine_type],
    'engines' => array(),
  );
  $list = drush_commandfile_list();
  $hook = 'drush_engine_' . str_replace('-', '_', $engine_type);
  foreach ($list as $commandfile => $path) {
    if (drush_command_hook($commandfile, $hook)) {
      $function = $commandfile . '_' . $hook;
      $result = $function();
      foreach ($result as $engine_name => $engine) {
        // Add some defaults.
        $engine += array(
          'commandfile' => $commandfile,
          'options' => array(),
          'sub-options' => array(),
          'drupal dependencies' => array(),
        );

        // Legacy engines live in a subdirectory
        // of the commandfile that declared them.
        $engine_path = sprintf("%s/%s", dirname($path), $engine_type);
        if (file_exists($engine_path)) {
          $engine['path'] = $engine_path;
        }
        // Build engine class name, in case the engine doesn't provide it.
        // The class name is based on the engine type and name, converted
        // from snake_case to CamelCase.
        // For example for type 'package_handler' and engine 'git_drupalorg'
        // the class is \Drush\PackageHandler\GitDrupalorg
        elseif (!isset($engine['class'])) {
          $parts = array();
          $parts[] = '\Drush';
          $parts[] = str_replace(' ', '', ucwords(strtr($engine_type, '_', ' ')));
          $parts[] = str_replace(' ', '', ucwords(strtr($engine_name, '_', ' ')));
          $engine['class'] = implode('\\', $parts);
        }

        $engines['engines'][$engine_name] = $engine;
      }
    }
  }

  return $engines;
}

/**
 * Take a look at all of the available engines,
 * and create topic commands for each one that
 * declares a topic.
 */
function drush_get_engine_topics() {
  $items = array();
  $info = drush_get_engine_types_info();
  foreach ($info as $engine => $data) {
    if (array_key_exists('topic', $data)) {
      $items[$data['topic']] = array(
        'description' => $data['description'],
        'hidden' => TRUE,
        'topic' => TRUE,
        'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
        'callback' => 'drush_engine_topic_command',
        'callback arguments' => array($engine),
      );
    }
  }
  return $items;
}

/**
 * Include, instantiate and validate command engines.
 *
 * @return FALSE if a engine doesn't validate.
 */
function drush_load_command_engines($command) {
  $result = TRUE;
  foreach ($command['engines'] as $engine_type => $config) {
    $result = drush_load_command_engine($command, $engine_type);
    // Stop loading engines if any of them fails.
    if ($result === FALSE) {
      break;
    }
  }
  return $result;
}

/**
 * Returns engine config supplied in the command definition.
 */
function drush_get_command_engine_config($command, $engine_type, $metadata = array()) {
  if (isset($command['engines'][$engine_type])) {
    $metadata = array_merge($metadata, $command['engines'][$engine_type]);
  }
  return $metadata;
}

/**
 * Selects and loads an engine implementing the given type.
 *
 * Loaded engines are stored as a context.
 */
function drush_load_command_engine($command, $engine_type, $metadata = array()) {
  drush_log(dt("Loading !engine engine.", array('!engine' => $engine_type), LogLevel::BOOTSTRAP));

  $config = drush_get_command_engine_config($command, $engine_type, $metadata);
  $engine_info = drush_get_engines($engine_type);
  $engine = drush_select_engine($config, $engine_info);
  $version = drush_drupal_major_version();

  $context = $engine_type . '_engine_' . $engine . '_' . $version;
  $instance = drush_get_context($context, FALSE);
  if ($instance != FALSE) {
    drush_set_engine($engine_type, $instance);
  }
  else {
    $instance = drush_load_engine($engine_type, $engine, $config);
    if ($instance == FALSE) {
      return FALSE;
    }
    drush_set_context($context, $instance);
  }
  return $instance;
}

/**
 * Add command structure info from each engine type back into the command.
 */
function drush_merge_engine_data(&$command) {
  // First remap engine data from the shortcut location
  // ($command['engine_type']) to the standard location
  // ($command['engines']['engine_type'])
  $info = drush_get_engine_types_info();
  foreach ($info as $engine_type => $info) {
    if (isset($command[$engine_type])) {
      $config = $command[$engine_type];
      foreach ($info['config-aliases'] as $engine_option_alias_name => $engine_option_standard_name) {
        if (array_key_exists($engine_option_alias_name, $config)) {
          $config[$engine_option_standard_name] = $config[$engine_option_alias_name];
          unset($config[$engine_option_alias_name]);
        }
      }
      // Convert single string values of 'require-engine-capability' to an array.
      if (isset($config['require-engine-capability']) && is_string($config['require-engine-capability'])) {
        $config['require-engine-capability'] = array($config['require-engine-capability']);
      }
      $command['engines'][$engine_type] = $config;
    }
  }

  foreach ($command['engines'] as $engine_type => $config) {
    // Normalize engines structure.
    if (!is_array($config)) {
      unset($command['engines'][$engine_type]);
      $command['engines'][$config] = array();
      $engine_type = $config;
    }

    // Get all implementations for this engine type.
    $engine_info = drush_get_engines($engine_type);
    if ($engine_info === FALSE) {
      return FALSE;
    }

    // Complete command-declared engine type with default info.
    $command['engines'][$engine_type] += $engine_info['info'];
    $config = $command['engines'][$engine_type];

    $engine_data = array();

    // If there's a single implementation for this engine type, it will be
    // loaded by default, and makes no sense to provide a command line option
    // to select the only flavor (ie. --release_info=updatexml), so we won't
    // add an option in this case.
    // Additionally, depending on the command, it may be convenient to extend
    // the command with the engine options.
    if (count($engine_info['engines']) == 1) {
      if ($config['add-options-to-command'] !== FALSE) {
        // Add options and suboptions of the engine type and
        // the sole implementation.
        $engine = key($engine_info['engines']);
        $data = $engine_info['engines'][$engine];
        $engine_data += array(
          'options' => $config['options'] + $data['options'],
          'sub-options' => $config['sub-options'] + $data['sub-options'],
        );
      }
    }
    // Otherwise, provide a command option to choose between engines and add
    // the engine options and sub-options.
    else {
      // Add engine type global options and suboptions.
      $engine_data += array(
        'options' => $config['options'],
        'sub-options' => $config['sub-options'],
      );

      // If the 'combine-help' flag is set in the engine config,
      // then we will combine all of the help items into the help
      // text for $config['option'].
      $combine_help = $config['combine-help'];
      $combine_help_data = array();

      // Process engines in order. First the default engine, the rest alphabetically.
      $default = drush_select_engine($config, $engine_info);
      $engines = array_keys($engine_info['engines']);
      asort($engines);
      array_unshift($engines, $default);
      $engines = array_unique($engines);
      foreach ($engines as $engine) {
        $data = $engine_info['engines'][$engine];
        // Check to see if the command requires any particular
        // capabilities.  If no capabilities are required, then
        // all engines are acceptable.
        $engine_is_usable = TRUE;
        if (array_key_exists('require-engine-capability', $config)) {
          // See if the engine declares that it provides any
          // capabilities.  If no capabilities are listed, then
          // it is assumed that the engine can satisfy all requirements.
          if (array_key_exists('engine-capabilities', $data)) {
            $engine_is_usable = FALSE;
            // If 'require-engine-capability' is TRUE instead of an array,
            // then only engines that are universal (do not declare any
            // particular capabilities) are usable.
            if (is_array($config['require-engine-capability'])) {
              foreach ($config['require-engine-capability'] as $required) {
                // We need an engine that provides any one of the requirements.
                if (in_array($required, $data['engine-capabilities'])) {
                  $engine_is_usable = TRUE;
                }
              }
            }
          }
        }
        if ($engine_is_usable) {
          $command['engines'][$engine_type]['usable'][] = $engine;
          if (!isset($data['hidden'])) {
            $option = $config['option'] . '=' . $engine;
            $engine_data['options'][$option]['description'] = array_key_exists('description', $data) ? $data['description'] : NULL;
            if ($combine_help) {
              $engine_data['options'][$option]['hidden'] = TRUE;
              if (drush_get_context('DRUSH_VERBOSE') || ($default == $engine) || !isset($data['verbose-only'])) {
                $combine_help_data[$engine] = $engine . ': ' . $data['description'];
              }
            }
            if (isset($data['options'])) {
              $engine_data['sub-options'][$option] = $data['options'];
            }
            if (isset($data['sub-options'])) {
              $engine_data['sub-options'] += $data['sub-options'];
            }
          }
        }
      }
      if (!empty($combine_help_data)) {
        $engine_selection_option = $config['option'];
        if (!is_array($engine_data['options'][$engine_selection_option])) {
          $engine_data['options'][$engine_selection_option] = array('description' => $config['options'][$engine_selection_option]);
        }
        if (drush_get_context('DRUSH_VERBOSE')) {
          $engine_data['options'][$engine_selection_option]['description'] .= "\n" . dt("All available values are:") . "\n - " . implode("\n - ", $combine_help_data) . "\n";
        }
        else {
          $engine_data['options'][$engine_selection_option]['description'] .= " " . dt("Available: ") . implode(', ', array_keys($combine_help_data)) . ". ";
        }
        $engine_data['options'][$engine_selection_option]['description'] .= dt("Default is !default.", array('!default' => $default));
      }
      else {
        // If the help options are not combined, then extend the
        // default engine description.
        $desc = $engine_info['engines'][$default]['description'];
        $engine_info['engines'][$default]['description'] = dt('Default !type engine.', array('!type' => $engine_type)) . ' ' . $desc;
      }
    }
    // This was simply array_merge_recursive($command, $engine_data), but this
    // function has an undesirable behavior when merging primative types.
    // If there is a 'key' => 'value' in $command, and the same 'key' => 'value'
    // exists in $engine data, then the result will be 'key' => array('value', 'value');.
    // This is NOT what we want, so we provide our own 'overlay' function instead.
    $command = _drush_array_overlay_recursive($command, $engine_data);
  }
}

// Works like array_merge_recursive(), but does not convert primative
// types into arrays.  Ever.
function _drush_array_overlay_recursive($a, $b) {
  foreach ($b as $key => $value) {
    if (!isset($a[$key]) || !is_array($a[$key])) {
      $a[$key] = $b[$key];
    }
    else {
      $a[$key] = _drush_array_overlay_recursive($a[$key], $b[$key]);
    }
  }
  return $a;
}

/**
 * Implementation of command hook for docs-output-formats
 */
function drush_engine_topic_command($engine) {
  $engine_instances = drush_get_engines($engine);
  $option = $engine_instances['info']['option'];

  if (isset($engine_instances['info']['topic-file'])) {
    // To do: put this file next to the commandfile that defines the
    // engine type, not in the core docs directory.
    $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
    $path = $engine_instances['info']['topic-file'];
    $docs_file = (drush_is_absolute_path($path) ? '' : $docs_dir . '/') . $path;
    $doc_text = drush_html_to_text(file_get_contents($docs_file));
  }
  elseif (isset($engine_instances['info']['topic-text'])) {
    $doc_text = $engine_instances['info']['topic-text'];
  }
  else {
    return drush_set_error('DRUSH_BAD_ENGINE_TOPIC', dt("The engine !engine did not define its topic command correctly.", array('!engine' => $engine)));
  }

  // Look at each instance of the engine; if it has an html
  // file in the the 'topics' folder named after itself, then
  // include the file contents in the engine topic text.
  $instances = $engine_instances['engines'];
  ksort($instances);
  foreach ($instances as $instance => $config) {
    if (isset($config['description'])) {
      $doc_text .= "\n\n::: --$option=$instance :::\n" . $config['description'];
      $path = $config['path'] . '/topics/' . $instance . '.html';
      if (file_exists($path)) {
        $doc_text .= "\n" . drush_html_to_text(file_get_contents($path));
      }
      $additional_topic_text = drush_command_invoke_all('drush_engine_topic_additional_text', $engine, $instance, $config);
      if (!empty($additional_topic_text)) {
        $doc_text .= "\n\n" . implode("\n\n", $additional_topic_text);
      }
    }
  }

  // Write the topic text to a file so that is can be paged
  $file = drush_save_data_to_temp_file($doc_text);
  drush_print_file($file);
}

/**
 * Selects an engine between the available ones.
 *
 * Precedence:
 *
 *  - preferred engine, if available.
 *  - user supplied engine via cli.
 *  - default engine from engine type / command declaration.
 *  - the first engine available.
 *
 * @param array $config
 *   Engine type configuration. My be overridden in command declaration.
 * @param array $engine_info
 *   Engine type declaration.
 * @param string $default
 *   Preferred engine.
 *
 * @return string
 *   Selected engine.
 */
function drush_select_engine($config, $engine_info, $preferred = NULL) {
  $engines = array_keys($engine_info['engines']);

  if (in_array($preferred, $engines)) {
    return $preferred;
  }

  if (!empty($config['option'])) {
    $engine = drush_get_option($config['option'], FALSE);
    if ($engine && in_array($engine, $engines)) {
      return $engine;
    }
  }

  if (isset($config['default']) && in_array($config['default'], $engines)) {
    return $config['default'];
  }

  return current($engines);
}

/**
 * Loads and validate an engine of the given type.
 *
 * @param string $type
 *   Engine type.
 * @param string $engine
 *   Engine name.
 * @param array $config
 *   Engine configuration. Tipically it comes from a command declaration.
 *
 * @return
 *   TRUE or instanced object of available class on success. FALSE on fail.
 */
function drush_load_engine($type, $engine, $config = array()) {
  $engine_info = drush_get_engines($type);
  $engine = drush_select_engine($config, $engine_info, $engine);
  $config['engine-info'] = $engine_info['engines'][$engine];

  // Check engine dependency on drupal modules before include.
  $dependencies = $config['engine-info']['drupal dependencies'];
  foreach ($dependencies as $dependency) {
    if (!drush_module_exists($dependency)) {
      return drush_set_error('DRUSH_ENGINE_DEPENDENCY_ERROR', dt('!engine_type: !engine engine needs the following modules installed/enabled to run: !dependencies.', array('!engine_type' => $type, '!engine' => $engine, '!dependencies' => implode(', ', $dependencies))));
    }
  }

  $result = drush_include_engine($type, $engine, $config);
  if (is_object($result)) {
    $valid = method_exists($result, 'validate') ? $result->validate() : TRUE;
    if ($valid) {
      drush_set_engine($type, $result);
    }
  }
  else {
    $function = strtr($type, '-', '_') . '_validate';
    $valid = function_exists($function) ? call_user_func($function) : TRUE;
  }
  if (!$valid) {
    return FALSE;
  }
  return $result;
}

/**
 * Include the engine code for a specific named engine of a certain type.
 *
 * If the engine type has implemented hook_drush_engine_$type the path to the
 * engine specified in the array will be used.
 *
 * If a class named in the form drush_$type_$engine exists, it will return an
 * instance of the class.
 *
 * @param string $type
 *   The type of engine.
 * @param string $engine
 *   The name for the engine to include.
 * @param array $config
 *   Parameters for the engine class constructor.
 *
 * @return
 *   TRUE or instanced object of available class on success. FALSE on fail.
 */
function drush_include_engine($type, $engine, $config = NULL) {
  $engine_info = drush_get_engines($type);

  // Pick the engine name that actually implements the requested engine.
  $engine = isset($engine_info['engines'][$engine]['implemented-by']) ? $engine_info['engines'][$engine]['implemented-by'] : $engine;

  // Legacy engines live in a subdirectory of the commandfile
  // that declares them. We need to explicitly include the file.
  if (isset($engine_info['engines'][$engine]['path'])) {
    $path = $engine_info['engines'][$engine]['path'];
    if (!drush_include($path, $engine)) {
      return drush_set_error('DRUSH_ENGINE_INCLUDE_FAILED', dt('Unable to include the !type engine !engine from !path.' , array('!path' => $path, '!type' => $type, '!engine' => $engine)));
    }
    // Legacy engines may be implemented in a magic class name.
    $class = 'drush_' . $type . '_' . str_replace('-', '_', $engine);
    if (class_exists($class)) {
      $instance = new $class($config);
      $instance->engine_type = $type;
      $instance->engine = $engine;
      return $instance;
    }

    return TRUE;
  }

  return drush_get_class($engine_info['engines'][$engine]['class'], array($type, $engine, $config));
}

/**
 * Return the engine of the specified type that was loaded by the Drush command.
 */
function drush_get_engine($type) {
  return drush_get_context($type . '_engine', FALSE);
}

/**
 * Called by the Drush command (@see _drush_load_command_engines())
 * to cache the active engine instance.
 */
function drush_set_engine($type, $instance) {
  drush_set_context($type . '_engine', $instance);
}

/**
 * Add engine topics to the command topics, if any.
 */
function drush_engine_add_help_topics(&$command) {
  $engine_types = drush_get_engine_types_info();
  foreach ($command['engines'] as $engine_type => $config) {
    $info = $engine_types[$engine_type];
    if (isset($info['topics'])) {
      $command['topics'] = array_merge($command['topics'], $info['topics']);
    }
    if (isset($info['topic'])) {
      $command['topics'][] = $info['topic'];
    }
  }
}
<?php

/**
 * @file
 * Functions used by drush to query the environment and
 * setting the current configuration.
 *
 * Bootstrapping now occurs in bootstrap.inc.
 *
 * @see includes/bootstrap.inc
 */

use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;

/**
 * Log PHP errors to the Drush log. This is in effect until Drupal's error
 * handler takes over.
 */
function drush_error_handler($errno, $message, $filename, $line, $context) {
  // E_DEPRECATED was added in PHP 5.3. Drupal 6 will not fix all the
  // deprecated errors, but suppresses them. So we suppress them as well.
  if (defined('E_DEPRECATED')) {
    $errno = $errno & ~E_DEPRECATED;
  }

  // "error_reporting" is usually set in php.ini, but may be changed by
  // drush_errors_on() and drush_errors_off().
  if ($errno & error_reporting()) {
    // By default we log notices.
    $type = drush_get_option('php-notices', 'notice');
    $halt_on_error = drush_get_option('halt-on-error', (drush_drupal_major_version() != 6));

    // Bitmask value that constitutes an error needing to be logged.
    $error = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
    if ($errno & $error) {
      $type = 'error';
    }

    // Bitmask value that constitutes a warning being logged.
    $warning = E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING;
    if ($errno & $warning) {
      $type = LogLevel::WARNING;
    }

    drush_log($message . ' ' . basename($filename) . ':' . $line, $type);

    if ($errno == E_RECOVERABLE_ERROR && $halt_on_error) {
      drush_log(dt('E_RECOVERABLE_ERROR encountered; aborting. To ignore recoverable errors, run again with --no-halt-on-error'), 'error');
      exit(DRUSH_APPLICATION_ERROR);
    }

    return TRUE;
  }
}

/**
 * Returns a localizable message about php.ini that
 * varies depending on whether the php_ini_loaded_file()
 * is available or not.
 */
function _drush_php_ini_loaded_file_message() {
  if (function_exists('php_ini_loaded_file')) {
    return dt('Please check your configuration settings in !phpini or in your drush.ini file; see examples/example.drush.ini for details.', array('!phpini' => php_ini_loaded_file()));
  }
  else {
    return dt('Please check your configuration settings in your php.ini file or in your drush.ini file; see examples/example.drush.ini for details.');
  }
}

/**
 * Evalute the environment after an abnormal termination and
 * see if we can determine any configuration settings that the user might
 * want to adjust.
 */
function _drush_postmortem() {
  // Make sure that the memory limit has been bumped up from the minimum default value of 32M.
  $php_memory_limit = drush_memory_limit();
  if (($php_memory_limit > 0) && ($php_memory_limit <= 32*DRUSH_KILOBYTE*DRUSH_KILOBYTE)) {
    drush_set_error('DRUSH_MEMORY_LIMIT', dt('Your memory limit is set to !memory_limit; Drush needs as much memory to run as Drupal.  !php_ini_msg', array('!memory_limit' => $php_memory_limit / (DRUSH_KILOBYTE*DRUSH_KILOBYTE) . 'M', '!php_ini_msg' => _drush_php_ini_loaded_file_message())));
  }
}

/**
 * Evaluate the environment before command bootstrapping
 * begins.  If the php environment is too restrictive, then
 * notify the user that a setting change is needed and abort.
 */
function _drush_environment_check_php_ini() {
  $ini_checks = array('safe_mode' => '', 'open_basedir' => '', 'disable_functions' => array('exec', 'system'), 'disable_classes' => '');

  // Test to insure that certain php ini restrictions have not been enabled
  $prohibited_list = array();
  foreach ($ini_checks as $prohibited_mode => $disallowed_value) {
    $ini_value = ini_get($prohibited_mode);
    $invalid_value = FALSE;
    if (empty($disallowed_value)) {
      $invalid_value = !empty($ini_value) && (strcasecmp($ini_value, 'off') != 0);
    }
    else {
      foreach ($disallowed_value as $test_value) {
        if (preg_match('/(^|,)' . $test_value . '(,|$)/', $ini_value)) {
          $invalid_value = TRUE;
        }
      }
    }
    if ($invalid_value) {
      $prohibited_list[] = $prohibited_mode;
    }
  }
  if (!empty($prohibited_list)) {
    drush_log(dt('The following restricted PHP modes have non-empty values: !prohibited_list. This configuration is incompatible with drush.  !php_ini_msg', array('!prohibited_list' => implode(' and ', $prohibited_list), '!php_ini_msg' => _drush_php_ini_loaded_file_message())), LogLevel::ERROR);
  }

  return TRUE;
}

/**
 * Returns the current working directory.
 *
 * This is the directory as it was when drush was started, not the
 * directory we are currently in. For that, use getcwd() directly.
 */
function drush_cwd() {
  if ($path = drush_get_context('DRUSH_OLDCWD')) {
    return $path;
  }
  // We use PWD if available because getcwd() resolves symlinks, which
  // could take us outside of the Drupal root, making it impossible to find.
  // $_SERVER['PWD'] isn't set on windows and generates a Notice.
  $path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : '';
  if (empty($path)) {
    $path = getcwd();
  }

  // Convert windows paths.
  $path = Path::canonicalize($path);

  // Save original working dir case some command wants it.
  drush_set_context('DRUSH_OLDCWD', $path);

  return $path;
}

/**
 * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3).
 * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a
 * proper drive path, still with Unix slashes (c:/dir1).
 */
function _drush_convert_path($path) {
  $path = str_replace('\\','/', $path);
  if (drush_is_windows(_drush_get_os()) && !drush_is_cygwin(_drush_get_os())) {
    $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path);
  }

  return $path;
}

/**
 * Returns parent directory.
 *
 * @param string
 *   Path to start from.
 *
 * @return string
 *   Parent path of given path.
 */
function _drush_shift_path_up($path) {
  if (empty($path)) {
    return FALSE;
  }
  $path = explode(DIRECTORY_SEPARATOR, $path);
  // Move one directory up.
  array_pop($path);
  return implode(DIRECTORY_SEPARATOR, $path);
  // return dirname($path);
}

/**
 * Like Drupal conf_path, but searching from beneath.
 * Allows proper site uri detection in site sub-directories.
 *
 * Essentially looks for a settings.php file.  Drush uses this
 * function to find a usable site based on the user's current
 * working directory.
 *
 * @param string
 *   Search starting path. Defaults to current working directory.
 *
 * @return
 *   Current site path (folder containing settings.php) or FALSE if not found.
 */
function drush_site_path($path = NULL) {
  $site_path = FALSE;

  $path = empty($path) ? drush_cwd() : $path;
  // Check the current path.
  if (file_exists($path . '/settings.php')) {
    $site_path = $path;
  }
  else {
    // Move up dir by dir and check each.
    // Stop if we get to a Drupal root.   We don't care
    // if it is DRUSH_SELECTED_DRUPAL_ROOT or some other root.
    while (($path = _drush_shift_path_up($path)) && !drush_valid_root($path)) {
      if (file_exists($path . '/settings.php')) {
        $site_path = $path;
        break;
      }
    }
  }

  $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  if (file_exists($site_root . '/sites/sites.php')) {
    $sites = array();
    // This will overwrite $sites with the desired mappings.
    include($site_root . '/sites/sites.php');
    // We do a reverse lookup here to determine the URL given the site key.
    if ($match = array_search($site_path, $sites)) {
      $site_path = $match;
    }
  }

  // Last resort: try from site root
  if (!$site_path) {
    if ($site_root) {
      if (file_exists($site_root . '/sites/default/settings.php')) {
        $site_path = $site_root . '/sites/default';
      }
    }
  }

  return $site_path;
}

/**
 * Lookup a site's directory via the sites.php file given a hostname.
 *
 * @param $hostname
 *   The hostname of a site.  May be converted from URI.
 *
 * @return $dir
 *   The directory associated with that hostname or FALSE if not found.
 */
function drush_site_dir_lookup_from_hostname($hostname, $site_root = NULL) {
  if (!isset($site_root)) {
    $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  }
  if (!empty($site_root) && file_exists($site_root . '/sites/sites.php')) {
    $sites = array();
    // This will overwrite $sites with the desired mappings.
    include($site_root . '/sites/sites.php');
    return isset($sites[$hostname]) ? $sites[$hostname] : FALSE;
  }
  else {
    return FALSE;
  }
}

/**
 * This is a copy of Drupal's conf_path function, taken from D7 and
 * adjusted slightly to search from the selected Drupal Root.
 *
 * Drush uses this routine to find a usable site based on a URI
 * passed in via a site alias record or the --uri commandline option.
 *
 * Drush uses Drupal itself (specifically, the Drupal conf_path function)
 * to bootstrap the site itself.  If the implementation of conf_path
 * changes, the site should still bootstrap correctly; the only consequence
 * of this routine not working is that drush configuration files
 * (drushrc.php) stored with the site's drush folders might not be found.
 */
function drush_conf_path($server_uri, $require_settings = TRUE) {
  $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  if(empty($drupal_root) || empty($server_uri)) {
    return NULL;
  }
  $parsed_uri = parse_url($server_uri);
  if (is_array($parsed_uri) && !array_key_exists('scheme', $parsed_uri)) {
    $parsed_uri = parse_url('http://' . $server_uri);
  }
  if (!is_array($parsed_uri)) {
    return NULL;
  }
  $server_host = $parsed_uri['host'];
  if (array_key_exists('path', $parsed_uri)) {
    $server_uri = $parsed_uri['path'] . '/index.php';
  }
  else {
    $server_uri = "/index.php";
  }
  $confdir = 'sites';

  $sites = array();
  if (file_exists($drupal_root . '/' . $confdir . '/sites.php')) {
    // This will overwrite $sites with the desired mappings.
    include($drupal_root . '/' . $confdir . '/sites.php');
  }

  $uri = explode('/', $server_uri);
  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($server_host, '.')))));
  for ($i = count($uri) - 1; $i > 0; $i--) {
    for ($j = count($server); $j > 0; $j--) {
      $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
      if (isset($sites[$dir]) && file_exists($drupal_root . '/' . $confdir . '/' . $sites[$dir])) {
        $dir = $sites[$dir];
      }
      if (file_exists($drupal_root . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) {
        $conf = "$confdir/$dir";
        return $conf;
      }
    }
  }
  $conf = "$confdir/default";
  return $conf;
}

/**
 * Exhaustive depth-first search to try and locate the Drupal root directory.
 * This makes it possible to run Drush from a subdirectory of the drupal root.
 *
 * @param
 *   Search start path. Defaults to current working directory.
 * @return
 *   A path to drupal root, or FALSE if not found.
 */
function drush_locate_root($start_path = NULL) {
  $drupal_root = FALSE;

  $start_path = empty($start_path) ? drush_cwd() : $start_path;
  foreach (array(TRUE, FALSE) as $follow_symlinks) {
    $path = $start_path;
    if ($follow_symlinks && is_link($path)) {
      $path = realpath($path);
    }
    // Check the start path.
    if (drush_valid_root($path)) {
      $drupal_root = $path;
      break;
    }
    else {
      // Move up dir by dir and check each.
      while ($path = _drush_shift_path_up($path)) {
        if ($follow_symlinks && is_link($path)) {
          $path = realpath($path);
        }
        if (drush_valid_root($path)) {
          $drupal_root = $path;
          break 2;
        }
      }
    }
  }

  return $drupal_root;
}

/**
 * Checks whether given path qualifies as a Drupal root.
 *
 * @param string
 *   Path to check.
 *
 * @return string
 *   The relative path to common.inc (varies by Drupal version), or FALSE if not
 *   a Drupal root.
 */
function drush_valid_root($path) {
  $bootstrap_class = drush_bootstrap_class_for_root($path);
  return $bootstrap_class != NULL;
}

/**
 * Tests the currently loaded database credentials to ensure a database connection can be made.
 *
 * @param bool $log_errors
 *   (optional) If TRUE, log error conditions; otherwise be quiet.
 *
 * @return bool
 *   TRUE if database credentials are valid.
 */
function drush_valid_db_credentials() {
  try {
    $sql = drush_sql_get_class();
    if (!$sqlVersion = drush_sql_get_version()) {
      drush_log(dt('While checking DB credentials, could not instantiate SQLVersion class.'), 'debug');
      return FALSE;
    }
    if (!$sqlVersion->valid_credentials($sql->db_spec())) {
      drush_log(dt('DB credentials are invalid.'), 'debug');
      return FALSE;
    }
    return $sql->query('SELECT 1;');
  }
  catch (Exception $e) {
    drush_log(dt('Checking DB credentials yielded error: @e', array('@e' => $e->getMessage())), 'debug');
    return FALSE;
  }
}

/**
 * Determine a proper way to call drush again
 *
 * This check if we were called directly or as an argument to some
 * wrapper command (php and sudo are checked now).
 *
 * Calling ./drush.php directly yields the following environment:
 *
 * _SERVER["argv"][0] => ./drush.php
 *
 * Calling php ./drush.php also yields the following:
 *
 * _SERVER["argv"][0] => ./drush.php
 *
 * Note that the $_ global is defined only in bash and therefore cannot
 * be relied upon.
 *
 * The DRUSH_COMMAND constant is initialised to the value of this
 * function when environment.inc is loaded.
 *
 * @see DRUSH_COMMAND
 */
function drush_find_drush() {
  if ($drush = realpath($_SERVER['argv']['0'])) {
    return Path::canonicalize($drush);
  }
  return FALSE;
}

/**
 * Verify that we are running PHP through the command line interface.
 *
 * This function is useful for making sure that code cannot be run via the web server,
 * such as a function that needs to write files to which the web server should not have
 * access to.
 *
 * @return
 *   A boolean value that is true when PHP is being run through the command line,
 *   and false if being run through cgi or mod_php.
 */
function drush_verify_cli() {
  return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0));
}

/**
 * Build a drush command suitable for use for Drush to call itself
 * e.g. in backend_invoke.
 */
function drush_build_drush_command($drush_path = NULL, $php = NULL, $os = NULL, $remote_command = FALSE, $environment_variables = array()) {
  $os = _drush_get_os($os);
  $additional_options = '';
  $prefix = '';
  if (!$drush_path) {
    if (!$remote_command) {
      $drush_path = DRUSH_COMMAND;
    }
    else {
      $drush_path = 'drush'; // drush_is_windows($os) ? 'drush.bat' : 'drush';
    }
  }
  // If the path to drush points to drush.php, then we will need to
  // run it via php rather than direct execution.  By default, we
  // will use 'php' unless something more specific was passed in
  // via the --php flag.
  if (substr($drush_path, -4) == ".php") {
    if (!isset($php)) {
      $php = drush_get_option('php');
      if (!isset($php)) {
        $php = 'php';
      }
    }
    if (isset($php) && ($php != "php")) {
      $additional_options .= ' --php=' . drush_escapeshellarg($php, $os);
    }
    // We will also add in the php options from --php-options
    $prefix .= drush_escapeshellarg($php, $os);
    $php_options = implode(' ', drush_get_context_options('php-options'));
    if (!empty($php_options)) {
      $prefix .= ' ' . $php_options;
      $additional_options .= ' --php-options=' . drush_escapeshellarg($php_options, $os);
    }
  }
  else {
    // Set environment variables to propogate config to redispatched calls.
    if (drush_has_bash($os)) {
      if ($php) {
        $environment_variables['DRUSH_PHP'] = $php;
      }
      if ($php_options_alias = drush_get_option('php-options', NULL, 'alias')) {
        $environment_variables['PHP_OPTIONS'] = $php_options_alias;
      }
      $columns = drush_get_context('DRUSH_COLUMNS');
      if (($columns) && ($columns != 80)) {
        $environment_variables['COLUMNS'] = $columns;
      }
    }
  }

  // Add environmental variables, if present
  if (!empty($environment_variables)) {
    $env = 'env';
    foreach ($environment_variables as $key=>$value) {
      $env .= ' ' . drush_escapeshellarg($key, $os) . '=' . drush_escapeshellarg($value, $os);
    }
    $prefix = $env . ' ' . $prefix;
  }

  return trim($prefix . ' ' . drush_escapeshellarg($drush_path, $os) . $additional_options);
}

/**
 * Check if the operating system is Winodws
 * running some variant of cygwin -- either
 * Cygwin or the MSYSGIT shell.  If you care
 * which is which, test mingw first.
 */
function drush_is_cygwin($os = NULL) {
  return _drush_test_os($os, array("CYGWIN","CWRSYNC","MINGW"));
}

function drush_is_mingw($os = NULL) {
  return _drush_test_os($os, array("MINGW"));
}

/**
 * Return tar executable name specific for the current OS
 */
function drush_get_tar_executable() {
  return drush_is_windows() ? (drush_is_mingw() ? "tar.exe" : "bsdtar.exe") : "tar";
}

/**
 * Check if the operating system is OS X.
 * This will return TRUE for Mac OS X (Darwin).
 */
function drush_is_osx($os = NULL) {
  return _drush_test_os($os, array("DARWIN"));
}

/**
 * Checks if the operating system has bash.
 *
 * MinGW has bash, but PHP isn't part of MinGW and hence doesn't run in bash.
 */
function drush_has_bash($os = NULL) {
  return (drush_is_cygwin($os) && !drush_is_mingw($os)) || !drush_is_windows($os);
}

/**
 * Checks operating system and returns
 * supported bit bucket folder.
 */
function drush_bit_bucket() {
  if (drush_has_bash()) {
    return '/dev/null';
  }
  else {
    return 'nul';
  }
}

/**
 * Return the OS we are running under.
 *
 * @return string
 *   Linux
 *   WIN* (e.g. WINNT)
 *   CYGWIN
 *   MINGW* (e.g. MINGW32)
 */
function _drush_get_os($os = NULL) {
  // The special os "CWRSYNC" can be used to indicate that we are testing
  // a path that will be passed as an argument to cwRsync, which requires
  // that the path be converted to /cygdrive/c/path, even on DOS or Powershell.
  // The special os "RSYNC" can be used to indicate that we want to assume
  // "CWRSYNC" when cwrsync is installed, or default to the local OS otherwise.
  if (strtoupper($os) == "RSYNC") {
    $os = _drush_get_os("LOCAL");
    // For now we assume that cwrsync is always installed on Windows, and never installed son any other platform.
    return drush_is_windows($os) ? "CWRSYNC" : $os;
  }
  // We allow "LOCAL" to document, in instances where some parameters are being escaped
  // for use on a remote machine, that one particular parameter will always be used on
  // the local machine (c.f. drush_backend_invoke).
  if (isset($os) && ($os != "LOCAL")) {
    return $os;
  }
  if (_drush_test_os(getenv("MSYSTEM"), array("MINGW"))) {
    return getenv("MSYSTEM");
  }
  // QUESTION: Can we differentiate between DOS and POWERSHELL? They appear to have the same environment.
  // At the moment, it does not seem to matter; they behave the same from PHP.
  // At this point we will just return PHP_OS.
  return PHP_OS;
}

function _drush_test_os($os, $os_list_to_check) {
  $os = _drush_get_os($os);
  foreach ($os_list_to_check as $test) {
    if (strtoupper(substr($os, 0, strlen($test))) == strtoupper($test)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Read the drush info file.
 */
function drush_read_drush_info() {
  $drush_info_file = dirname(__FILE__) . '/../drush.info';

  return parse_ini_file($drush_info_file);
}

/**
 * Make a determination whether or not the given
 * host is local or not.
 *
 * @param host
 *   A hostname, 'localhost' or '127.0.0.1'.
 * @return
 *   True if the host is local.
 */
function drush_is_local_host($host) {
  // Check to see if the provided host is "local".
  // @see hook_drush_sitealias_alter() in drush.api.php.
  if (
    ($host == 'localhost') ||
    ($host == '127.0.0.1')
  ) {
    return TRUE;
  }

  return FALSE;
}

/**
 * Return the user's home directory.
 */
function drush_server_home() {
  try {
    return Path::getHomeDirectory();
  } catch (Exception $e) {
    return NULL;
  }
}

/**
 * Return the name of the user running drush.
 */
function drush_get_username() {
  $name = NULL;
  if (!$name = getenv("username")) { // Windows
    if (!$name = getenv("USER")) {
      // If USER not defined, use posix
      if (function_exists('posix_getpwuid')) {
        $processUser = posix_getpwuid(posix_geteuid());
        $name = $processUser['name'];
      }
    }
  }
  return $name;
}

/**
 * The path to the global cache directory.
 *
 * @param subdir
 *   Return the specified subdirectory inside the global
 *   cache directory instead.  The subdirectory is created.
 */
function drush_directory_cache($subdir = '') {
  $cache_locations = array();
  if (getenv('CACHE_PREFIX')) {
    $cache_locations[getenv('CACHE_PREFIX')] = 'cache';
  }
  $home = drush_server_home();
  if ($home) {
    $cache_locations[$home] = '.drush/cache';
  }
  $cache_locations[drush_find_tmp()] = 'drush-' . drush_get_username() . '/cache';
  foreach ($cache_locations as $base => $dir) {
    if (!empty($base) && is_writable($base)) {
      $cache_dir = $base . '/' . $dir;
      if (!empty($subdir)) {
        $cache_dir .= '/' . $subdir;
      }
      if (drush_mkdir($cache_dir)) {
        return $cache_dir;
      }
      else {
        // If the base directory is writable, but the cache directory
        // is not, then we will get an error. The error will be displayed,
        // but we will still call drush_clear_error so that we can go
        // on and try the next location to see if we can find a cache
        // directory somewhere.
        drush_clear_error();
      }
    }
  }
  return drush_set_error('DRUSH_NO_WRITABLE_CACHE', dt('Drush must have a writable cache directory; please insure that one of the following locations is writable: @locations',
    array('@locations' => implode(',', array_keys($cache_locations)))));
}

/**
 * Get complete information for all available extensions (modules and themes).
 *
 * @return
 *   An array containing info for all available extensions. In D8, these are Extension objects.
 */
function drush_get_extensions($include_hidden = TRUE) {
  drush_include_engine('drupal', 'environment');
  $extensions = array_merge(drush_get_modules($include_hidden), drush_get_themes($include_hidden));
  foreach ($extensions as $name => $extension) {
    if (isset($extension->info['name'])) {
      $extensions[$name]->label = $extension->info['name'].' ('.$name.')';
    }
    else {
      drush_log(dt("Extension !name provides no human-readable name in .info file.", array('!name' => $name), LogLevel::DEBUG));
      $extensions[$name]->label = $name.' ('.$name.')';
    }
    if (empty($extension->info['package'])) {
      $extensions[$name]->info['package'] = dt('Other');
    }
  }
  return $extensions;
}

/**
 * Gets the extension name.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension name.
 */
function drush_extension_get_name($info) {
  drush_include_engine('drupal', 'environment');
  return _drush_extension_get_name($info);
}

/**
 * Gets the extension type.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension type.
 */
function drush_extension_get_type($info) {
  drush_include_engine('drupal', 'environment');
  return _drush_extension_get_type($info);
}

/**
 * Gets the extension path.
 *
 * @param $info
 *   The extension info.
 * @return string
 *   The extension path.
 */
function drush_extension_get_path($info) {
  drush_include_engine('drupal', 'environment');
  return _drush_extension_get_path($info);
}

/**
 * Test compatibility of a extension with version of drupal core and php.
 *
 * @param $file Extension object as returned by system_rebuild_module_data().
 * @return FALSE if the extension is compatible.
 */
function drush_extension_check_incompatibility($file) {
  if (!isset($file->info['core']) || $file->info['core'] != drush_get_drupal_core_compatibility()) {
    return 'Drupal';
  }
  if (version_compare(phpversion(), $file->info['php']) < 0) {
    return 'PHP';
  }
  return FALSE;
}

/**
 *
 */
function drush_drupal_required_modules($modules) {
  drush_include_engine('drupal', 'environment');
  return _drush_drupal_required_modules($modules);
}

/**
 * Return the default theme.
 *
 * @return
 *  Machine name of the default theme.
 */
function drush_theme_get_default() {
  drush_include_engine('drupal', 'environment');
  return _drush_theme_default();
}

/**
 * Return the administration theme.
 *
 * @return
 *  Machine name of the administration theme.
 */
function drush_theme_get_admin() {
  drush_include_engine('drupal', 'environment');
  return _drush_theme_admin();
}

/**
 * Return the path to public files directory.
 */
function drush_file_get_public() {
  drush_include_engine('drupal', 'environment');
  return _drush_file_public_path();
}

/**
 * Return the path to private files directory.
 */
function drush_file_get_private() {
  drush_include_engine('drupal', 'environment');
  return _drush_file_private_path();
}

/**
 * Returns the sitewide Drupal directory for extensions.
 */
function drush_drupal_sitewide_directory($major_version = NULL) {
  if (!$major_version) {
    $major_version = drush_drupal_major_version();
  }
  return ($major_version < 8) ? 'sites/all' : '';
}

/**
 * Helper function to get core compatibility constant.
 *
 * @return string
 *   The Drupal core compatibility constant.
 */
function drush_get_drupal_core_compatibility() {
  if (defined('DRUPAL_CORE_COMPATIBILITY')) {
    return DRUPAL_CORE_COMPATIBILITY;
  }
  elseif (defined('\Drupal::CORE_COMPATIBILITY')) {
    return \Drupal::CORE_COMPATIBILITY;
  }
}

/**
 * Set Env. Variables for given site-alias.
 */
function drush_set_environment_vars(array $site_record) {
  if (!empty($site_record)) {
    $os = drush_os($site_record);
    if (isset($site_record['#env-vars'])) {
      foreach ($site_record['#env-vars'] as $var => $value) {
        $env_var = drush_escapeshellarg($var, $os, TRUE) . '=' . drush_escapeshellarg($value, $os, TRUE);
        putenv($env_var);
      }
    }
  }
}
<?php

/**
 * @file
 *   Functions for executing system commands. (e.g. exec(), system(), ...).
 */

use Drush\Log\LogLevel;

/**
 * @defgroup commandwrappers Functions to execute commands.
 * @{
 */

/**
 * Calls 'system()' function, passing through all arguments unchanged.
 *
 * This should be used when calling possibly mutative or destructive functions
 * (e.g. unlink() and other file system functions) so that can be suppressed
 * if the simulation mode is enabled.
 *
 * @param $exec
 *   The shell command to execute.  Parameters should already be escaped.
 * @return
 *   The result code from system():  0 == success.
 *
 * @see drush_shell_exec()
 */
function drush_op_system($exec) {
  if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
    drush_print("Calling system($exec);", 0, STDERR);
  }
  if (drush_get_context('DRUSH_SIMULATE')) {
    return 0;
  }

  // Throw away output.  Use drush_shell_exec() to capture output.
  system($exec, $result_code);

  return $result_code;
}

/**
 * Executes a shell command at a new working directory.
 * The old cwd is restored on exit.
 *
 * @param $effective_wd
 *   The new working directory to execute the shell command at.
 * @param $cmd
 *   The command to execute. May include placeholders used for sprintf.
 * @param ...
 *   Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line.
 * @return
 *   TRUE on success, FALSE on failure
 */
function drush_shell_cd_and_exec($effective_wd, $cmd) {
  $args = func_get_args();

  $effective_wd = array_shift($args);
  $cwd = getcwd();
  drush_op('chdir', $effective_wd);
  $result = call_user_func_array('drush_shell_exec', $args);
  drush_op('chdir', $cwd);
  return $result;
}

/**
 * Executes a shell command.
 * Output is only printed if in verbose mode.
 * Output is stored and can be retrieved using drush_shell_exec_output().
 * If in simulation mode, no action is taken.
 *
 * @param $cmd
 *   The command to execute. May include placeholders used for sprintf.
 * @param ...
 *   Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line.
 * @return
 *   TRUE on success, FALSE on failure
 */
function drush_shell_exec($cmd) {
  return _drush_shell_exec(func_get_args());
}

/**
 * Returns executable code for invoking preferred test editor.
 *
 * The next line after calling this function is usually
 *   @code drush_shell_exec_interactive($exec, $filepath, $filepath) @endcode
 *
 * @see drush_config_edit()
 */
function drush_get_editor() {
  $bg = drush_get_option('bg') ? '&' : '';
  // see http://drupal.org/node/1740294
  $exec = drush_get_option('editor', '${VISUAL-${EDITOR-vi}}') . " %s $bg";
  return $exec;
}

/**
 * Executes a command in interactive mode.
 *
 * @see drush_shell_exec.
 */
function drush_shell_exec_interactive($cmd) {
  return _drush_shell_exec(func_get_args(), TRUE);
}

/**
 * Internal function: executes a shell command on the
 * local machine.  This function should not be used
 * in instances where ssh is utilized to execute a
 * command remotely; otherwise, remote operations would
 * fail if executed from a Windows machine to a remote
 * Linux server.
 *
 * @param $args
 *   The command and its arguments.
 * @param $interactive
 *   Whether to run in
 *
 * @return
 *   TRUE on success, FALSE on failure
 *
 * @see drush_shell_exec.
 */
function _drush_shell_exec($args, $interactive = FALSE) {
  // Do not change the command itself, just the parameters.
  for ($x = 1; $x < count($args); $x++) {
    $args[$x] = drush_escapeshellarg($args[$x]);
  }
  // Important: we allow $args to take one of two forms here.  If
  // there is only one item in the array, it is the already-escaped
  // command string, but otherwise sprintf is used.  In the case
  // of pre-escaped strings, sprintf will fail if any of the escaped
  // parameters contain '%', so we must not call sprintf unless necessary.
  if (count($args) == 1) {
    $command = $args[0];
  }
  else {
    $command = call_user_func_array('sprintf', $args);
  }

  if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
    drush_print('Executing: ' . $command, 0, STDERR);
  }
  if (!drush_get_context('DRUSH_SIMULATE')) {
    if ($interactive) {
      $result = drush_shell_proc_open($command);
      return ($result == 0) ? TRUE : FALSE;
    }
    else {
      exec($command . ' 2>&1', $output, $result);
      _drush_shell_exec_output_set($output);

      if (drush_get_context('DRUSH_DEBUG')) {
        foreach ($output as $line) {
          drush_print($line, 2);
        }
      }

      // Exit code 0 means success.
      return ($result == 0);
    }
  }
  else {
    return TRUE;
  }
}

/**
 * Determine whether 'which $command' can find
 * a command on this system.
 */
function drush_which($command) {
  exec("which $command 2>&1", $output, $result);
  return ($result == 0);
}

/**
 * Build an SSH string including an optional fragment of bash. Commands that use
 * this should also merge drush_shell_proc_build_options() into their
 * command options. @see ssh_drush_command().
 *
 * @param array $site
 *   A site alias record.
 * @param string $command
 *   An optional bash fragment.
 * @param string $cd
 *   An optional directory to change into before executing the $command. Set to
 *   boolean TRUE to change into $site['root'] if available.
 * @param boolean $interactive
 *   Force creation of a tty
 * @return string
 *   A string suitable for execution with drush_shell_remote_exec().
 */
function drush_shell_proc_build($site, $command = '', $cd = NULL, $interactive = FALSE) {
  // Build up the command. TODO: We maybe refactor this soon.
  $hostname = drush_remote_host($site);
  $ssh_options = drush_sitealias_get_option($site, 'ssh-options', "-o PasswordAuthentication=no");
  $os = drush_os($site);
  if (drush_sitealias_get_option($site, 'tty') || $interactive) {
    $ssh_options .= ' -t';
  }

  $cmd = "ssh " . $ssh_options . " " . $hostname;

  if ($cd === TRUE) {
    if (array_key_exists('root', $site)) {
      $cd = $site['root'];
    }
    else {
      $cd = FALSE;
    }
  }
  if ($cd) {
    $command = 'cd ' . drush_escapeshellarg($cd, $os) . ' && ' . $command;
  }

  if (!empty($command)) {
    if (!drush_get_option('escaped', FALSE)) {
      $cmd .= " " . drush_escapeshellarg($command, $os);
    }
    else {
      $cmd .= " $command";
    }
  }

  return $cmd;
}

/**
 * Execute bash command using proc_open().
 *
 * @returns
 *   Exit code from launched application
 *     0 no error
 *     1 general error
 *     127 command not found
 */
function drush_shell_proc_open($cmd) {
  if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
    drush_print("Calling proc_open($cmd);", 0, STDERR);
  }
  if (!drush_get_context('DRUSH_SIMULATE')) {
    $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes);
    $proc_status = proc_get_status($process);
    $exit_code = proc_close($process);
    return ($proc_status["running"] ? $exit_code : $proc_status["exitcode"] );
  }
  return 0;
}

/**
 * Used by definition of ssh and other commands that call into drush_shell_proc_build()
 * to declare their options.
 */
function drush_shell_exec_proc_build_options() {
  return array(
   'ssh-options' => 'A string of extra options that will be passed to the ssh command (e.g. "-p 100")',
    'tty' => 'Create a tty (e.g. to run an interactive program).',
    'escaped' => 'Command string already escaped; do not add additional quoting.',
  );
}

/**
 * Determine the appropriate os value for the
 * specified site record
 *
 * @returns
 *   NULL for 'same as local machine', 'Windows' or 'Linux'.
 */
function drush_os($site_record = NULL) {
  // Default to $os = NULL, meaning 'same as local machine'
  $os = NULL;
  // If the site record has an 'os' element, use it
  if (isset($site_record) && array_key_exists('os', $site_record)) {
    $os = $site_record['os'];
  }
  // Otherwise, we will assume that all remote machines are Linux
  // (or whatever value 'remote-os' is set to in drushrc.php).
  elseif (isset($site_record) && array_key_exists('remote-host', $site_record) && !empty($site_record['remote-host'])) {
    $os = drush_get_option('remote-os', 'Linux');
  }

  return $os;
}

/**
 * Determine the remote host (username@hostname.tld) for
 * the specified site.
 */
function drush_remote_host($site, $prefix = '') {
  $hostname = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-host', '', $prefix), "LOCAL");
  $username = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-user', '', $prefix), "LOCAL");
  return $username . (empty($username) ? '' : '@') . $hostname;
}

/**
 * Make an attempt to simply wrap the arg with the
 * kind of quote characters it does not already contain.
 * If it contains both kinds, then this function reverts to drush_escapeshellarg.
 */
function drush_wrap_with_quotes($arg) {
  $has_double = strpos($arg, '"') !== FALSE;
  $has_single = strpos($arg, "'") !== FALSE;
  if ($has_double && $has_single) {
    return drush_escapeshellarg($arg);
  }
  elseif ($has_double) {
    return "'" . $arg . "'";
  }
  else {
    return '"' . $arg . '"';
  }
}

/**
 * Platform-dependent version of escapeshellarg().
 * Given the target platform, return an appropriately-escaped
 * string. The target platform may be omitted for args that
 * are /known/ to be for the local machine.
 * Use raw to get an unquoted version of the escaped arg.
 * Notice that you can't add quotes later until you know the platform.
 */

/**
 * Stores output for the most recent shell command.
 * This should only be run from drush_shell_exec().
 *
 * @param array|bool $output
 *   The output of the most recent shell command.
 *   If this is not set the stored value will be returned.
 */
function _drush_shell_exec_output_set($output = FALSE) {
  static $stored_output;
  if ($output === FALSE) return $stored_output;
  $stored_output = $output;
}

/**
 * Returns the output of the most recent shell command as an array of lines.
 */
function drush_shell_exec_output() {
  return _drush_shell_exec_output_set();
}

/**
 * Starts a background browser/tab for the current site or a specified URL.
 *
 * Uses a non-blocking proc_open call, so Drush execution will continue.
 *
 * @param $uri
 *   Optional URI or site path to open in browser. If omitted, or if a site path
 *   is specified, the current site home page uri will be prepended if the sites
 *   hostname resolves.
 * @return
 *   TRUE if browser was opened, FALSE if browser was disabled by the user or a,
 *   default browser could not be found.
 */
function drush_start_browser($uri = NULL, $sleep = FALSE, $port = FALSE) {
  if ($browser = drush_get_option('browser', TRUE)) {
    // We can only open a browser if we have a DISPLAY environment variable on
    // POSIX or are running Windows or OS X.
    if (!drush_get_context('DRUSH_SIMULATE') && !getenv('DISPLAY') && !drush_is_windows() && !drush_is_osx()) {
      drush_log(dt('No graphical display appears to be available, not starting browser.'), LogLevel::NOTICE);
      return FALSE;
    }
    $host = parse_url($uri, PHP_URL_HOST);
    if (!$host) {
      // Build a URI for the current site, if we were passed a path.
      $site = drush_get_context('DRUSH_URI');
      $host = parse_url($site, PHP_URL_HOST);
      $uri = $site . '/' . ltrim($uri, '/');
    }
    // Validate that the host part of the URL resolves, so we don't attempt to
    // open the browser for http://default or similar invalid hosts.
    $hosterror = (gethostbynamel($host) === FALSE);
    $iperror = (ip2long($host) && gethostbyaddr($host) == $host);
    if (!drush_get_context('DRUSH_SIMULATE') && ($hosterror || $iperror)) {
      drush_log(dt('!host does not appear to be a resolvable hostname or IP, not starting browser. You may need to use the --uri option in your command or site alias to indicate the correct URL of this site.', array('!host' => $host)), LogLevel::WARNING);
      return FALSE;
    }
    if ($port) {
      $uri = str_replace($host, "localhost:$port", $uri);
    }
    if ($browser === TRUE) {
      // See if we can find an OS helper to open URLs in default browser.
      if (drush_shell_exec('which xdg-open')) {
        $browser = 'xdg-open';
      }
      else if (drush_shell_exec('which open')) {
        $browser = 'open';
      }
      else if (!drush_has_bash()) {
        $browser = 'start';
      }
      else {
        // Can't find a valid browser.
        $browser = FALSE;
      }
    }
    $prefix = '';
    if ($sleep) {
      $prefix = 'sleep ' . $sleep . ' && ';
    }
    if ($browser) {
      drush_log(dt('Opening browser !browser at !uri', array('!browser' => $browser, '!uri' => $uri)));
      if (!drush_get_context('DRUSH_SIMULATE')) {
        $pipes = array();
        proc_close(proc_open($prefix . $browser . ' ' . drush_escapeshellarg($uri) . ' 2> ' . drush_bit_bucket() . ' &', array(), $pipes));
      }
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * @} End of "defgroup commandwrappers".
 */
<?php

/**
 * @file
 * Filesystem utilities.
 */
use Webmozart\PathUtil\Path;

/**
 * @defgroup filesystemfunctions Filesystem convenience functions.
 * @{
 */

/**
 * Behavior for drush_copy_dir() and drush_move_dir() when destinations exist.
 */
define('FILE_EXISTS_ABORT', 0);
define('FILE_EXISTS_OVERWRITE', 1);
define('FILE_EXISTS_MERGE', 2);

 /**
  * Determines whether the provided path is absolute or not
  * on the specified O.S. -- starts with "/" on *nix, or starts
  * with "[A-Z]:\" or "[A-Z]:/" on Windows.
  */
function drush_is_absolute_path($path, $os = NULL) {
  // Relative paths will never start with a '/', even on Windows,
  // so it is safe to just call all paths that start with a '/'
  // absolute.  This simplifies things for Windows with CYGWIN / MINGW / CWRSYNC,
  // where absolute paths sometimes start c:\path and sometimes
  // start /cygdrive/c/path.
  if ($path[0] == '/') {
    return TRUE;
  }
  if (drush_is_windows($os)) {
    return preg_match('@^[a-zA-Z]:[\\\/]@', $path);
  }
  return FALSE;
}

/**
 * If we are going to pass a path to exec or proc_open,
 * then we need to fix it up under CYGWIN or MINGW.  In
 * both of these environments, PHP works with absolute paths
 * such as "C:\path".  CYGWIN expects these to be converted
 * to "/cygdrive/c/path" and MINGW expects these to be converted
 * to "/c/path"; otherwise, the exec will not work.
 *
 * This call does nothing if the parameter is not an absolute
 * path, or we are not running under CYGWIN / MINGW.
 *
 * UPDATE:  It seems I was mistaken; this is only necessary if we
 * are using cwRsync.  We do not need to correct every path to
 * exec or proc_open (thank god).
 */
function drush_correct_absolute_path_for_exec($path, $os = NULL) {
  if (drush_is_windows() && drush_is_absolute_path($path, "WINNT")) {
    if (drush_is_mingw($os)) {
      $path = preg_replace('/(\w):/', '/${1}', str_replace('\\', '/', $path));
    }
    elseif (drush_is_cygwin($os)) {
      $path = preg_replace('/(\w):/', '/cygdrive/${1}', str_replace('\\', '/', $path));
    }
  }
  return $path;
}

/**
 * Remove the trailing DIRECTORY_SEPARATOR from a path.
 * Will actually remove either / or \ on Windows.
 */
function drush_trim_path($path, $os = NULL) {
  if (drush_is_windows($os)) {
    return rtrim($path, '/\\');
  }
  else {
    return rtrim($path, '/');
  }
}

/**
 * Makes sure the path has only path separators native for the current operating system
 */
function drush_normalize_path($path) {
  if (drush_is_windows()) {
    $path = str_replace('/', '\\',  strtolower($path));
  }
  else {
    $path = str_replace('\\', '/', $path);
  }
  return drush_trim_path($path);
}

/**
 * Calculates a single md5 hash for all files a directory (incuding subdirectories)
 */
function drush_dir_md5($dir) {
  $flist = drush_scan_directory($dir, '/./', array('.', '..'), 0, TRUE, 'filename', 0, TRUE);
  $hashes = array();
  foreach ($flist as $f) {
    $sum = array();
    exec('cksum ' . escapeshellarg($f->filename), $sum);
    $hashes[] = trim(str_replace(array($dir), array(''), $sum[0]));
  }
  sort($hashes);
  return md5(implode("\n", $hashes));
}

/**
 * Deletes the specified file or directory and everything inside it.
 *
 * Usually respects read-only files and folders. To do a forced delete use
 * drush_delete_tmp_dir() or set the parameter $forced.
 *
 * @param string $dir
 *   The file or directory to delete.
 * @param bool $force
 *   Whether or not to try everything possible to delete the directory, even if
 *   it's read-only. Defaults to FALSE.
 * @param bool $follow_symlinks
 *   Whether or not to delete symlinked files. Defaults to FALSE--simply
 *   unlinking symbolic links.
 *
 * @return bool
 *   FALSE on failure, TRUE if everything was deleted.
 */
function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) {
  // Do not delete symlinked files, only unlink symbolic links
  if (is_link($dir) && !$follow_symlinks) {
    return unlink($dir);
  }
  // Allow to delete symlinks even if the target doesn't exist.
  if (!is_link($dir) && !file_exists($dir)) {
    return TRUE;
  }
  if (!is_dir($dir)) {
    if ($force) {
      // Force deletion of items with readonly flag.
      @chmod($dir, 0777);
    }
    return unlink($dir);
  }
  if (drush_delete_dir_contents($dir, $force) === FALSE) {
    return FALSE;
  }
  if ($force) {
    // Force deletion of items with readonly flag.
    @chmod($dir, 0777);
  }
  return rmdir($dir);
}

/**
 * Deletes the contents of a directory.
 *
 * @param string $dir
 *   The directory to delete.
 * @param bool $force
 *   Whether or not to try everything possible to delete the contents, even if
 *   they're read-only. Defaults to FALSE.
 *
 * @return bool
 *   FALSE on failure, TRUE if everything was deleted.
 */
function drush_delete_dir_contents($dir, $force = FALSE) {
  $scandir = @scandir($dir);
  if (!is_array($scandir)) {
    return FALSE;
  }

  foreach ($scandir as $item) {
    if ($item == '.' || $item == '..') {
      continue;
    }
    if ($force) {
      @chmod($dir, 0777);
    }
    if (!drush_delete_dir($dir . '/' . $item, $force)) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Deletes the provided file or folder and everything inside it.
 * This function explicitely tries to delete read-only files / folders.
 *
 * @param $dir
 *   The directory to delete
 * @return
 *   FALSE on failure, TRUE if everything was deleted
 */
function drush_delete_tmp_dir($dir) {
  return drush_delete_dir($dir, TRUE);
}

/**
 * Copy $src to $dest.
 *
 * @param $src
 *   The directory to copy.
 * @param $dest
 *   The destination to copy the source to, including the new name of
 *   the directory.  To copy directory "a" from "/b" to "/c", then
 *   $src = "/b/a" and $dest = "/c/a".  To copy "a" to "/c" and rename
 *   it to "d", then $dest = "/c/d".
 * @param $overwrite
 *   Action to take if destination already exists.
 *     - FILE_EXISTS_OVERWRITE - completely removes existing directory.
 *     - FILE_EXISTS_ABORT - aborts the operation.
 *     - FILE_EXISTS_MERGE - Leaves existing files and directories in place.
 * @return
 *   TRUE on success, FALSE on failure.
 */
function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) {
  // Preflight based on $overwrite if $dest exists.
  if (file_exists($dest)) {
    if ($overwrite === FILE_EXISTS_OVERWRITE) {
      drush_op('drush_delete_dir', $dest, TRUE);
    }
    elseif ($overwrite === FILE_EXISTS_ABORT) {
      return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
    }
    elseif ($overwrite === FILE_EXISTS_MERGE) {
      // $overwrite flag may indicate we should merge instead.
      drush_log(dt('Merging existing !dest directory', array('!dest' => $dest)));
    }
  }
  // $src readable?
  if (!is_readable($src)) {
    return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  }
  // $dest writable?
  if (!is_writable(dirname($dest))) {
    return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  }
  // Try to do a recursive copy.
  if (@drush_op('_drush_recursive_copy', $src, $dest)) {
    return TRUE;
  }

  return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest)));
}

/**
 * Internal function called by drush_copy_dir; do not use directly.
 */
function _drush_recursive_copy($src, $dest) {
  // all subdirectories and contents:
  if(is_dir($src)) {
    if (!drush_mkdir($dest, TRUE)) {
      return FALSE;
    }
    $dir_handle = opendir($src);
    while($file = readdir($dir_handle)) {
      if ($file != "." && $file != "..") {
        if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) {
          return FALSE;
        }
      }
    }
    closedir($dir_handle);
  }
  elseif (is_link($src)) {
    symlink(readlink($src), $dest);
  }
  elseif (!copy($src, $dest)) {
    return FALSE;
  }

  // Preserve file modification time.
  // https://github.com/drush-ops/drush/pull/1146
  touch($dest, filemtime($src));

  // Preserve execute permission.
  if (!is_link($src) && !drush_is_windows()) {
    // Get execute bits of $src.
    $execperms = fileperms($src) & 0111;
    // Apply execute permissions if any.
    if ($execperms > 0) {
      $perms = fileperms($dest) | $execperms;
      chmod($dest, $perms);
    }
  }

  return TRUE;
}

/**
 * Move $src to $dest.
 *
 * If the php 'rename' function doesn't work, then we'll do copy & delete.
 *
 * @param $src
 *   The directory to move.
 * @param $dest
 *   The destination to move the source to, including the new name of
 *   the directory.  To move directory "a" from "/b" to "/c", then
 *   $src = "/b/a" and $dest = "/c/a".  To move "a" to "/c" and rename
 *   it to "d", then $dest = "/c/d" (just like php rename function).
 * @param $overwrite
 *   If TRUE, the destination will be deleted if it exists.
 * @return
 *   TRUE on success, FALSE on failure.
 */
function drush_move_dir($src, $dest, $overwrite = FALSE) {
  // Preflight based on $overwrite if $dest exists.
  if (file_exists($dest)) {
    if ($overwrite) {
      drush_op('drush_delete_dir', $dest, TRUE);
    }
    else {
      return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
    }
  }
  // $src readable?
  if (!drush_op('is_readable', $src)) {
    return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  }
  // $dest writable?
  if (!drush_op('is_writable', dirname($dest))) {
    return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  }
  // Try rename. It will fail if $src and $dest are not in the same partition.
  if (@drush_op('rename', $src, $dest)) {
    return TRUE;
  }
  // Eventually it will create an empty file in $dest. See
  // http://www.php.net/manual/es/function.rename.php#90025
  elseif (is_file($dest)) {
    drush_op('unlink', $dest);
  }

  // If 'rename' fails, then we will use copy followed
  // by a delete of the source.
  if (drush_copy_dir($src, $dest)) {
    drush_op('drush_delete_dir', $src, TRUE);
    return TRUE;
  }

  return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('!src' => $src, '!dest' => $dest)));
}

/**
 * Cross-platform compatible helper function to recursively create a directory tree.
 *
 * @param path
 *   Path to directory to create.
 * @param required
 *   If TRUE, then drush_mkdir will call drush_set_error on failure.
 *
 * Callers should *always* do their own error handling after calling drush_mkdir.
 * If $required is FALSE, then a different location should be selected, and a final
 * error message should be displayed if no usable locations can be found.
 * @see drush_directory_cache().
 * If $required is TRUE, then the execution of the current command should be
 * halted if the required directory cannot be created.
 */
function drush_mkdir($path, $required = TRUE) {
  if (!is_dir($path)) {
    if (drush_mkdir(dirname($path))) {
      if (@mkdir($path)) {
        return TRUE;
      }
      elseif (is_dir($path) && is_writable($path)) {
        // The directory was created by a concurrent process.
        return TRUE;
      }
      else {
        if (!$required) {
          return FALSE;
        }
        if (is_writable(dirname($path))) {
          return drush_set_error('DRUSH_CREATE_DIR_FAILURE', dt('Unable to create !dir.', array('!dir' => preg_replace('/\w+\/\.\.\//', '', $path))));
        }
        else {
          return drush_set_error('DRUSH_PARENT_NOT_WRITABLE', dt('Unable to create !newdir in !dir. Please check directory permissions.', array('!newdir' => basename($path), '!dir' => realpath(dirname($path)))));
        }
      }
    }
    return FALSE;
  }
  else {
    if (!is_writable($path)) {
      if (!$required) {
        return FALSE;
      }
      return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Directory !dir exists, but is not writable. Please check directory permissions.', array('!dir' => realpath($path))));
    }
    return TRUE;
  }
}

/*
 * Determine if program exists on user's PATH.
 *
 * @return bool|null
 */
function drush_program_exists($program) {
  if (drush_has_bash()) {
    $bucket = drush_bit_bucket();
    return drush_op_system("command -v $program >$bucket 2>&1") === 0 ? TRUE : FALSE;
  }
}

/**
 * Save a string to a temporary file. Does not depend on Drupal's API.
 * The temporary file will be automatically deleted when drush exits.
 *
 * @param string $data
 * @param string $suffix
 *   Append string to filename. use of this parameter if is discouraged. @see
 *   drush_tempnam().
 * @return string
 *   A path to the file.
 */
function drush_save_data_to_temp_file($data, $suffix = NULL) {
  static $fp;

  $file = drush_tempnam('drush_', NULL, $suffix);
  $fp = fopen($file, "w");
  fwrite($fp, $data);
  $meta_data = stream_get_meta_data($fp);
  $file = $meta_data['uri'];
  fclose($fp);

  return $file;
}

/**
 * Returns the path to a temporary directory.
 *
 * This is a custom version of Drupal's file_directory_path().
 * We can't directly rely on sys_get_temp_dir() as this
 * path is not valid in some setups for Mac, and we want to honor
 * an environment variable (used by tests).
 */
function drush_find_tmp() {
  static $temporary_directory;

  if (!isset($temporary_directory)) {
    $directories = array();

    // Get user specific and operating system temp folders from system environment variables.
    // See http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true
    $tempdir = getenv('TEMP');
    if (!empty($tempdir)) {
      $directories[] = $tempdir;
    }
    $tmpdir = getenv('TMP');
    if (!empty($tmpdir)) {
      $directories[] = $tmpdir;
    }
    // Operating system specific dirs.
    if (drush_is_windows()) {
      $windir = getenv('WINDIR');
      if (isset($windir)) {
        // WINDIR itself is not writable, but it always contains a /Temp dir,
        // which is the system-wide temporary directory on older versions. Newer
        // versions only allow system processes to use it.
        $directories[] = Path::join($windir, 'Temp');
      }
    }
    else {
      $directories[] = Path::canonicalize('/tmp');
    }
    $directories[] = Path::canonicalize(sys_get_temp_dir());

    foreach ($directories as $directory) {
      if (is_dir($directory) && is_writable($directory)) {
        $temporary_directory = $directory;
        break;
      }
    }

    if (empty($temporary_directory)) {
      // If no directory has been found, create one in cwd.
      $temporary_directory = Path::join(drush_cwd(), 'tmp');
      drush_mkdir($temporary_directory, TRUE);
      if (!is_dir($temporary_directory)) {
        return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory."));
      }
      drush_register_file_for_deletion($temporary_directory);
    }
  }

  return $temporary_directory;
}

/**
 * Creates a temporary file, and registers it so that
 * it will be deleted when drush exits.  Whenever possible,
 * drush_save_data_to_temp_file() should be used instead
 * of this function.
 *
 * @param string $suffix
 *   Append this suffix to the filename. Use of this parameter is discouraged as
 *   it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052.
 *   Originally added to support Oracle driver.
 */
function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') {
  if ($tmp_dir == NULL) {
    $tmp_dir = drush_find_tmp();
  }
  $tmp_file = tempnam($tmp_dir, $pattern);
  drush_register_file_for_deletion($tmp_file);
  $tmp_file_with_suffix = $tmp_file . $suffix;
  drush_register_file_for_deletion($tmp_file_with_suffix);
  return $tmp_file_with_suffix;
}

/**
 * Creates a temporary directory and return its path.
 */
function drush_tempdir() {
  $tmp_dir = drush_trim_path(drush_find_tmp());
  $tmp_dir .= '/' . 'drush_tmp_' . uniqid(time() . '_');

  drush_mkdir($tmp_dir);
  drush_register_file_for_deletion($tmp_dir);

  return $tmp_dir;
}

/**
 * Any file passed in to this function will be deleted
 * when drush exits.
 */
function drush_register_file_for_deletion($file = NULL) {
  static $registered_files = array();

  if (isset($file)) {
    if (empty($registered_files)) {
      register_shutdown_function('_drush_delete_registered_files');
    }
    $registered_files[] = $file;
  }

  return $registered_files;
}

/**
 * Delete all of the registered temporary files.
 */
function _drush_delete_registered_files() {
  $files_to_delete = drush_register_file_for_deletion();

  foreach ($files_to_delete as $file) {
    // We'll make sure that the file still exists, just
    // in case someone came along and deleted it, even
    // though they did not need to.
    if (file_exists($file)) {
      if (is_dir($file)) {
        drush_delete_dir($file, TRUE);
      }
      else {
        @chmod($file, 0777); // Make file writeable
        unlink($file);
      }
    }
  }
}

/**
 * Decide where our backup directory should go
 *
 * @param string $subdir
 *   The name of the desired subdirectory(s) under drush-backups.
 *   Usually a database name.
 */
function drush_preflight_backup_dir($subdir = NULL) {
  $backup_dir = drush_get_context('DRUSH_BACKUP_DIR', drush_get_option('backup-location'));

  if (empty($backup_dir)) {
    // Try to use db name as subdir if none was provided.
    if (empty($subdir)) {
      $subdir = 'unknown';
      if ($sql = drush_sql_get_class()) {
        $db_spec = $sql->db_spec();
        $subdir = $db_spec['database'];
      }
    }

    // Save the date to be used in the backup directory's path name.
    $date = gmdate('YmdHis', $_SERVER['REQUEST_TIME']);

    $backup_dir = drush_get_option('backup-dir', Path::join(drush_server_home(), 'drush-backups'));
    $backup_dir = Path::join($backup_dir, $subdir, $date);
    drush_set_context('DRUSH_BACKUP_DIR', $backup_dir);
  }
  else {
    Path::canonicalize($backup_dir);
  }
  return $backup_dir;
}

/**
 * Prepare a backup directory
 */
function drush_prepare_backup_dir($subdir = NULL) {
  $backup_dir = drush_preflight_backup_dir($subdir);
  $backup_parent = Path::getDirectory($backup_dir);
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');

  if ((!empty($drupal_root)) && (strpos($backup_parent, $drupal_root) === 0)) {
    return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.'));
  }
  if (!file_exists($backup_parent)) {
    if (!drush_mkdir($backup_parent, TRUE)) {
      return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Unable to create backup directory !dir.', array('!dir' => $backup_parent)));
    }
  }
  if (!is_writable($backup_parent)) {
    return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Backup directory !dir is not writable.', array('!dir' => $backup_parent)));
  }

  if (!drush_mkdir($backup_dir, TRUE)) {
    return FALSE;
  }
  return $backup_dir;
}

/**
 * Test to see if a file exists and is not empty
 */
function drush_file_not_empty($file_to_test) {
  if (file_exists($file_to_test)) {
    clearstatcache();
    $stat = stat($file_to_test);
    if ($stat['size'] > 0) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Finds all files that match a given mask in a given directory.
 * Directories and files beginning with a period are excluded; this
 * prevents hidden files and directories (such as SVN working directories
 * and GIT repositories) from being scanned.
 *
 * @param $dir
 *   The base directory for the scan, without trailing slash.
 * @param $mask
 *   The regular expression of the files to find.
 * @param $nomask
 *   An array of files/directories to ignore.
 * @param $callback
 *   The callback function to call for each match.
 * @param $recurse_max_depth
 *   When TRUE, the directory scan will recurse the entire tree
 *   starting at the provided directory.  When FALSE, only files
 *   in the provided directory are returned.  Integer values
 *   limit the depth of the traversal, with zero being treated
 *   identically to FALSE, and 1 limiting the traversal to the
 *   provided directory and its immediate children only, and so on.
 * @param $key
 *   The key to be used for the returned array of files. Possible
 *   values are "filename", for the path starting with $dir,
 *   "basename", for the basename of the file, and "name" for the name
 *   of the file without an extension.
 * @param $min_depth
 *   Minimum depth of directories to return files from.
 * @param $include_dot_files
 *   If TRUE, files that begin with a '.' will be returned if they
 *   match the provided mask.  If FALSE, files that begin with a '.'
 *   will not be returned, even if they match the provided mask.
 * @param $depth
 *   Current depth of recursion. This parameter is only used internally and should not be passed.
 *
 * @return
 *   An associative array (keyed on the provided key) of objects with
 *   "path", "basename", and "name" members corresponding to the
 *   matching files.
 */
function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) {
  $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  $files = array();

  // Exclude Bower and Node directories.
  $nomask = array_merge($nomask, drush_get_option_list('ignored-directories', array('node_modules', 'bower_components')));

  if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) {
    while (FALSE !== ($file = readdir($handle))) {
      if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
        if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
          // Give priority to files in this folder by merging them in after any subdirectory files.
          $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
        }
        elseif ($depth >= $min_depth && preg_match($mask, $file)) {
          // Always use this match over anything already set in $files with the same $$key.
          $filename = "$dir/$file";
          $basename = basename($file);
          $name = substr($basename, 0, strrpos($basename, '.'));
          $files[$$key] = new stdClass();
          $files[$$key]->filename = $filename;
          $files[$$key]->basename = $basename;
          $files[$$key]->name = $name;
          if ($callback) {
            drush_op($callback, $filename);
          }
        }
      }
    }

    closedir($handle);
  }

  return $files;
}

/**
 * Simple helper function to append data to a given file.
 *
 * @param string $file
 *   The full path to the file to append the data to.
 * @param string $data
 *   The data to append.
 *
 * @return boolean
 *   TRUE on success, FALSE in case of failure to open or write to the file.
 */
function drush_file_append_data($file, $data) {
  if (!$fd = fopen($file, 'a+')) {
    drush_set_error(dt("ERROR: fopen(@file, 'ab') failed", array('@file' => $file)));
    return FALSE;
  }
  if (!fwrite($fd, $data)) {
    drush_set_error(dt("ERROR: fwrite(@file) failed", array('@file' => $file)) . '<pre>' . $data);
    return FALSE;
  }
  return TRUE;
}

/**
 * Return 'TRUE' if one directory is located anywhere inside
 * the other.
 */
function drush_is_nested_directory($base_dir, $test_is_nested) {
  $common = Path::getLongestCommonBasePath([$test_is_nested, $base_dir]);
  return $common == Path::canonicalize($base_dir);
}

/**
 * @} End of "defgroup filesystemfunctions".
 */
<?php

use Drupal\Core\Render\Markup;
use Drush\Log\LogLevel;

/**
 * @defgroup outputfunctions Process output text.
 * @{
 */

/**
 * Prints a message with optional indentation. In general,
 * drush_log($message, LogLevel::OK) is often a better choice than this function.
 * That gets your confirmation message (for example) into the logs for this
 * drush request. Consider that drush requests may be executed remotely and
 * non interactively.
 *
 * @param $message
 *   The message to print.
 * @param $indent
 *    The indentation (space chars)
 * @param $handle
 *    File handle to write to.  NULL will write
 *    to standard output, STDERR will write to the standard
 *    error.  See http://php.net/manual/en/features.commandline.io-streams.php
 * @param $newline
 *    Add a "\n" to the end of the output.  Defaults to TRUE.
 */
function drush_print($message = '', $indent = 0, $handle = NULL, $newline = TRUE) {
  $msg = str_repeat(' ', $indent) . (string)$message;
  if ($newline) {
    $msg .= "\n";
  }
  if (($charset = drush_get_option('output_charset')) && function_exists('iconv')) {
    $msg = iconv('UTF-8', $charset, $msg);
  }
  if (!isset($handle)) {
    $handle = STDOUT;
    // In the past, Drush would use `print` here; now that we are using
    // fwrite (to avoid problems with php sending the http headers), we
    // must explicitly capture the output, because ob_start() / ob_end()
    // does not capture output written via fwrite to STDOUT.
    drush_backend_output_collect($msg);
  }
  fwrite($handle, $msg);
}

/**
 * Print a prompt -- that is, a message with no trailing newline.
 */
function drush_print_prompt($message, $indent = 0, $handle = NULL) {
  drush_print($message, $indent, $handle, FALSE);
}

/**
 * Stores a message which is printed during drush_shutdown() if in compact mode.
 * @param $message
 *   The message to print.  If $message is an array,
 *   then each element of the array is printed on a
 *   separate line.
 */
function drush_print_pipe($message = '') {
  $buffer = &drush_get_context('DRUSH_PIPE_BUFFER' , '');
  if (is_array($message)) {
    $message = implode("\n", $message) . "\n";
  }
  $buffer .= $message;
}

/**
 * Prints an array or string.
 * @param $array
 *   The array to print.
 * @param $newline
 *    Add a "\n" to the end of the output.  Defaults to TRUE.
 */
function drush_print_r($array, $handle = NULL, $newline = TRUE) {
  drush_print(print_r($array, TRUE), 0, $handle, $newline);
}

/**
 * Format some data and print it out.  Respect --format option.
 */
function drush_print_format($input, $default_format, $metadata = NULL) {
  drush_print(drush_format($input, $metadata, drush_get_option('format', $default_format)));
}

/**
 * Prepares a variable for printing.  Loads the requested output
 * formatter and uses it to process the provided input.
 *
 * @param mixed $input
 *   A variable.
 * @param string $metadata
 *   Optional formatting metadata. Not used by all formats.
 *     'label' - text to label data with in some formats (e.g. export, config)
 * @param string $format
 *   Optional format; defaults to print_r.
 * @return string
 *   The variable formatted according to specified format.
 *   Ready for drush_print().
 */
function drush_format($input, $metadata = NULL, $format = NULL) {
  $output = '';
  // Look up the format and label, and fill in default values for metadata
  if (is_string($metadata)) {
    $metadata = array('label' => $metadata);
  }
  if (!is_array($metadata)) {
    $metadata = array();
  }
  $metadata += array(
    'metameta' => array(),
  );
  if (isset($metadata['format'])) {
    // If the format is set in metadata, then it will
    // override whatever is passed in via the $format parameter.
    $format = $metadata['format'];
  }
  if (!isset($format)) {
    // TODO: we shouldn't ever call drush_get_option here.
    // Get rid of this once we confirm that there are no
    // callers that still need it.
    $format = drush_get_option('format', 'print-r');
  }

  $formatter = drush_load_engine('outputformat', $format);
  if ($formatter) {
    if ($formatter === TRUE) {
      return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
    }
    $output = $formatter->process($input, $metadata);
  }

  return $output;
}

/**
 * Rudimentary replacement for Drupal API t() function.
 *
 * @param string
 *   String to process, possibly with replacement item.
 * @param array
 *  An associative array of replacement items.
 *
 * @return
 *   The processed string.
 *
 * @see t()
 */
function dt($string, $args = array()) {
  $output = NULL;
  if (function_exists('t') && drush_drupal_major_version() == 7) {
    $output = t($string, $args);
  }
  // The language system requires a working container which has the string
  // translation service.
  else if (drush_drupal_major_version() >= 8 && \Drupal::hasService('string_translation')) {
    // Drupal 8 removes !var replacements, creating a user-level error when
    // these are used, so we'll pre-replace these before calling translate().
    list($string, $args) = replace_legacy_dt_args($string, $args);
    $output = (string) \Drupal::translation()->translate($string, $args);
  }
  else if (function_exists('t') && drush_drupal_major_version() <= 7 && function_exists('theme')) {
    $output = t($string, $args);
  }

  // If Drupal's t() function unavailable.
  if (!isset($output)) {
    if (!empty($args)) {
      $output = strtr($string, $args);
    }
    else {
      $output = $string;
    }
  }
  return $output;
}

/**
 * Replace placeholders that begin with a '!' with '@'.
 */
function replace_legacy_dt_args(&$string, &$legacy_args) {
  $args = array();
  $replace = array();
  foreach ($legacy_args as $name => $argument) {
    if ($name[0] == '!') {
      $new_arg = '@' . substr($name, 1);
      $replace[$name] = $new_arg;
      $args[$new_arg] = Markup::create($argument);
    }
    else {
      $args[$name] = $argument;
    }
  }
  return [
    strtr($string, $replace),
    $args
  ];
}

/**
 * Convert html to readable text.  Compatible API to
 * drupal_html_to_text, but less functional.  Caller
 * might prefer to call drupal_html_to_text if there
 * is a bootstrapped Drupal site available.
 *
 * @param string $html
 *   The html text to convert.
 *
 * @return string
 *   The plain-text representation of the input.
 */
function drush_html_to_text($html, $allowed_tags = NULL) {
  $replacements = array(
    '<hr>' => '------------------------------------------------------------------------------',
    '<li>' => '  * ',
    '<h1>' => '===== ',
    '</h1>' => ' =====',
    '<h2>' => '---- ',
    '</h2>' => ' ----',
    '<h3>' => '::: ',
    '</h3>' => ' :::',
    '<br/>' => "\n",
  );
  $text = str_replace(array_keys($replacements), array_values($replacements), $html);
  return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text));
}


/**
 * Print a formatted table.
 *
 * @param $rows
 *   The rows to print.
 * @param $header
 *   If TRUE, the first line will be treated as table header.
 * @param $widths
 *   An associative array whose keys are column IDs and values are widths of each column (in characters).
 *   If not specified this will be determined automatically, based on a "best fit" algorithm.
 * @param $handle
 *    File handle to write to.  NULL will write
 *    to standard output, STDERR will write to the standard
 *    error.  See http://php.net/manual/en/features.commandline.io-streams.php
 * @return $tbl
 *   Use $tbl->getTable() to get the output from the return value.
 */
function drush_print_table($rows, $header = FALSE, $widths = array(), $handle = NULL) {
  $tbl = _drush_format_table($rows, $header, $widths);
  $output = $tbl->getTable();
  if (!stristr(PHP_OS, 'WIN')) {
    $output = str_replace("\r\n", PHP_EOL, $output);
  }

  drush_print(rtrim($output), 0, $handle);
  return $tbl;
}

/**
 * Format a table of data.
 *
 * @param $rows
 *   The rows to print.
 * @param $header
 *   If TRUE, the first line will be treated as table header.
 * @param $widths
 *   An associative array whose keys are column IDs and values are widths of each column (in characters).
 *   If not specified this will be determined automatically, based on a "best fit" algorithm.
 * @param array $console_table_options
 *   An array that is passed along when constructing a Console_Table instance.
 * @return $output
 *   The formatted output.
 */
function drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
  $tbl = _drush_format_table($rows, $header, $widths, $console_table_options);
  $output = $tbl->getTable();
  if (!drush_is_windows()) {
    $output = str_replace("\r\n", PHP_EOL, $output);
  }
  return $output;
}

function _drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
  // Add defaults.
  $tbl = new ReflectionClass('Console_Table');
  $console_table_options += array(CONSOLE_TABLE_ALIGN_LEFT , '');
  $tbl = $tbl->newInstanceArgs($console_table_options);

  $auto_widths = drush_table_column_autowidth($rows, $widths);

  // Do wordwrap on all cells.
  $newrows = array();
  foreach ($rows as $rowkey => $row) {
    foreach ($row as $col_num => $cell) {
      $newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE);
      if (isset($widths[$col_num])) {
        $newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]);
      }
    }
  }
  if ($header) {
    $headers = array_shift($newrows);
    $tbl->setHeaders($headers);
  }

  $tbl->addData($newrows);
  return $tbl;
}

/**
 * Convert an associative array of key : value pairs into
 * a table suitable for processing by drush_print_table.
 *
 * @param $keyvalue_table
 *    An associative array of key : value pairs.
 * @param $metadata
 *    'key-value-item':  If the value is an array, then
 *    the item key determines which item from the value
 *    will appear in the output.
 * @return
 *    An array of arrays, where the keys from the input
 *    array are stored in the first column, and the values
 *    are stored in the third.  A second colum is created
 *    specifically to hold the ':' separator.
 */
function drush_key_value_to_array_table($keyvalue_table, $metadata = array()) {
  if (!is_array($keyvalue_table)) {
    return drush_set_error('DRUSH_INVALID_FORMAT', dt("Data not compatible with selected key-value output format."));
  }
  if (!is_array($metadata)) {
    $metadata = array('key-value-item' => $metadata);
  }
  $item_key = array_key_exists('key-value-item', $metadata) ? $metadata['key-value-item'] : NULL;
  $metadata += array(
    'format' => 'list',
    'separator' => ' ',
  );
  $table = array();
  foreach ($keyvalue_table as $key => $value) {
    if (isset($value)) {
      if (is_array($value) && isset($item_key)) {
        $value = $value[$item_key];
      }
      // We should only have simple values here, but if
      // we don't, use drush_format() to flatten as a fallback.
      if (is_array($value)) {
        $value = drush_format($value, $metadata, 'list');
      }
    }
    if (isset($metadata['include-field-labels']) && !$metadata['include-field-labels']) {
      $table[] = array(isset($value) ? $value : '');
    }
    elseif (isset($value)) {
      $table[] = array($key, ' :', $value);
    }
    else {
      $table[] = array($key . ':', '', '');
    }
  }
  return $table;
}

/**
 * Select the fields that should be used.
 */
function drush_select_fields($all_field_labels, $fields, $strict = TRUE) {
  $field_labels = array();
  foreach ($fields as $field) {
    if (array_key_exists($field, $all_field_labels)) {
      $field_labels[$field] = $all_field_labels[$field];
    }
    else {
      // Allow the user to select fields via their human-readable names.
      // This is less convenient than the field name (since the human-readable
      // names may contain spaces, and must therefore be quoted), but these are
      // the values that the user sees in the command output. n.b. the help
      // text lists fields by their more convenient machine names.
      $key = array_search(strtolower($field), array_map('strtolower', $all_field_labels));
      if ($key !== FALSE) {
        $field_labels[$key] = $all_field_labels[$key];
      }
      elseif (!$strict) {
        $field_labels[$field] = $field;
      }
    }
  }
  return $field_labels;
}

/**
 * Select the fields from the input array that should be output.
 *
 * @param $input
 *   An associative array of key:value pairs to be output
 * @param $fields
 *   An associative array that maps FROM a field in $input
 *   TO the corresponding field name in $output.
 * @param $mapping
 *   An associative array that maps FROM a field in $fields
 *   TO the actual field in $input to use in the preceeding
 *   translation described above.
 * @return
 *   The input array, re-ordered and re-keyed per $fields
 */
function drush_select_output_fields($input, $fields, $mapping = array(), $default_value = NULL) {
  $result = array();
  if (empty($fields)) {
    $result = $input;
  }
  else {
    foreach ($fields as $key => $label) {
      $value = drush_lookup_field_by_path($input, $key, $mapping, $default_value);
      if (isset($value)) {
        $result[$label] = $value;
      }
    }
  }
  return $result;
}

/**
 * Return a specific item inside an array, as identified
 * by the provided path.
 *
 * @param $input:
 *   An array of items, potentially multiple layers deep.
 * @param $path:
 *   A specifier of array keys, either in an array or separated by
 *   a '/', that list the elements of the array to access.  This
 *   works much like a very simple version of xpath for arrays, with
 *   all items being treated identically (like elements).
 * @param $mapping:
 *   (optional) An array whose keys may correspond to the $path parameter and
 *   whose values are the corresponding paths to be used in $input.
 *
 * Example 1:
 *
 *   $input = array('#name' => 'site.dev', '#id' => '222');
 *   $path = '#name';
 *   result: 'site.dev';
 *
 * Example 2:
 *
 *   $input = array('ca' => array('sf' => array('mission'=>array('1700'=>'woodward'))));
 *   $path = 'ca/sf/mission/1701';
 *   result: 'woodward'
 *
 * Example 3:
 *
 *   $input = array('#name' => 'site.dev', '#id' => '222');
 *   $path = 'name';
 *   $mapping = array('name' => '#name');
 *   result: 'site.dev';
 */
function drush_lookup_field_by_path($input, $path, $mapping = array(), $default_value = NULL) {
  $result = '';
  if (isset($mapping[$path])) {
    $path = $mapping[$path];
  }
  if (!is_array($path)) {
    $parts = explode('/', $path);
  }
  if (!empty($parts)) {
    $result = $input;
    foreach ($parts as $key) {
      if ((is_array($result)) && (isset($result[$key]))) {
        $result = $result[$key];
      }
      else {
        return $default_value;
      }
    }
  }
  return $result;
}

/**
 * Given a table array (an associative array of associative arrays),
 * return an array of all of the values with the specified field name.
 */
function drush_output_get_selected_field($input, $field_name, $default_value = '') {
  $result = array();
  foreach ($input as $key => $data) {
    if (is_array($data) && isset($data[$field_name])) {
      $result[] = $data[$field_name];
    }
    else {
      $result[] = $default_value;
    }
  }
  return $result;
}

/**
 * Hide any fields that are empty
 */
function drush_hide_empty_fields($input, $fields) {
  $has_data = array();
  foreach ($input as $key => $data) {
    foreach ($fields as $field => $label) {
      if (isset($data[$field]) && !empty($data[$field])) {
        $has_data[$field] = TRUE;
      }
    }
  }
  foreach ($fields as $field => $label) {
    if (!isset($has_data[$field])) {
      unset($fields[$field]);
    }
  }
  return $fields;
}

/**
 * Convert an array of data rows, where each row contains an
 * associative array of key : value pairs, into
 * a table suitable for processing by drush_print_table.
 * The provided $header determines the order that the items
 * will appear in the output.  Only data items listed in the
 * header will be placed in the output.
 *
 * @param $rows_of_keyvalue_table
 *    array(
 *      'row1' => array('col1' => 'data', 'col2' => 'data'),
 *      'row2' => array('col1' => 'data', 'col2' => 'data'),
 *    )
 * @param $header
 *    array('col1' => 'Column 1 Label', 'col2' => 'Column 2 Label')
 * @param $metadata
 *    (optional) An array of special options, all optional:
 *    - strip-tags: call the strip_tags function on the data
 *         before placing it in the table
 *    - concatenate-columns: an array of:
 *         - dest-col: array('src-col1', 'src-col2')
 *         Appends all of the src columns with whatever is
 *         in the destination column.  Appended columns are
 *         separated by newlines.
 *    - transform-columns: an array of:
 *         - dest-col: array('from' => 'to'),
 *         Transforms any occurance of 'from' in 'dest-col' to 'to'.
 *    - format-cell: Drush output format name to use to format
 *         any cell that is an array.
 *    - process-cell: php function name to use to process
 *         any cell that is an array.
 *    - field-mappings: an array whose keys are some or all of the keys in
 *         $header and whose values are the corresponding keys to use when
 *         indexing the values of $rows_of_keyvalue_table.
 * @return
 *    An array of arrays
 */
function drush_rows_of_key_value_to_array_table($rows_of_keyvalue_table, $header, $metadata) {
  if (isset($metadata['hide-empty-fields'])) {
    $header = drush_hide_empty_fields($rows_of_keyvalue_table, $header);
  }
  if (empty($header)) {
    $first = (array)reset($rows_of_keyvalue_table);
    $keys = array_keys($first);
    $header = array_combine($keys, $keys);
  }
  $table = array(array_values($header));
  if (isset($rows_of_keyvalue_table) && is_array($rows_of_keyvalue_table) && !empty($rows_of_keyvalue_table)) {
    foreach ($rows_of_keyvalue_table as $row_index => $row_data) {
      $row_data = (array)$row_data;
      $row = array();
      foreach ($header as $column_key => $column_label) {
        $data = drush_lookup_field_by_path($row_data, $column_key, $metadata['field-mappings']);
        if (array_key_exists('transform-columns', $metadata)) {
          foreach ($metadata['transform-columns'] as $dest_col => $transformations) {
            if ($dest_col == $column_key) {
              $data = str_replace(array_keys($transformations), array_values($transformations), $data);
            }
          }
        }
        if (array_key_exists('concatenate-columns', $metadata)) {
          foreach ($metadata['concatenate-columns'] as $dest_col => $src_cols) {
            if ($dest_col == $column_key) {
              $data = '';
              if (!is_array($src_cols)) {
                $src_cols = array($src_cols);
              }
              foreach($src_cols as $src) {
                if (array_key_exists($src, $row_data) && !empty($row_data[$src])) {
                  if (!empty($data)) {
                    $data .= "\n";
                  }
                  $data .= $row_data[$src];
                }
              }
            }
          }
        }
        if (array_key_exists('format-cell', $metadata) && is_array($data)) {
          $data = drush_format($data, array(), $metadata['format-cell']);
        }
        if (array_key_exists('process-cell', $metadata) && is_array($data)) {
          $data = $metadata['process-cell']($data, $metadata);
        }
        if (array_key_exists('strip-tags', $metadata)) {
          $data = strip_tags($data);
        }
        $row[] = $data;
      }
      $table[] = $row;
    }
  }
  return $table;
}

/**
 * Determine the best fit for column widths.
 *
 * @param $rows
 *   The rows to use for calculations.
 * @param $widths
 *   Manually specified widths of each column (in characters) - these will be
 *   left as is.
 */
function drush_table_column_autowidth($rows, $widths) {
  $auto_widths = $widths;

  // First we determine the distribution of row lengths in each column.
  // This is an array of descending character length keys (i.e. starting at
  // the rightmost character column), with the value indicating the number
  // of rows where that character column is present.
  $col_dist = array();
  foreach ($rows as $rowkey => $row) {
    foreach ($row as $col_id => $cell) {
      if (empty($widths[$col_id])) {
        $length = strlen($cell);
        if ($length == 0) {
          $col_dist[$col_id][0] = 0;
        }
        while ($length > 0) {
          if (!isset($col_dist[$col_id][$length])) {
            $col_dist[$col_id][$length] = 0;
          }
          $col_dist[$col_id][$length]++;
          $length--;
        }
      }
    }
  }
  foreach ($col_dist as $col_id => $count) {
    // Sort the distribution in decending key order.
    krsort($col_dist[$col_id]);
    // Initially we set all columns to their "ideal" longest width
    // - i.e. the width of their longest column.
    $auto_widths[$col_id] = max(array_keys($col_dist[$col_id]));
  }

  // We determine what width we have available to use, and what width the
  // above "ideal" columns take up.
  $available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2);
  $auto_width_current = array_sum($auto_widths);

  // If we need to reduce a column so that we can fit the space we use this
  // loop to figure out which column will cause the "least wrapping",
  // (relative to the other columns) and reduce the width of that column.
  while ($auto_width_current > $available_width) {
    $count = 0;
    $width = 0;
    foreach ($col_dist as $col_id => $counts) {
      // If we are just starting out, select the first column.
      if ($count == 0 ||
         // OR: if this column would cause less wrapping than the currently
         // selected column, then select it.
         (current($counts) < $count) ||
         // OR: if this column would cause the same amount of wrapping, but is
         // longer, then we choose to wrap the longer column (proportionally
         // less wrapping, and helps avoid triple line wraps).
         (current($counts) == $count && key($counts) > $width)) {
        // Select the column number, and record the count and current width
        // for later comparisons.
        $column = $col_id;
        $count = current($counts);
        $width = key($counts);
      }
    }
    if ($width <= 1) {
      // If we have reached a width of 1 then give up, so wordwrap can still progress.
      break;
    }
    // Reduce the width of the selected column.
    $auto_widths[$column]--;
    // Reduce our overall table width counter.
    $auto_width_current--;
    // Remove the corresponding data from the disctribution, so next time
    // around we use the data for the row to the left.
    unset($col_dist[$column][$width]);
  }
  return $auto_widths;
}

/**
 * Print the contents of a file.
 *
 * @param string $file
 *   Full path to a file.
 */
function drush_print_file($file) {
  // Don't even bother to print the file in --no mode
  if (drush_get_context('DRUSH_NEGATIVE')) {
    return;
  }
  if ((substr($file,-4) == ".htm") || (substr($file,-5) == ".html")) {
    $tmp_file = drush_tempnam(basename($file));
    file_put_contents($tmp_file, drush_html_to_text(file_get_contents($file)));
    $file = $tmp_file;
  }
  // Do not wait for user input in --yes or --pipe modes
  if (drush_get_context('DRUSH_PIPE')) {
    drush_print_pipe(file_get_contents($file));
  }
  elseif (drush_get_context('DRUSH_AFFIRMATIVE')) {
    drush_print(file_get_contents($file));
  }
  elseif (drush_shell_exec_interactive("less %s", $file)) {
    return;
  }
  elseif (drush_shell_exec_interactive("more %s", $file)) {
    return;
  }
  else {
    drush_print(file_get_contents($file));
  }
}


/**
 * Converts a PHP variable into its Javascript equivalent.
 *
 * We provide a copy of D7's drupal_json_encode since this function is
 * unavailable on earlier versions of Drupal.
 *
 * @see drupal_json_decode()
 * @ingroup php_wrappers
 */
function drush_json_encode($var) {
  if (version_compare(phpversion(), '5.4.0', '>=')) {
    $json = json_encode($var, JSON_PRETTY_PRINT);
  }
  else {
    $json = json_encode($var);
  }
  // json_encode() does not escape <, > and &, so we do it with str_replace().
  return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), $json);
}

/**
 * Converts an HTML-safe JSON string into its PHP equivalent.
 *
 * We provide a copy of D7's drupal_json_decode since this function is
 * unavailable on earlier versions of Drupal.
 *
 * @see drupal_json_encode()
 * @ingroup php_wrappers
 */
function drush_json_decode($var) {
  return json_decode($var, TRUE);
}

/**
 * Drupal-friendly var_export().  Taken from utility.inc in Drupal 8.
 *
 * @param $var
 *   The variable to export.
 * @param $prefix
 *   A prefix that will be added at the beginning of every lines of the output.
 *
 * @return
 *   The variable exported in a way compatible to Drupal's coding standards.
 */
function drush_var_export($var, $prefix = '') {
  if (is_array($var)) {
    if (empty($var)) {
      $output = 'array()';
    }
    else {
      $output = "array(\n";
      // Don't export keys if the array is non associative.
      $export_keys = array_values($var) != $var;
      foreach ($var as $key => $value) {
        $output .= '  ' . ($export_keys ? drush_var_export($key) . ' => ' : '') . drush_var_export($value, '  ', FALSE) . ",\n";
      }
      $output .= ')';
    }
  }
  elseif (is_bool($var)) {
    $output = $var ? 'TRUE' : 'FALSE';
  }
  elseif (is_string($var)) {
    $line_safe_var = str_replace("\n", '\n', $var);
    if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) {
      // If the string contains a line break or a single quote, use the
      // double quote export mode. Encode backslash and double quotes and
      // transform some common control characters.
      $var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var);
      $output = '"' . $var . '"';
    }
    else {
      $output = "'" . $var . "'";
    }
  }
  elseif (is_object($var) && get_class($var) === 'stdClass') {
    // var_export() will export stdClass objects using an undefined
    // magic method __set_state() leaving the export broken. This
    // workaround avoids this by casting the object as an array for
    // export and casting it back to an object when evaluated.
    $output = '(object) ' . drush_var_export((array) $var, $prefix);
  }
  else {
    $output = var_export($var, TRUE);
  }

  if ($prefix) {
    $output = str_replace("\n", "\n$prefix", $output);
  }

  return $output;
}

/**
 * @} End of "defgroup outputfunctions".
 */
<?php

/**
 * @file
 * Preflight, postflight and shutdown code.
 */

use Drush\Log\LogLevel;

/**
 * The main Drush function.
 *
 * - Runs "early" option code, if set (see global options).
 * - Parses the command line arguments, configuration files and environment.
 * - Prepares and executes a Drupal bootstrap, if possible,
 * - Dispatches the given command.
 *
 * function_exists('drush_main') may be used by modules to detect whether
 * they are being called from Drush.  See http://drupal.org/node/1181308
 * and http://drupal.org/node/827478
 *
 * @return mixed
 *   Whatever the given command returns.
 */
function drush_main() {
  // Load Drush core include files, and parse command line arguments.
  if (drush_preflight_prepare() === FALSE) {
    return(1);
  }
  // Start code coverage collection.
  if ($coverage_file = drush_get_option('drush-coverage', FALSE)) {
    drush_set_context('DRUSH_CODE_COVERAGE', $coverage_file);
    xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
    register_shutdown_function('drush_coverage_shutdown');
  }

  // Load the global Drush configuration files, and global Drush commands.
  // Find the selected site based on --root, --uri or cwd
  // Preflight the selected site, and load any configuration and commandfiles associated with it.
  // Select and return the bootstrap class.
  $bootstrap = drush_preflight();

  // Reset our bootstrap phase to the beginning
  drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE);

  $return = '';
  if (!drush_get_error()) {
    if ($file = drush_get_option('early', FALSE)) {
      require_once drush_is_absolute_path($file) ? $file : DRUSH_BASE_PATH . DIRECTORY_SEPARATOR . $file;
      $function = 'drush_early_' . basename($file, '.inc');
      if (function_exists($function)) {
        if ($return = $function()) {
          // If the function returns FALSE, we continue and attempt to bootstrap
          // as normal. Otherwise, we exit early with the returned output.
          if ($return === TRUE) {
            $return = '';
          }
        }
      }
    }
    else {
      // Do any necessary preprocessing operations on the command,
      // perhaps handling immediately.
      $command_handled = drush_preflight_command_dispatch();
      if (!$command_handled) {
        $return = $bootstrap->bootstrap_and_dispatch();
      }
    }
  }
  // TODO: Get rid of global variable access here, and just trust
  // the bootstrap object returned from drush_preflight().  This will
  // require some adjustments to Drush bootstrapping.
  // See: https://github.com/drush-ops/drush/pull/1303
  if ($bootstrap = drush_get_bootstrap_object()) {
    $bootstrap->terminate();
  }
  drush_postflight();
  if (is_object($return)) {
    $return = 0;
  }

  // How strict are we?  If we are very strict, turn 'ok' into 'error'
  // if there are any warnings in the log.
  if (($return == 0) && (drush_get_option('strict') > 1) && drush_log_has_errors()) {
    $return = 1;
  }

  // After this point the drush_shutdown function will run,
  // exiting with the correct exit code.
  return $return;
}

/**
 * Prepare Drush for preflight.
 *
 * Runs before drush_main().
 *
 * @see drush_main()
 * @see drush.php
 */
function drush_preflight_prepare() {
  define('DRUSH_BASE_PATH', dirname(dirname(__FILE__)));
  // Local means that autoload.php is inside of Drush. That is, Drush is its own Composer project.
  // Global means autoload.php is outside of Drush. That is, Drush is a dependency of a bigger project.
  $local_vendor_path = DRUSH_BASE_PATH . '/vendor/autoload.php';
  $global_vendor_path = DRUSH_BASE_PATH . '/../../../vendor/autoload.php';

  // Check for a local composer install or a global composer install. Vendor dirs are in different spots).
  if (file_exists($local_vendor_path)) {
    $vendor_path = $local_vendor_path;
  }
  elseif (file_exists($global_vendor_path)) {
    $vendor_path = $global_vendor_path;
  }
  else {
    $msg = "Unable to load autoload.php. Run composer install to fetch dependencies and write this file (http://docs.drush.org/en/master/install-alternative/). Or if you prefer, use the drush.phar which already has dependencies included (http://docs.drush.org/en/master/install).\n";
    fwrite(STDERR, $msg);
    return FALSE;
  }

  $classloader = require $vendor_path;

  require_once DRUSH_BASE_PATH . '/includes/startup.inc';
  require_once DRUSH_BASE_PATH . '/includes/bootstrap.inc';
  require_once DRUSH_BASE_PATH . '/includes/environment.inc';
  require_once DRUSH_BASE_PATH . '/includes/annotationcommand_adapter.inc';
  require_once DRUSH_BASE_PATH . '/includes/command.inc';
  require_once DRUSH_BASE_PATH . '/includes/drush.inc';
  require_once DRUSH_BASE_PATH . '/includes/engines.inc';
  require_once DRUSH_BASE_PATH . '/includes/backend.inc';
  require_once DRUSH_BASE_PATH . '/includes/batch.inc';
  require_once DRUSH_BASE_PATH . '/includes/context.inc';
  require_once DRUSH_BASE_PATH . '/includes/sitealias.inc';
  require_once DRUSH_BASE_PATH . '/includes/exec.inc';
  require_once DRUSH_BASE_PATH . '/includes/drupal.inc';
  require_once DRUSH_BASE_PATH . '/includes/output.inc';
  require_once DRUSH_BASE_PATH . '/includes/cache.inc';
  require_once DRUSH_BASE_PATH . '/includes/filesystem.inc';
  require_once DRUSH_BASE_PATH . '/includes/dbtng.inc';
  require_once DRUSH_BASE_PATH . '/includes/array_column.inc';

  // Stash our vendor path and classloader.
  drush_set_context('DRUSH_VENDOR_PATH', dirname($vendor_path));
  drush_set_context('DRUSH_CLASSLOADER', $classloader);

  // Can't log until we have a logger, so we'll create this ASAP.
  _drush_create_default_logger();

  // Terminate immediately unless invoked as a command line script
  if (!drush_verify_cli()) {
    return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Drush is designed to run via the command line.'));
  }

  // Check supported version of PHP.
  // Note: If this is adjusted, check other code that compares
  // PHP_VERSION, such as drush_json_encode(), runserver/runserver.drush.inc, and also
  // adjust _drush_environment_check_php_ini() and the php_prohibited_options
  // list in the drush script.  See http://drupal.org/node/1748228
  define('DRUSH_MINIMUM_PHP', '5.4.5');
  if (version_compare(phpversion(), DRUSH_MINIMUM_PHP) < 0 && !getenv('DRUSH_NO_MIN_PHP')) {
    return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Your command line PHP installation is too old. Drush requires at least PHP !version. To suppress this check, set the environment variable DRUSH_NO_MIN_PHP=1', array('!version' => DRUSH_MINIMUM_PHP)));
  }

  if (!$return = _drush_environment_check_php_ini()) {
    return; // An error was logged.
  }

  $drush_info = drush_read_drush_info();
  define('DRUSH_VERSION', $drush_info['drush_version']);
  $version_parts = explode('.', DRUSH_VERSION);
  define('DRUSH_MAJOR_VERSION', $version_parts[0]);
  define('DRUSH_MINOR_VERSION', $version_parts[1]);

  define('DRUSH_REQUEST_TIME', microtime(TRUE));

  drush_set_context('argc', $GLOBALS['argc']);
  drush_set_context('argv', $GLOBALS['argv']);

  // Set an error handler and a shutdown function
  set_error_handler('drush_error_handler');
  register_shutdown_function('drush_shutdown');
  // We need some global options/arguments processed at this early stage.
  drush_parse_args();

  // Process initial global options such as --debug.
  _drush_preflight_global_options();

  drush_log(dt("Drush preflight prepare loaded autoloader at !autoloader", array('!autoloader' => realpath($vendor_path))), LogLevel::PREFLIGHT);
}

/**
 * During the initialization of Drush, this is the first
 * step where we load our configuration and commandfiles,
 * and select the site we are going to operate on; however,
 * we take no irreversible actions (e.g. site bootstrapping).
 * This allows commands that are declared with no bootstrap
 * to select a new site root and bootstrap it.
 *
 * In this step we will register the shutdown function,
 * parse the command line arguments and store them in their
 * related contexts.
 *
 * Configuration files (drushrc.php) that are
 *   a) Specified on the command line
 *   b) Stored in the root directory of drush.php
 *   c) Stored in the home directory of the system user.
 *
 * Additionally the DRUSH_QUIET and DRUSH_BACKEND contexts,
 * will be evaluated now, as they need to be set very early in
 * the execution flow to be able to take affect.
 *
 * @return \Drush\Boot\Boot;
 */
function drush_preflight() {
  // Create an alias '@none' to represent no Drupal site
  _drush_sitealias_cache_alias('@none', array('root' => '', 'uri' => ''));

  // Discover terminal width for pretty output.
  _drush_preflight_columns();

  // Display is tidy now that column width has been handled.
  drush_log(dt('Starting Drush preflight.'), LogLevel::PREFLIGHT);

  // Statically define a way to call drush again.
  define('DRUSH_COMMAND', drush_find_drush());

  // prime the CWD cache
  drush_cwd();

  // Set up base environment for system-wide file locations.
  _drush_preflight_base_environment();

  // Setup global alias_paths[] in context system.
  if (!drush_get_option('local')) {
    _drush_preflight_alias_path();
  }
  if (!drush_get_option('local')) {
    // Load a drushrc.php file in the drush.php's directory.
    drush_load_config('drush');

    // Load a drushrc.php file in the $ETC_PREFIX/etc/drush directory.
    drush_load_config('system');

    // Load a drushrc.php file at ~/.drushrc.php.
    drush_load_config('user');

    // Load a drushrc.php file in the ~/.drush directory.
    drush_load_config('home.drush');
  }

  // Load a custom config specified with the --config option.
  drush_load_config('custom');

  _drush_preflight_global_options();
  // Load all the commandfiles findable from any of the
  // scopes listed above.
  _drush_find_commandfiles_drush();

  // Look up the alias identifier that the user wants to use,
  // either via an argument or via 'site-set'.
  $target_alias = drush_sitealias_check_arg_and_site_set();

  // Process the site alias that specifies which instance
  // of Drush (local or remote) this command will operate on.
  // We must do this after we load our config files (so that
  // site aliases are available), but before the rest of
  // Drush preflight and Drupal root bootstrap phase are
  // done, since site aliases may set option values that
  // affect these phases.
  $alias_record = _drush_sitealias_set_context_by_name($target_alias);

  // Find the selected site based on --root, --uri or cwd
  drush_preflight_root();

  // Preflight the selected site, and load any configuration and commandfiles associated with it.
  drush_preflight_site();

  // Check to see if anything changed during the 'site' preflight
  // that might allow us to find our alias record now
  if (empty($alias_record)) {
    $alias_record = _drush_sitealias_set_context_by_name($target_alias);

    // If the site alias settings changed late in the preflight,
    // then run the preflight for the root and site contexts again.
    if (!empty($alias_record)) {
      $remote_host = drush_get_option('remote-host');
      if (!isset($remote_host)) {
        drush_preflight_root();
        drush_preflight_site();
      }
    }
  }

  // Fail if we could not find the selected site alias.
  if ($target_alias && empty($alias_record)) {
    // We will automatically un-set the site-set alias if it could not be found.
    // Otherwise, we'd be stuck -- the user would only be able to execute Drush
    // commands again after `drush @none site-set @none`, and most folks would
    // have a hard time figuring that out.
    $site_env = drush_sitealias_site_get();
    if ($site_env == $target_alias) {
      drush_sitealias_site_clear();
    }
    return drush_set_error('DRUSH_BOOTSTRAP_NO_ALIAS', dt("Could not find the alias !alias", array('!alias' => $target_alias)));
  }

  // If applicable swaps in shell alias values.
  drush_shell_alias_replace($target_alias);

  // Copy global options to their respective contexts
  _drush_preflight_global_options();

  // Set environment variables based on #env-vars.
  drush_set_environment_vars($alias_record);

  // Select the bootstrap object and return it.
  return drush_select_bootstrap_class();
}

/**
 * If --root is provided, set context.
 */
function drush_preflight_root() {
  $root = drush_get_option('root');
  if (!isset($root)) {
    $root = drush_locate_root();
  }
  if ($root) {
    $root = realpath($root);
  }
  // @todo This context name should not mention Drupal.
  // @todo Drupal code should use DRUSH_DRUPAL_ROOT instead of this constant.
  drush_set_context('DRUSH_SELECTED_DRUPAL_ROOT', $root);

  // Load the config options from Drupal's /drush, ../drush, and sites/all/drush directories,
  // even prior to bootstrapping the root.
  drush_load_config('drupal');

  // Search for commandfiles in the root locations
  $discovery = annotationcommand_adapter_get_discovery();
  $searchpath = [dirname($root) . '/drush', "$root/drush", "$root/sites/all/drush"];

  $drush_root_extensions = $discovery->discover($searchpath, '\Drush');
  drush_set_context(
    'DRUSH_ANNOTATED_COMMANDFILES',
    array_merge(
      drush_get_context('DRUSH_ANNOTATED_COMMANDFILES'),
      $drush_root_extensions
    )
  );
}

function drush_preflight_site() {
  // Load the Drupal site configuration options upfront.
  drush_load_config('site');

  // Determine URI and set constants/contexts accordingly. Keep this after loading of drupal,site configs.
  _drush_preflight_uri();

  // If someone set 'uri' in the 'site' context, then copy it
  // to the 'process' context (to give it a higher priority
  // than the 'cli' and 'alias' contexts) and reset our selected
  // site and @self alias.
  $uri = drush_get_option('uri');
  if ($uri != drush_get_option('uri', $uri, 'site')) {
    drush_set_option('uri', drush_get_option('uri', $uri, 'site'));
    _drush_preflight_uri();
  }

  // Create a @self site alias record.
  drush_sitealias_create_self_alias();
}

function _drush_preflight_global_options() {
  // Debug implies verbose
  $verbose = drush_get_option('verbose', FALSE);
  $debug = drush_get_option('debug', FALSE);
  drush_set_context('DRUSH_VERBOSE',      $verbose || $debug);
  drush_set_context('DRUSH_DEBUG',        $debug);
  drush_set_context('DRUSH_DEBUG_NOTIFY', $verbose && $debug);
  drush_set_context('DRUSH_SIMULATE',     drush_get_option('simulate', FALSE));

  // Backend implies affirmative unless negative is explicitly specified
  drush_set_context('DRUSH_NEGATIVE',    drush_get_option('no', FALSE));
  drush_set_context('DRUSH_AFFIRMATIVE', drush_get_option(array('yes', 'pipe'), FALSE) || (drush_get_context('DRUSH_BACKEND') && !drush_get_context('DRUSH_NEGATIVE')));

  // Pipe implies quiet.
  drush_set_context('DRUSH_QUIET', drush_get_option(array('quiet', 'pipe')));
  drush_set_context('DRUSH_PIPE', drush_get_option('pipe'));

  // Suppress colored logging if --nocolor option is explicitly given or if
  // terminal does not support it.
  $nocolor = (drush_get_option('nocolor', FALSE));
  if (!$nocolor) {
    // Check for colorless terminal.  If there is no terminal, then
    // 'tput colors 2>&1' will return "tput: No value for $TERM and no -T specified",
    // which is not numeric and therefore will put us in no-color mode.
    $colors = exec('tput colors 2>&1');
    $nocolor = !($colors === FALSE || (is_numeric($colors) && $colors >= 3));
  }
  drush_set_context('DRUSH_NOCOLOR', $nocolor);
}

/**
 * Sets up basic environment that controls where Drush looks for files on a
 * system-wide basis. Important to call for "early" functions that need to
 * work with unit tests.
 */
function _drush_preflight_base_environment() {
  // Copy ETC_PREFIX and SHARE_PREFIX from environment variables if available.
  // This alters where we check for server-wide config and alias files.
  // Used by unit test suite to provide a clean environment.
  if (getenv('ETC_PREFIX')) drush_set_context('ETC_PREFIX', getenv('ETC_PREFIX'));
  if (getenv('SHARE_PREFIX')) drush_set_context('SHARE_PREFIX', getenv('SHARE_PREFIX'));

  drush_set_context('DOC_PREFIX', DRUSH_BASE_PATH);
  if (!file_exists(DRUSH_BASE_PATH . '/README.md') && file_exists(drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush' . '/README.md')) {
    drush_set_context('DOC_PREFIX', drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush');
  }

  $default_prefix_configuration = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : '';
  $default_prefix_commandfile = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : '/usr';
  $site_wide_configuration_dir = drush_get_context('ETC_PREFIX', $default_prefix_configuration) . '/etc/drush';
  $site_wide_commandfile_dir = drush_get_context('SHARE_PREFIX', $default_prefix_commandfile) . '/share/drush/commands';
  drush_set_context('DRUSH_SITE_WIDE_CONFIGURATION', $site_wide_configuration_dir);
  drush_set_context('DRUSH_SITE_WIDE_COMMANDFILES', $site_wide_commandfile_dir);

  $server_home = drush_server_home();
  if (isset($server_home)) {
    drush_set_context('DRUSH_PER_USER_CONFIGURATION', $server_home . '/.drush');
  }
}

/*
 * Set the terminal width, used for wrapping table output.
 * Normally this is exported using tput in the drush script.
 * If this is not present we do an additional check using stty here.
 * On Windows in CMD and PowerShell is this exported using mode con.
 */
function _drush_preflight_columns() {
  if (!($columns = getenv('COLUMNS'))) {
    // Trying to export the columns using stty.
    exec('stty size 2>&1', $columns_output, $columns_status);
    if (!$columns_status) $columns = preg_replace('/\d+\s(\d+)/', '$1', $columns_output[0], -1, $columns_count);

    // If stty fails and Drush us running on Windows are we trying with mode con.
    if (($columns_status || !$columns_count) && drush_is_windows()) {
      $columns_output = array();
      exec('mode con', $columns_output, $columns_status);
      if (!$columns_status && is_array($columns_output)) {
        $columns = (int)preg_replace('/\D/', '', $columns_output[4], -1, $columns_count);
      }
      else {
        drush_log(dt('Drush could not detect the console window width. Set a Windows Environment Variable of COLUMNS to the desired width.'), LogLevel::WARNING);
      }
    }

    // Failling back to default columns value
    if (empty($columns)) {
      $columns = 80;
    }
  }
  // If a caller wants to reserve some room to add additional
  // information to the drush output via post-processing, the
  // --reserve-margin flag can be used to declare how much
  // space to leave out.  This only affects drush functions
  // such as drush_print_table() that wrap the output.
  $columns -= drush_get_option('reserve-margin', 0);
  drush_set_context('DRUSH_COLUMNS', $columns);
}

function _drush_preflight_alias_path() {
  $alias_path =& drush_get_context('ALIAS_PATH');
  $default_prefix_configuration = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : '';
  $site_wide_configuration_dir = drush_get_context('ETC_PREFIX', $default_prefix_configuration) . '/etc/drush';
  $alias_path[] = drush_sitealias_alias_base_directory($site_wide_configuration_dir);

  $alias_path[] = drush_sitealias_alias_base_directory(dirname(__FILE__) . '/..');

  $server_home = drush_server_home();
  if (isset($server_home)) {
    $alias_path[] = drush_sitealias_alias_base_directory($server_home . '/.drush');
  }
}

/*
 * Set root and uri.
 */
function _drush_preflight_root_uri() {
  drush_preflight_root();
  _drush_preflight_uri();
}

/**
 * If --uri is provided, set context.
 */
function _drush_preflight_uri() {
  $uri = drush_get_option('uri', '');
  drush_set_context('DRUSH_SELECTED_URI', $uri);
}

function _drush_find_commandfiles_drush() {
  // Core commands shipping with Drush
  $searchpath[] = dirname(__FILE__) . '/../commands/';

  // User commands, specified by 'include' option
  $include = drush_get_context('DRUSH_INCLUDE', array());
  foreach ($include as $path) {
    if (is_dir($path)) {
      drush_log('Include ' . $path, LogLevel::NOTICE);
      $searchpath[] = $path;
    }
  }

  if (!drush_get_option('local')) {
    // System commands, residing in $SHARE_PREFIX/share/drush/commands
    $share_path = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES');
    if (is_dir($share_path)) {
      $searchpath[] = $share_path;
    }

    // User commands, residing in ~/.drush
    $per_user_config_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION');
    if (!empty($per_user_config_dir)) {
      $searchpath[] = $per_user_config_dir;
    }
  }

  // @todo the zero parameter is a bit weird here. It's $phase.
  _drush_add_commandfiles($searchpath, 0);

  // Also discover Drush's own annotation commands.
  $discovery = annotationcommand_adapter_get_discovery();
  $annotation_commandfiles = $discovery->discover(DRUSH_BASE_PATH . '/lib/Drush', '\Drush');

  // And, finally, search for commandfiles in the $searchpath
  $searchpath = array_map(
    function ($item) {
      if (strtolower(basename($item)) == 'commands') {
        return dirname($item);
      }
      return $item;
    },
    $searchpath
  );
  $global_drush_extensions = $discovery->discover($searchpath, '\Drush');
  $annotation_commandfiles += $global_drush_extensions;

  drush_set_context('DRUSH_ANNOTATED_COMMANDFILES', $annotation_commandfiles);
}

/**
 * Handle any command preprocessing that may need to be done, including
 * potentially redispatching the command immediately (e.g. for remote
 * commands).
 *
 * @return
 *   TRUE if the command was handled remotely.
 */
function drush_preflight_command_dispatch() {
  $interactive = drush_get_option('interactive', FALSE);

  // The command will be executed remotely if the --remote-host flag
  // is set; note that if a site alias is provided on the command line,
  // and the site alias references a remote server, then the --remote-host
  // option will be set when the site alias is processed.
  // @see drush_sitealias_check_arg_and_site_set and _drush_sitealias_set_context_by_name
  $remote_host = drush_get_option('remote-host');
  $site_list = drush_get_option('site-list');
  // Get the command early so that we can allow commands to directly handle remote aliases if they wish
  $command = drush_parse_command();
  drush_command_default_options($command);

  // If the command sets the 'strict-option-handling' flag, then we will remove
  // any cli options that appear after the command name from the 'cli' context.
  // The cli options that appear before the command name are stored in the
  // 'DRUSH_GLOBAL_CLI_OPTIONS' context, so we will just overwrite the cli context
  // with this, after doing the neccessary fixup from short-form to long-form options.
  // After we do that, we put back any local drush options identified by $command['options'].
  if (is_array($command) && !empty($command['strict-option-handling'])) {
    $cli_options = drush_get_context('DRUSH_GLOBAL_CLI_OPTIONS', array());
    // Now we are going to sort out any options that exist in $command['options'];
    // we will remove these from DRUSH_COMMAND_ARGS and put them back into the
    // cli options.
    $cli_context = drush_get_context('cli');
    $remove_from_command_args = array();
    foreach ($command['options'] as $option => $info) {
      if (array_key_exists($option, $cli_context)) {
        $cli_options[$option] = $cli_context[$option];
        $remove_from_command_args[$option] = $option;
      }
    }
    if (!empty($remove_from_command_args)) {
      $drush_command_args = array();
      foreach (drush_get_context('DRUSH_COMMAND_ARGS') as $arg) {
        if (!_drush_should_remove_command_arg($arg, $remove_from_command_args)) {
          $drush_command_args[] = $arg;
        }
      }
      drush_set_context('DRUSH_COMMAND_ARGS', $drush_command_args);
    }
    drush_expand_short_form_options($cli_options);
    drush_set_context('cli', $cli_options);
    _drush_preflight_global_options();
  }
  $args = drush_get_arguments();
  $command_name = array_shift($args);
  $root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  $local_drush = drush_get_option('drush-script');
  if (empty($local_drush) && !empty($root)) {
    $local_drush = find_wrapper_or_launcher($root);
  }
  $is_local = drush_get_option('local');
  $values = NULL;
  if (!empty($root) && !empty($local_drush) && empty($is_local)) {
    if (!drush_is_absolute_path($local_drush)) {
      $local_drush = $root . DIRECTORY_SEPARATOR . $local_drush;
    }
    $local_drush = realpath($local_drush);
    $this_drush = drush_find_drush();
    // If there is a local Drush selected, and it is not the
    // same Drush that is currently running, redispatch to it.
    // We assume that if the current Drush is nested inside
    // the current Drupal root (or, more specifically, the
    // current Drupal root's parent), then it is a site-local Drush.
    // We avoid redispatching in that instance to prevent an
    // infinite loop.
    if (file_exists($local_drush) && !drush_is_nested_directory(dirname($root), $this_drush)) {
      $uri = drush_get_context('DRUSH_SELECTED_URI');
      $aditional_options = array(
        'root' => $root,
      );
      if (!empty($uri)) {
        $aditional_options['uri'] = $uri;
      }
      // We need to chdir to the Drupal root here, for the
      // benefit of the Drush wrapper.
      chdir($root);
      $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, NULL, NULL, $local_drush, TRUE, $aditional_options);
    }
  }
  // If the command sets the 'handle-remote-commands' flag, then we will short-circuit
  // remote command dispatching and site-list command dispatching, and always let
  // the command handler run on the local machine.
  if (is_array($command) && !empty($command['handle-remote-commands'])) {
    return FALSE;
  }
  if (isset($remote_host)) {
    $remote_user = drush_get_option('remote-user');

    // Force interactive mode if there is a single remote target.  #interactive is added by drush_do_command_redispatch
    $user_interactive = drush_get_option('interactive');
    drush_set_option('interactive', TRUE);
    $values = drush_do_command_redispatch(is_array($command) ? $command : $command_name, $args, $remote_host, $remote_user, $user_interactive);
  }
  // If the --site-list flag is set, then we will execute the specified
  // command once for every site listed in the site list.
  if (isset($site_list)) {
    if (!is_array($site_list)) {
      $site_list = explode(',', $site_list);
    }
    $site_record = array('site-list' => $site_list);
    $args = drush_get_arguments();

    if (!drush_get_context('DRUSH_SIMULATE') && !$interactive  && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_QUIET')) {
      drush_print(dt("You are about to execute '!command' non-interactively (--yes forced) on all of the following targets:", array('!command' => implode(" ", $args))));
      foreach ($site_list as $one_destination) {
        drush_print(dt('  !target', array('!target' => $one_destination)));
      }

      if (drush_confirm('Continue? ') === FALSE) {
        drush_user_abort();
        return TRUE;
      }
    }
    $command_name = array_shift($args);
    $multi_options = drush_redispatch_get_options();
    $backend_options = array();
    if (drush_get_option('pipe') || drush_get_option('interactive')) {
      $backend_options['interactive'] = TRUE;
    }
    if (drush_get_option('no-label', FALSE)) {
      $backend_options['no-label'] = TRUE;
    }
    // If the user specified a format, try to look up the
    // default list separator for the specified format.
    // If the user did not specify a different label separator,
    // then pass in the default as an option, so that the
    // separator between the items in the list and the site
    // name will be consistent.
    $format = drush_get_option('format', FALSE);
    if ($format && !array_key_exists('label-separator', $multi_options)) {
      $formatter = drush_load_engine('outputformat', $format);
      if ($formatter) {
        $list_separator = $formatter->get_info('list-separator');
        if ($list_separator) {
          $multi_options['label-separator'] = $list_separator;
        }
      }
    }
    $values = drush_invoke_process($site_record, $command_name, $args, $multi_options, $backend_options);
  }
  if (isset($values)) {
    if (is_array($values) && ($values['error_status'] > 0)) {
      // Force an error result code.  Note that drush_shutdown() will still run.
      drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);
      exit($values['error_status']);
    }
    return TRUE;
  }
  return FALSE;
}

/**
 * Look for instances of arguments or parameters that
 * start with "~/".  Replace these with "$HOME/".
 *
 * Note that this function is called _after_ Drush does
 * its redispatch checks; tildes are passed through
 * unmodified on a redispatch, and are only expanded when
 * a command is handled locally.
 */
function drush_preflight_tilde_expansion(&$command) {
  // Skip tilde expansion for commands that use
  // stict option handling, or those that explicitly
  // turn it off via $command['tilde-expansion'] = FALSE.
  if ($command['tilde-expansion'] && !$command['strict-option-handling']) {
    $cli =& drush_get_context('cli');
    $match = '#^~/#';
    $replacement = drush_server_home() . '/';
    foreach ($cli as $key => $value) {
      if (is_string($value) && preg_match($match, $value)) {
        $cli[$key] = preg_replace($match, $replacement, $value);
      }
    }
    $command['arguments'] = array_map(function($value) use($match, $replacement) { return is_string($value) ? preg_replace($match, $replacement, $value) : $value; } , $command['arguments']);
  }
}

/**
 * We set this context to let the shutdown function know we reached the end of drush_main().
 *
 * @see drush_main()
 */
function drush_postflight() {
  drush_set_context("DRUSH_EXECUTION_COMPLETED", TRUE);
}

/**
 * Shutdown function for use while Drush and Drupal are bootstrapping and to return any
 * registered errors.
 *
 * The shutdown command checks whether certain options are set to reliably
 * detect and log some common Drupal initialization errors.
 *
 * If the command is being executed with the --backend option, the script
 * will return a json string containing the options and log information
 * used by the script.
 *
 * The command will exit with '1' if it was successfully executed, and the
 * result of drush_get_error() if it wasn't.
 */
function drush_shutdown() {
  // Mysteriously make $user available during sess_write(). Avoids a NOTICE.
  global $user;

  if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE) && !drush_get_context('DRUSH_USER_ABORT', FALSE)) {
    $php_error_message = '';
    if ($error = error_get_last()) {
      $php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line']));
    }
    // We did not reach the end of the drush_main function,
    // this generally means somewhere in the code a call to exit(),
    // was made. We catch this, so that we can trigger an error in
    // those cases.
    drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message)));
    // Attempt to give the user some advice about how to fix the problem
    _drush_postmortem();
  }

  // @todo Ask the bootstrap object (or maybe dispatch) how far we got.
  $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  if (drush_get_context('DRUSH_BOOTSTRAPPING')) {
    switch ($phase) {
      case DRUSH_BOOTSTRAP_DRUPAL_FULL :
        ob_end_clean();
        _drush_log_drupal_messages();
        drush_set_error('DRUSH_DRUPAL_BOOTSTRAP_ERROR');
        break;
    }
  }

  if (drush_get_context('DRUSH_BACKEND', FALSE)) {
    drush_backend_output();
  }
  elseif (drush_get_context('DRUSH_QUIET', FALSE)) {
    ob_end_clean();
    // If we are in pipe mode, emit the compact representation of the command, if available.
    if (drush_get_context('DRUSH_PIPE')) {
      drush_pipe_output();
    }
  }

  // This way drush_return_status() will always be the last shutdown function (unless other shutdown functions register shutdown functions...)
  // and won't prevent other registered shutdown functions (IE from numerous cron methods) from running by calling exit() before they get a chance.
  register_shutdown_function('drush_return_status');
}

/**
 * Shutdown function to save code coverage data.
 */
function drush_coverage_shutdown() {
  if ($file_name = drush_get_context('DRUSH_CODE_COVERAGE', FALSE)) {
    $data = xdebug_get_code_coverage();
    xdebug_stop_code_coverage();

    // If coverage dump file contains anything, merge in the old data before
    // saving. This happens if the current drush command invoked another drush
    // command.
    if (file_exists($file_name) && $content = file_get_contents($file_name)) {
      $merge_data = unserialize($content);
      if (is_array($merge_data)) {
        foreach ($merge_data as $file => $lines) {
          if (!isset($data[$file])) {
            $data[$file] = $lines;
          }
          else {
            foreach ($lines as $num => $executed) {
              if (!isset($data[$file][$num])) {
                $data[$file][$num] = $executed;
              }
              else {
                $data[$file][$num] = ($executed == 1 ? $executed : $data[$file][$num]);
              }
            }
          }
        }
      }
    }

    file_put_contents($file_name, serialize($data));
  }
}

function drush_return_status() {
  // If a specific exit code was set, then use it.
  $exit_code = drush_get_context('DRUSH_EXIT_CODE');
  if (empty($exit_code)) {
    $exit_code = (drush_get_error()) ? DRUSH_FRAMEWORK_ERROR : DRUSH_SUCCESS;
  }

  exit($exit_code);
}
<?php

/**
 * @file
 * The site alias API.
 *
 * Run commands on remote server(s).
 * @see example.aliases.drushrc.php
 * @see http://drupal.org/node/670460
 */

use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;

/**
 * Check to see if the user specified an alias
 * in an arguement, or via site-set.  If so, return
 * the name of the alias.
 *
 * If the alias came from args, then remove it
 * from args.
 */
function drush_sitealias_check_arg_and_site_set() {
  $args = drush_get_arguments();
  $target_alias = FALSE;

  // Test to see if the first arg is a valid alias identifier.
  // If the first arguement is a well-formed identifier, but we
  // cannot find a record for it, then we will fail with an error.
  if (!empty($args) && drush_sitealias_valid_alias_format($args[0])) {
    // Pop the alias off the arguments list first thing.
    $target_alias = array_shift($args);
    drush_set_arguments($args);
  }
  else {
    // If the user did not specify an alias via an argument,
    // check to see if a site env was set.
    $target_alias = drush_sitealias_site_get();
  }

  // Record the user's desired target alias name
  if ($target_alias) {
    drush_set_context('DRUSH_TARGET_SITE_ALIAS', $target_alias);
  }
  return $target_alias;
}

/**
 * Check to see if the first command-line arg or the
 * -l option is a site alias; if it is, copy its record
 * values to the 'alias' context.
 *
 * @return boolean
 *   TRUE if a site alias was found and processed.
 */
function drush_sitealias_check_arg() {
  $args = drush_get_arguments();

  // Test to see if the first arg is a site specification
  if (!empty($args) && _drush_sitealias_set_context_by_name($args[0])) {
    drush_set_context('DRUSH_TARGET_SITE_ALIAS', $args[0]);
    array_shift($args);
    // We only need to expand the site specification
    // once, then we are done.
    drush_set_arguments($args);
    return TRUE;
  }
  // Return false to indicate that no site alias was specified.
  return FALSE;
}

/*
 * Check to see if user has selected a site via site-set command.
 */
function drush_sitealias_check_site_env() {
  $site = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
  if (empty($site)) {
    $site_env = drush_sitealias_site_get();
    if (!empty($site_env) && (_drush_sitealias_set_context_by_name($site_env))) {
      drush_set_context('DRUSH_TARGET_SITE_ALIAS', $site_env);
      return TRUE;
    }
  }
  // Return false to indicate that no site alias was specified.
  return FALSE;
}

/**
 * Check to see if a '@self' record was created during bootstrap.
 * If not, make one now.
 */
function drush_sitealias_create_self_alias() {
  $self_record = drush_sitealias_get_record('@self');
  if (!array_key_exists('root', $self_record) && !array_key_exists('remote-host', $self_record)) {
    $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
    $uri = drush_get_context('DRUSH_SELECTED_URI');
    if (!empty($drupal_root) && !empty($uri)) {
      // Create an alias '@self'
      _drush_sitealias_cache_alias('@self', array('root' => $drupal_root, 'uri' => $uri));
    }
  }
}

/**
 * Given a list of alias records, shorten the name used if possible
 */
function drush_sitealias_simplify_names($site_list) {
  $result = array();
  foreach ($site_list as $original_name => $alias_record) {
    $adjusted_name = $alias_record['#name'];
    $hashpos = strpos($original_name, '#');
    if ($hashpos !== FALSE) {
      $adjusted_name = substr($original_name, $hashpos);
      if (array_key_exists('remote-host', $alias_record)) {
        $adjusted_name = $alias_record['remote-host'] . $adjusted_name;
      }
    }
    $result[$adjusted_name] = $alias_record;
  }
  return $result;
}

/**
 * Given an array of site specifications, resolve each one in turn and
 * return an array of alias records.  If you only want a single record,
 * it is preferable to simply call drush_sitealias_get_record() directly.
 *
 * @param $site_specifications
 *   One of:
 *     A comma-separated list of site specifications: '@site1,@site2'
 *     An array of site specifications: array('@site1','@site2')
 *     An array of alias records:
 *       array(
 *         'site1' => array('root' => ...),
 *         'site2' => array('root' => ...)
 *       )
 *   An array of site specifications.
 *   @see drush_sitealias_get_record() for the format of site specifications.
 * @return
 *   An array of alias records
 */
function drush_sitealias_resolve_sitespecs($site_specifications, $alias_path_context = NULL) {
  $result_list = array();
  $not_found = array();
  if (!is_array($site_specifications)) {
    $site_specifications = explode(',', $site_specifications);
  }
  if (!empty($site_specifications)) {
    foreach ($site_specifications as $site) {
      if (is_array($site)) {
        $result_list[] = $site;
      }
      else {
        $alias_record = drush_sitealias_get_record($site, $alias_path_context);
        if (!$alias_record) {
          $not_found[] = $site;
        }
        else {
          $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record));
        }
      }
    }
  }
  return array($result_list, $not_found);
}

/**
 * Returns TRUE if $alias is a valid format for an alias name.
 *
 * Mirrors the allowed formats shown below for drush_sitealias_get_record.
 */
function drush_sitealias_valid_alias_format($alias) {
  return ( (strpos($alias, ',') !== false) ||
    ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) ||
    ($alias{0} == '#') ||
    ($alias{0} == '@')
  );
}

/**
 * Get a site alias record given an alias name or site specification.
 *
 * If it is the name of a site alias, return the alias record from
 * the site aliases array.
 *
 * If it is the name of a folder in the 'sites' folder, construct
 * an alias record from values stored in settings.php.
 *
 * If it is a site specification, construct an alias record from the
 * values in the specification.
 *
 * Site specifications come in several forms:
 * - /path/to/drupal#sitename
 * - user@server/path/to/drupal#sitename
 * - user@server/path/to/drupal            (sitename == server)
 * - user@server#sitename                  (only if $option['r'] set in some drushrc file on server)
 * - #sitename                             (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
 * - sitename                              (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
 *
 * Note that in the case of the first four forms, it is also possible
 * to add additional site variable to the specification using uri query
 * syntax.  For example:
 *
 *      user@server/path/to/drupal?db-url=...#sitename
 *
 * @param alias
 *   An alias name or site specification
 * @return array
 *   An alias record, or empty if none found.
 */
function drush_sitealias_get_record($alias, $alias_context = NULL) {
  // Check to see if the alias contains commas.  If it does, then
  // we will go ahead and make a site list record
  $alias_record = array();
  if (strpos($alias, ',') !== false) {
    // TODO:  If the site list contains any site lists, or site
    // search paths, then we should expand those and merge them
    // into this list longhand.
    $alias_record['site-list'] = explode(',', $alias);
  }
  else {
    $alias_record = _drush_sitealias_get_record($alias, $alias_context);
  }
  if (!empty($alias_record)) {
    if (array_key_exists('#name', $alias_record)) {
      if ($alias_record['#name'] == 'self') {
        $path = drush_sitealias_local_site_path($alias_record);
        if ($path) {
          $cached_alias_record = drush_sitealias_lookup_alias_by_path($path);
          // Don't overrite keys which have already been negotiated.
          unset($cached_alias_record['#name'], $cached_alias_record['root'], $cached_alias_record['uri']);
          $alias_record = array_merge($alias_record, $cached_alias_record);
        }
      }
    }
    else {
      $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias);
    }
  }
  return $alias_record;
}

/**
 * This is a continuation of drush_sitealias_get_record, above.  It is
 * not intended to be called directly.
 */
function _drush_sitealias_get_record($alias, $alias_context = NULL) {
  $alias_record = array();
  // Before we do anything else, load $alias if it needs to be loaded
  _drush_sitealias_load_alias($alias, $alias_context);

  // Check to see if the provided parameter is in fact a defined alias.
  $all_site_aliases =& drush_get_context('site-aliases');
  if (array_key_exists($alias, $all_site_aliases)) {
    $alias_record = $all_site_aliases[$alias];
  }
  // If the parameter is not an alias, then it is some form of
  // site specification (or it is nothing at all)
  else {
    if (isset($alias)) {
      // Cases 1.) - 4.):
      // We will check for a site specification if the alias has at least
      // two characters from the set '@', '/', '#'.
      if ((strpos($alias, '@') === FALSE ? 0 : 1) + ((strpos($alias, '/') === FALSE && strpos($alias, '\\') === FALSE) ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) {
        if ((substr($alias,0,7) != 'http://') && !drush_is_absolute_path($alias)) {
          // Add on a scheme so that "user:pass@server" will always parse correctly
          $parsed = parse_url('http://' . $alias);
        }
        else if (drush_is_windows() && drush_is_absolute_path($alias)) {
          // On windows if alias begins with a filesystem path we must add file:// scheme to make it parse correcly
          $parsed = parse_url('file:///' . $alias);
        }
        else {
          $parsed = parse_url($alias);
        }
        // Copy various parts of the parsed URL into the appropriate records of the alias record
        foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) {
          if (array_key_exists($url_key, $parsed)) {
            _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
          }
        }
        // If the site specification has a query, also set the query items
        // in the alias record.  This allows passing db_url as part of the
        // site specification, for example.
        if (array_key_exists('query', $parsed)) {
          foreach (explode('&', $parsed['query']) as $query_arg) {
            $query_components = explode('=', $query_arg);
            _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
          }
        }

        // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
        // Note: We presume that 'server' is the best default for case 3; without this code, the default would
        // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
        if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
          $alias_record['uri'] = $parsed['host'];
        }

        // Special checking:  relative aliases embedded in a path
        $relative_alias_pos = strpos($alias_record['root'], '/@');
        if ($relative_alias_pos !== FALSE) {
          // Special checking: /path/@sites
          $base = substr($alias_record['root'], 0, $relative_alias_pos);
          $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1);
          if (drush_valid_root($base) || ($relative_alias == '@sites')) {
            drush_sitealias_create_sites_alias($base);
            $alias_record = drush_sitealias_get_record($relative_alias);
          }
          else {
            $alias_record = array();
          }
        }
      }
      else {
        // Case 5.) and 6.):
        // If the alias is the name of a folder in the 'sites' directory,
        // then use it as a local site specification.
        $alias_record = _drush_sitealias_find_record_for_local_site($alias);
      }
    }
  }

  if (!empty($alias_record)) {
    if (!isset($alias_record['remote']) && !isset($alias_record['#loaded-config'])) {
      if (array_key_exists('root', $alias_record)) {
        drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
        drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
      }
      // TODO: We should probably remove this feature, and put it back
      // in, but in different places (e.g. site selection, sql-sync + rsync
      // parameters, etc.)
      $alias_site_dir = drush_sitealias_local_site_path($alias_record);

      if (isset($alias_site_dir)) {
        // Add the sites folder of this site to the alias search path list
        drush_sitealias_add_to_alias_path($alias_site_dir);
      }
      if (isset($alias_record['config']) && file_exists($alias_record['config'])) {
        drush_load_config_file('site', $alias_record['config']);
        $alias_record['#loaded-config'] = TRUE;
      }
      unset($alias_record['config']);
    }

    // Add the static defaults
    _drush_sitealias_add_static_defaults($alias_record);

    // Cache the result with all of its calculated values
    $all_site_aliases[$alias] = $alias_record;
  }

  return $alias_record;
}

/**
 * Add a path to the array of paths where alias files are searched for.
 *
 * @param $add_path
 *   A path to add to the search path (or NULL to not add any).
 *   Once added, the new path will remain available until drush
 *   exits.
 * @return
 *   An array of paths containing all values added so far
 */
function drush_sitealias_add_to_alias_path($add_path) {
  static $site_paths = array();

  if ($add_path != NULL) {
    if (!is_array($add_path)) {
      $add_path = explode(PATH_SEPARATOR, $add_path);
    }
    // Normalize path to make sure we don't add the same path twice on
    // windows due to different spelling. e.g. c:\tmp and c:/tmp
    foreach($add_path as &$path) {
      $path = drush_normalize_path($path);
    }
    $site_paths = array_unique(array_merge($site_paths, $add_path));
  }
  return $site_paths;
}

/**
 * Return the array of paths where alias files are searched for.
 *
 * @param $alias_path_context
 *   If the alias being looked up is part of a relative alias,
 *   the alias path context specifies the context of the primary
 *   alias the new alias is rooted from.  Alias files stored in
 *   the sites folder of this context, or inside the context itself
 *   takes priority over any other search path that might define
 *   a similarly-named alias.  In this way, multiple sites can define
 *   a '@peer' alias.
 * @return
 *   An array of paths
 */
function drush_sitealias_alias_path($alias_path_context = NULL) {
  $context_path = array();
  if (isset($alias_path_context)) {
    $context_path = array(drush_sitealias_local_site_path($alias_path_context));
  }
  // We get the current list of site paths by adding NULL
  // (nothing) to the path list, which is a no-op
  $site_paths = drush_sitealias_add_to_alias_path(NULL);

  // If the user defined the root of a drupal site, then also
  // look for alias files in /drush and /sites/all/drush.
  $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  if (!empty($drupal_root)) {
    $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/../drush');
    $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/drush');
    $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/sites/all/drush');
    $uri = drush_get_context('DRUSH_SELECTED_URI');
    if (empty($uri)) {
      $uri = 'default';
    }
    $site_dir = drush_sitealias_uri_to_site_dir($uri, $drupal_root);
    if ($site_dir) {
      $site_paths[] = drush_sitealias_alias_base_directory("$drupal_root/sites/$site_dir");
    }
  }
  $alias_path = (array) drush_get_context('ALIAS_PATH', array());
  return array_unique(array_merge($context_path, $alias_path, $site_paths));
}

/**
 * If there is a directory 'site-aliases' in the specified search location,
 * then search ONLY in that directory for aliases.  Otherwise, search
 * anywhere inside the specified directory for aliases.
 */
function drush_sitealias_alias_base_directory($dir) {
  $potential_location = $dir . '/site-aliases';
  if (is_dir($potential_location)) {
    return $potential_location;
  }
  return $dir;
}

/**
 * Return the full path to the site directory of the
 * given alias record.
 *
 * @param $alias_record
 *   The alias record
 * @return
 *   The path to the site directory of the associated
 *   alias record, or NULL if the record is not a local site.
 */
function drush_sitealias_local_site_path($alias_record) {
  $result = NULL;

  if (isset($alias_record['root']) && !isset($alias_record['remote-host'])) {
    if (isset($alias_record['uri'])) {
      $uri = $alias_record['uri'];
      $uri = preg_replace('#^[^:]*://#', '', $uri);
      while (!$result && !empty($uri)) {
        if (file_exists($alias_record['root'] . '/sites/sites.php')) {
          $sites = array();
          include($alias_record['root'] . '/sites/sites.php');
          if (array_key_exists($uri, $sites)) {
            $result = $alias_record['root'] . '/sites/' . $sites[$uri];
          }
        }
        if (!$result) {
          $result = ($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($uri, drush_sitealias_get_root($alias_record)));
        }
        $result = realpath($result);
        $uri = preg_replace('#^[^.]*\.*#', '', $uri);
      }
    }
    if (!$result) {
      $result = realpath($alias_record['root'] . '/sites/default');
    }
  }

  return $result;
}

/**
 * Check and see if an alias definition for $alias is available.
 * If it is, load it into the list of aliases cached in the
 * 'site-aliases' context.
 *
 * @param $alias
 *   The name of the alias to load in ordinary form ('@name')
 * @param $alias_path_context
 *   When looking up a relative alias, the alias path context is
 *   the primary alias that we will start our search from.
 */
function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) {
  $all_site_aliases = drush_get_context('site-aliases');
  $result = array();

  // Only aliases--those named entities that begin with '@'--can be loaded this way.
  // We also skip any alias that has already been loaded.
  if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) {
    $aliasname = substr($alias,1);
    $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context);
    if (!empty($result)) {
      $alias_options = array('site-aliases' => array($aliasname => $result));
      _drush_sitealias_add_inherited_values($alias_options['site-aliases']);
      drush_set_config_special_contexts($alias_options);
      if (array_key_exists('#file', $result)) {
        drush_log(dt('Loaded alias !alias from file !file', array('!alias' => $alias, '!file' => $result['#file'])));
      }
    }
  }

  return $result;
}

/**
 * Load every alias file that can be found anywhere in the
 * alias search path.
 */
function drush_sitealias_load_all($resolve_parent = TRUE) {
  $result = _drush_sitealias_find_and_load_all_aliases();
  if (!empty($result) && ($resolve_parent == TRUE)) {
    // If any aliases were returned, then check for
    // inheritance and then store the aliases into the
    // alias cache
    _drush_sitealias_add_inherited_values($result);
    $alias_options = array('site-aliases' => $result);
    drush_set_config_special_contexts($alias_options);
  }
}

/**
 * Worker function called by _drush_sitealias_load_alias and
 * drush_sitealias_load_all.  Traverses the alias search path
 * and finds the specified alias record.
 *
 * @return
 *   An array of $kay => $value pair of alias names and alias records
 *   loaded.
 */
function _drush_sitealias_find_and_load_all_aliases() {
  $result = array();

  $drush_alias_files = _drush_sitealias_find_alias_files();
  drush_set_context('drush-alias-files', $drush_alias_files);

  // For every file that matches, check inside it for
  // an alias with a matching name.
  foreach ($drush_alias_files as $filename) {
    if (file_exists($filename)) {
      $aliases = $options = array();
      // silently ignore files we can't include
      if ((@include $filename) === FALSE) {
        drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
        continue;
      }
      unset($options['site-aliases']); // maybe unnecessary

      // If $aliases are not set, but $options are, then define one alias named
      // after the first word of the file, before '.alias.drushrc.php.
      if (empty($aliases) && !empty($options)) {
        $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
        $aliases[$this_alias_name] = $options;
        $options = array();
      }
      // If this is a group alias file, then make an
      // implicit alias from the group name that contains
      // a site-list of all of the aliases in the file
      $group_name = '';
      if (substr($filename, -20) == ".aliases.drushrc.php") {
        $group_name = basename($filename,".aliases.drushrc.php");
        if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
          $alias_names = array();
          foreach (array_keys($aliases) as $one_alias) {
            $alias_names[] = "@$group_name.$one_alias";
            $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
            unset($aliases[$one_alias]);
          }
          $aliases[$group_name] = array('site-list' => implode(',', $alias_names));
        }
      }
      if (!empty($aliases)) {
        if (!empty($options)) {
          foreach ($aliases as $name => $value) {
            $aliases[$name] = array_merge($options, $value);
          }
          $options = array();
        }

        foreach ($aliases as $name => $value) {
          _drush_sitealias_initialize_alias_record($aliases[$name]);
          $aliases[$name]['#name'] = $name;
          $aliases[$name]['#file'] = $filename;
        }

        $result = _sitealias_array_merge($result, $aliases);
        // If we found at least one alias from this file
        // then record it in the drush-alias-files context.
        $drush_alias_files = drush_get_context('drush-alias-files');
        if (!in_array($filename, $drush_alias_files)) {
          $drush_alias_files[] = $filename;
        }
        drush_set_context('drush-alias-files', $drush_alias_files);
      }
    }
  }

  return $result;
}

/**
 * Function to find all alias files that might contain aliases
 * that match the requested alias name.
 */
function _drush_sitealias_find_alias_files($aliasname = NULL, $alias_path_context = NULL) {
  $alias_files_to_consider = array();

  // The alias path is a list of folders to search for alias settings files
  $alias_path = drush_sitealias_alias_path($alias_path_context);

  // $alias_files contains a list of filename patterns
  // to search for.  We will find any matching file in
  // any folder in the alias path.  The directory scan
  // is not deep, though; only files immediately in the
  // search path are considered.
  $alias_files = array('/.*aliases\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/');
  if ($aliasname == NULL) {
    $alias_files[] = '/.*\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
  }
  else {
    $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
  }

  // Do not scan into the files directory.
  $blacklist = array_merge(array('files'), drush_filename_blacklist());

  // Search each path in turn.
  foreach ($alias_path as $path) {
    // Find all of the matching files in this location
    foreach ($alias_files as $file_pattern_to_search_for) {
      drush_log(dt('Scanning into @path for @pattern', array('@path' => $path, '@pattern' => $file_pattern_to_search_for)), LogLevel::DEBUG_NOTIFY);
      $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, $blacklist, 0, TRUE)));
    }
  }

  return $alias_files_to_consider;
}

/**
 * Traverses the alias search path and finds the specified alias record.
 *
 * @param $aliasname
 *   The name of the alias without the leading '@' (i.e. '#name')
 *   or NULL to load every alias found in every alias file.
 * @param $alias_path_context
 *   When looking up a relative alias, the alias path context is
 *   the primary alias that we will start our search from.
 * @return
 *   An empty array if nothing was loaded.  If $aliasname is
 *   not null, then the array returned is the alias record for
 *   $aliasname.  If $aliasname is NULL, then the array returned
 *   is a $kay => $value pair of alias names and alias records
 *   loaded.
 */
function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) {
  // Special checking for '@sites' alias
  if ($aliasname == 'sites') {
    $drupal_root = NULL;
    if ($alias_path_context != null) {
      if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) {
        $drupal_root = $alias_path_context['root'];
      }
    }
    else {
      $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
    }
    if (isset($drupal_root) && !is_array($drupal_root)) {
      drush_sitealias_create_sites_alias($drupal_root);
    }
  }

  $alias_files_to_consider = _drush_sitealias_find_alias_files($aliasname, $alias_path_context);

  return _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider);
}

function _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider) {
  $result = array();
  $result_names = array();

  // For every file that matches, check inside it for
  // an alias with a matching name.
  $recorded_files = array();
  foreach ($alias_files_to_consider as $filename) {
    if (file_exists($filename)) {
      $aliases = $options = array();
      // silently ignore files we can't include
      if ((@include $filename) === FALSE) {
        drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
        continue;
      }
      unset($options['site-aliases']); // maybe unnecessary

      // If $aliases are not set, but $options are, then define one alias named
      // after the first word of the file, before '.alias.drushrc.php.
      if (empty($aliases) && !empty($options)) {
        $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
        $aliases[$this_alias_name] = $options;
        $options = array();
      }
      // If this is a group alias file, then make an
      // implicit alias from the group name that contains
      // a site-list of all of the aliases in the file
      $group_prefix = '';
      if (substr($filename, -20) == ".aliases.drushrc.php") {
        $group_name = basename($filename,".aliases.drushrc.php");
        $group_prefix = $group_name . '.';
        if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
          $alias_names = array();
          foreach (array_keys($aliases) as $one_alias) {
            $alias_names[] = "@$group_name.$one_alias";
            $aliases[$one_alias]['#name'] = "$group_name.$one_alias";
            $aliases[$one_alias]['#group'] = $group_name;
            $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
            $aliases[$one_alias]["#hidden"] = TRUE;
          }
          $aliases[$group_name] = array('site-list' => implode(',', $alias_names), '#group' => $group_name, '#name' => $group_name);
        }
      }
      // Store only the named alias into the alias cache
      if ((isset($aliases)) && !empty($aliasname) && array_key_exists($aliasname, $aliases)) {
        drush_set_config_special_contexts($options); // maybe unnecessary
        $one_result = array_merge($options, $aliases[$aliasname]);
        $one_result['#file'] = $filename;
        if (!array_key_exists('#name', $one_result)) {
          $one_result['#name'] = $aliasname;
        }
        _drush_sitealias_initialize_alias_record($one_result);
        // If the alias name is exactly the same as a previous match, then
        // merge the two records together
        if (!empty($result) && ($result['#name'] == $one_result['#name'])) {
          $result = _sitealias_array_merge($result, $one_result);
        }
        // Add the name of the found record to the list of results
        else {
          $result_names[] = "@" . $one_result['#name'];
          $result = $one_result;
        }
      }
    }
  }
  // If there are multiple matches, then return a list of results.
  if (count($result_names) > 1) {
    $result = array('site-list' => $result_names);
  }

  return $result;
}

/**
 * Merges two site aliases.
 *
 * array_merge_recursive is too much; we only want to run
 * array_merge on the common top-level keys of the array.
 *
 * @param array $site_alias_a
 *   A site alias array.
 * @param array $site_alias_b
 *   A site alias array.
 * @return
 *   A site alias array where the keys from $site_alias_a are overwritten by the
 *   keys from $site_alias_b.
 */
function _sitealias_array_merge($site_alias_a, $site_alias_b) {
  $result = $site_alias_a;

  foreach($site_alias_b as $key => $value) {
    if (is_array($value) && array_key_exists($key, $result)) {
      $result[$key] = array_merge($result[$key], $value);
    }
    else {
      $result[$key] = $value;
    }
  }

  return $result;
}

/**
 * Check to see if there is a 'parent' item in the alias; if there is,
 * then load the parent alias record and overlay the entries in the
 * current alias record on top of the items from the parent record.
 *
 * @param $aliases
 *   An array of alias records that are modified in-place.
 */
function _drush_sitealias_add_inherited_values(&$aliases) {
  foreach ($aliases as $alias_name => $alias_value) {
    // Prevent circular references from causing an infinite loop
    _drush_sitealias_cache_alias("@$alias_name", array());
    _drush_sitealias_add_inherited_values_to_record($alias_value);
    $aliases[$alias_name] = $alias_value;
  }
}

function _drush_sitealias_add_inherited_values_to_record(&$alias_value) {
  drush_command_invoke_all_ref('drush_sitealias_alter', $alias_value);
  if (isset($alias_value['parent'])) {
    drush_log(dt("Using deprecated 'parent' element '!parent' in '!name'.", array('!parent' => $alias_value['parent'], '!name' => $alias_value['#name'])), LogLevel::DEBUG);
    // Fetch and merge in each parent
    foreach (explode(',', $alias_value['parent']) as $parent) {
      $parent_record = drush_sitealias_get_record($parent);
      unset($parent_record['#name']);
      unset($parent_record['#file']);
      unset($parent_record['#hidden']);
      $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases'));
      foreach ($array_based_keys as $array_based_key) {
        if (isset($alias_value[$array_based_key]) && isset($parent_record[$array_based_key])) {
          $alias_value[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_value[$array_based_key]);
        }
      }
      $alias_value = array_merge($parent_record, $alias_value);
    }
  }
  unset($alias_value['parent']);
}

/**
 * Add an empty record for the specified alias name
 *
 * @param $alias_name
 *   The name of the alias, including the leading "@"
 */
function _drush_sitealias_cache_alias($alias_name, $alias_record) {
  $cache =& drush_get_context('site-aliases');
  // If the alias already exists in the cache, then merge
  // the new alias with the existing alias
  if (array_key_exists($alias_name, $cache)) {
    $alias_record = array_merge($cache[$alias_name], $alias_record);
  }
  if (!isset($alias_record['#name'])) {
    $alias_record['#name'] = trim($alias_name, '@');
  }
  $cache[$alias_name] = $alias_record;

  // If the alias record points at a local site, make sure
  // that /drush, /sites/all/drush and the site folder for that site
  // are added to the alias path, so that other alias files
  // stored in those locations become searchable.
  if (!array_key_exists('remote-host', $alias_record) && !empty($alias_record['root'])) {
    drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
    drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
    $site_dir = drush_sitealias_local_site_path($alias_record);
    if (isset($site_dir)) {
      drush_sitealias_add_to_alias_path($site_dir);
    }
  }
}

/**
 * If the alias record does not contain a 'databases' or 'db-url'
 * entry, then use backend invoke to look up the settings value
 * from the remote or local site.  The 'db_url' form is preferred;
 * nothing is done if 'db_url' is not available (e.g. on a D7 site)
 *
 * @param $alias_record
 *   The full alias record to populate with database settings
 */
function drush_sitealias_add_db_url(&$alias_record) {
  if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
    drush_sitealias_add_db_settings($alias_record);
  }
  if (!isset($alias_record['db-url']) && isset($alias_record['databases'])) {
    $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']);
  }
}

/**
 * Drush still accepts --db-url format database specifications as
 * cli parameters; it is therefore useful to be able to convert
 * from a database record back to a db-url sometimes.
 */
function drush_sitealias_convert_db_spec_to_db_url($db_spec) {
  $result = urlencode($db_spec["driver"]) . "://";
  if (isset($db_spec["username"])) {
    $result .= urlencode($db_spec["username"]);
    if (isset($db_spec["password"])) {
      $result .= ":" . urlencode($db_spec["password"]);
    }
    $result .= "@";
  }
  // Host is required, unless this is an sqlite db.
  if (isset($db_spec["host"])) {
    $result .= urlencode($db_spec["host"]);
    if (isset($db_spec["port"])) {
      $result .= ":" . urlencode($db_spec["port"]);
    }
    $result .= '/' . urlencode($db_spec["database"]);
  }
  else {
    // URL-encode the database, but convert slashes
    // back to their original form for readability.
    // This portion is the "path" of the URL, so it may
    // contain slashes.  This is important for sqlite.
    $result .= str_replace("%2F", "/", urlencode(ltrim($db_spec["database"], '/')));
  }
  return $result;
}

/**
 * Create a db-url from the databases record.
 */
function drush_sitealias_convert_databases_to_db_url($databases) {
  if ((count($databases) == 1) && isset($databases['default'])) {
    $result = drush_sitealias_convert_db_spec_to_db_url($databases['default']['default']);
  }
  else {
    foreach ($databases as $key => $db_info) {
      $result[$key] = drush_sitealias_convert_db_spec_to_db_url($db_info['default']);
    }
  }
  return $result;
}

/**
 * Return the databases record from the alias record
 *
 * @param $alias_record
 *   A record returned from drush_sitealias_get_record
 * @returns
 *   A databases record (always in D7 format) or NULL
 *   if the databases record could not be found.
 */
function sitealias_get_databases_from_record(&$alias_record) {
  $altered_record = drush_sitealias_add_db_settings($alias_record);

  return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL;
}

/**
 * Return the $db_spec record for the database associated with
 * the provided alias record.  @see drush_sitealias_add_db_settings(),
 * which will be used to first add the database information to the
 * alias records, invoking sql-conf to look them up if necessary.
 *
 * The options 'database' and 'target' are used to specify which
 * specific database should be fetched from the database record;
 * they may appear in the alias definition, or may be taken from the
 * command line options.  The values 'default' and 'default' are
 * used if these options are not specified in either location.
 *
 * Note that in the context of sql-sync, the site alias record will
 * be taken from one of the source or target aliases
 * (e.g. `drush sql-sync @source @target`), which will be overlayed with
 * any options that begin with 'source-' or 'target-', respectively.
 * Therefore, the commandline options 'source-database' and 'source-target'
 * (or 'target-database' and 'source-target') may also affect the operation
 * of this function.
 */
function drush_sitealias_get_db_spec(&$alias_record, $default_to_self = FALSE, $prefix = '') {
  $db_spec = NULL;
  $databases = sitealias_get_databases_from_record($alias_record);
  if (isset($databases) && !empty($databases)) {
    $database = drush_sitealias_get_option($alias_record, 'database', 'default', $prefix);
    $target = drush_sitealias_get_option($alias_record, 'target', 'default', $prefix);
    if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) {
      $db_spec = $databases[$database][$target];
    }
  }
  elseif ($default_to_self) {
    $db_spec = _drush_sql_get_db_spec();
  }

  if (isset($db_spec)) {
    $remote_host = drush_sitealias_get_option($alias_record, 'remote-host', NULL, $prefix);
    if (!drush_is_local_host($remote_host)) {
      $db_spec['remote-host'] = $remote_host;
      $db_spec['port'] = drush_sitealias_get_option($alias_record, 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL), $prefix);
    }
  }

  return $db_spec;
}

/**
 * If the alias record does not contain a 'databases' or 'db-url'
 * entry, then use backend invoke to look up the settings value
 * from the remote or local site.  The 'databases' form is
 * preferred; 'db_url' will be converted to 'databases' if necessary.
 *
 * @param $alias_record
 *   The full alias record to populate with database settings
 */
function drush_sitealias_add_db_settings(&$alias_record) {
  $altered_record = FALSE;
  if (isset($alias_record['root'])) {
    // If the alias record does not have a defined 'databases' entry,
    // then we'll need to look one up
    if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
      $values = drush_invoke_process($alias_record, "sql-conf", array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE));
      if (is_array($values) && ($values['error_status'] == 0)) {
        $altered_record = TRUE;
        // If there are any special settings in the '@self' record returned by drush_invoke_process,
        // then add those into our altered record as well
        if (array_key_exists('self', $values)) {
          $alias_record = array_merge($values['self'], $alias_record);
        }
        drush_sitealias_cache_db_settings($alias_record, $values['object']);
      }
    }
  }
  return $altered_record;
}

function drush_sitealias_cache_db_settings(&$alias_record, $databases) {
  if (!empty($databases)) {
    $alias_record['databases'] = $databases;
  }

  // If the name is set, then re-cache the record after we fetch the databases
  if (array_key_exists('#name', $alias_record)) {
    $all_site_aliases =& drush_get_context('site-aliases');
    $all_site_aliases['@' . $alias_record['#name']] = $alias_record;
    // Check and see if this record is a copy of 'self'
    if (($alias_record['#name'] != 'self') && array_key_exists('@self', $all_site_aliases) && array_key_exists('#name', $all_site_aliases['@self']) && ($all_site_aliases['@self']['#name'] == $alias_record['#name'])) {
      $all_site_aliases['@self'] = $alias_record;
    }
  }
}

/**
 * Check to see if we have already bootstrapped to a site.
 */
function drush_sitealias_is_bootstrapped_site($alias_record) {
  if (!isset($alias_record['remote-host']) && array_key_exists('root', $alias_record)) {
    $self_record = drush_sitealias_get_record("@self");
    if (empty($self_record) || !array_key_exists('root', $self_record)) {
      // TODO:  If we have not bootstrapped to a site yet, we could
      // perhaps bootstrap to $alias_record here.
      return FALSE;
    }
    elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Determines whether a given site alias is for a remote site.
 *
 * @param string $alias
 *   An alias name or site specification.
 *
 * @return bool
 *   Returns TRUE if the alias refers to a remote site, FALSE if it does not, or NULL is unsure.
 */
function drush_sitealias_is_remote_site($alias) {
  if (is_array($alias) && !empty($alias['remote-host'])) {
    return TRUE;
  }
  if (!is_string($alias) || !strlen($alias)) {
    return NULL;
  }

  $site_record = drush_sitealias_get_record($alias);
  if ($site_record) {
    if (!empty($site_record['remote-host'])) {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }
  else {
    drush_set_error('Unrecognized site alias.');
  }
}

/**
 * Get the name of the current bootstrapped site
 */
function drush_sitealias_bootstrapped_site_name() {
  $site_name = NULL;
  $self_record = drush_sitealias_get_record('@self');
  if (array_key_exists('#name', $self_record)) {
    $site_name = $self_record['#name'];
  }
  if (!isset($site_name) || ($site_name == '@self')) {
    $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
    if (isset($drupal_root)) {
      $drupal_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
      $drupal_uri = str_replace('http://', '', $drupal_uri);
      // TODO: Maybe use _drush_sitealias_find_local_alias_name?
      $site_name = $drupal_root . '#' . $drupal_uri;
    }
  }
  return $site_name;
}

/**
 * If there are any path aliases (items beginning with "%") in the test
 * string, then resolve them as path aliases and add them to the provided
 * alias record.
 *
 * @param $alias_record
 *   The full alias record to use in path alias expansion
 * @param $test_string
 *   A slash-separated list of path aliases to resolve
 *   e.g. "%files/%special".
 */
function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') {
  $path_aliases = array_key_exists('path-aliases', $alias_record) ? $alias_record['path-aliases'] : array();
  // Convert the test string into an array of items, and
  // from this make a comma-separated list of projects
  // that we can pass to 'drush status'.
  $test_array = explode('/', $test_string);
  $project_array = array();
  foreach($test_array as $one_item) {
    if (!empty($one_item) && ($one_item[0] == '%') && (!array_key_exists($one_item,$path_aliases))) {
      $project_array[] = substr($one_item,1);
    }
  }
  $project_list = implode(',', $project_array);

  if (!empty($project_array)) {
    // Optimization:  if we're already bootstrapped to the
    // site specified by $alias_record, then we can just
    // call _core_site_status_table() rather than use backend invoke.
    if (drush_sitealias_is_bootstrapped_site($alias_record) && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      $status_values = _core_site_status_table($project_list);
    }
    else {
      $values = drush_invoke_process($alias_record, "core-status", array(), empty($project_list) ? array() : array('project' => $project_list), array('integrate' => FALSE, 'override-simulated' => TRUE));
      $status_values = $values['object'];
    }
    if (isset($status_values['%paths'])) {
      foreach ($status_values['%paths'] as $key => $path) {
        $alias_record['path-aliases'][$key] = $path;
      }
    }
    // If 'root' is not set in the alias, then fill it in from the status values.
    if (!isset($alias_record['root']) && isset($status_values['root'])) {
      $alias_record['root'] = $status_values['root'];
    }
  }
}

/**
 * Given an alias record that is a site list (contains a 'site-list' entry),
 * resolve all of the members of the site list and return them
 * is an array of alias records.
 *
 * @param $alias_record
 *   The site list alias record array
 * @return
 *   An array of individual site alias records
 */
function drush_sitealias_resolve_sitelist($alias_record) {
  $result_list = array();
  if (isset($alias_record)) {
    if (array_key_exists('site-list', $alias_record)) {
      foreach ($alias_record['site-list'] as $sitespec) {
        $one_result = drush_sitealias_get_record($sitespec);
        $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result));
      }
    }
    elseif (array_key_exists('#name', $alias_record)) {
      $result_list[$alias_record['#name']] = $alias_record;
    }
  }

  return $result_list;
}

function _drush_sitelist_find_in_list($one_source, &$target) {
  $result = FALSE;

  foreach ($target as $key => $one_target) {
    if(_drush_sitelist_check_site_records($one_source, $one_target)) {
      $result = $one_target;
      unset($target[$key]);
    }
  }

  return $result;
}

function _drush_sitelist_check_site_records($source, $target) {
  if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Initialize an alias record; called as soon as the alias
 * record is loaded from its alias file, before it is stored
 * in the cache.
 *
 * @param alias_record
 *   The alias record to be initialized; parameter is modified in place.
 */
function _drush_sitealias_initialize_alias_record(&$alias_record) {
  // If there is a 'from-list' entry, then build a derived
  // list based on the site list with the given name.
  if (array_key_exists('from-list', $alias_record)) {
    // danger of infinite loops... move to transient defaults?
    $from_record = drush_sitealias_get_record($alias_record['from-list']);
    $from_list = drush_sitealias_resolve_sitelist($from_record);
    $derived_list = array();
    foreach ($from_list as $one_record) {
      $derived_record = _drush_sitealias_derive_record($one_record, $alias_record);
      $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record);
    }

    $alias_record = array();
    if (!empty($derived_list)) {
      $alias_record['site-list'] = $derived_list;
    }
  }
  // If there is a 'site-search-path' entry, then build
  // a 'site-list' entry from all of the sites that can be
  // found in the search path.
  if (array_key_exists('site-search-path', $alias_record)) {
    // TODO:  Is there any point in merging the sites from
    // the search path with any sites already listed in the
    // 'site-list' entry?  For now we'll just overwrite.
    $search_path = $alias_record['site-search-path'];
    if (!is_array($search_path)) {
      $search_path = explode(',', $search_path);
    }
    $found_sites = _drush_sitealias_find_local_sites($search_path);
    $alias_record['site-list'] = $found_sites;
    // The 'unordered-list' flag indicates that the order of the items in the site list is not stable.
    $alias_record['unordered-list'] = '1';
    // DEBUG: var_export($alias_record, FALSE);
  }
  if (array_key_exists('site-list', $alias_record)) {
    if (!is_array($alias_record['site-list'])) {
      $alias_record['site-list'] = explode(',', $alias_record['site-list']);
    }
  }
  else {
    if (isset($alias_record['root']) && !isset($alias_recort['uri'])) {
      $alias_recort['uri'] = 'default';
    }
  }
}

/**
 * Add "static" default values to the given alias record.  The
 * difference between a static default and a transient default is
 * that static defaults -always- exist in the alias record, and
 * they are cached, whereas transient defaults are only added
 * if the given drush command explicitly adds them.
 *
 * @param alias_record
 *   An alias record with most values already filled in
 */
function _drush_sitealias_add_static_defaults(&$alias_record) {
  // If there is a 'db-url' entry but not 'databases' entry, then we will
  // build 'databases' from 'db-url' so that drush commands that use aliases
  // can always count on using a uniform 'databases' array.
  if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) {
    $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']);
  }

  // Canonicalize paths.
  if (!empty($alias_record['root'])) {
    $alias_record['root'] = Path::canonicalize($alias_record['root']);
  }

  // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists)
  if (array_key_exists('uri', $alias_record)) {
    // Make sure that there is always a 'path-aliases' array
    if (!array_key_exists('path-aliases', $alias_record)) {
      $alias_record['path-aliases'] = array();
    }
    // If there is a 'root' entry, then copy it to the '%root' path alias
    if (isset($alias_record['root'])) {
      $alias_record['path-aliases']['%root'] = $alias_record['root'];
    }
  }
}

function _drush_sitealias_derive_record($from_record, $modifying_record) {
  $result = $from_record;

  // If there is a 'remote-user' in the modifying record, copy it.
  if (array_key_exists('remote-user', $modifying_record)) {
    $result['remote-user'] = $from_record['remote_user'];
  }
  // If there is a 'remote-host', then:
  //   If it is empty, clear the remote host in the result record
  //   If it ends in '.', then prepend it to the remote host in the result record
  //   Otherwise, copy it to the result record
  if (array_key_exists('remote-host', $modifying_record)) {
    $remote_host_modifier = $modifying_record['remote-host'];
    if(empty($remote_host_modifier)) {
      unset($result['remote-host']);
      unset($result['remote-user']);
    }
    elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') {
      $result['remote-host'] = $remote_host_modifier . $result['remote-host'];
    }
    else {
      $result['remote-host'] = $remote_host_modifier;
    }
  }
  // If there is a 'root', then:
  //   If it begins with '/', copy it to the result record
  //   Otherwise, append it to the result record
  if (array_key_exists('root', $modifying_record)) {
    $root_modifier = $modifying_record['root'];
    if($root_modifier[0] == '/') {
      $result['root'] = $root_modifier;
    }
    else {
      $result['root'] = $result['root'] . '/' . $root_modifier;
    }
  }
  // Poor man's realpath: take out the /../ with preg_replace.
  // (realpath fails if the files in the path do not exist)
  while(strpos($result['root'], '/../') !== FALSE) {
    $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']);
  }

  // TODO:  Should we allow the uri to be transformed?
  // I think that if the uri does not match, then you should
  // always build the list by hand, and not rely on '_drush_sitealias_derive_record'.

  return $result;
}

/**
 * Convert from an alias record to a site specification
 *
 * @param alias_record
 *   The full alias record to convert
 *
 * @param with_db
 *   True if the site specification should include a ?db-url term
 *
 * @return string
 *   The site specification
 */
function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
    $result = '';

    // TODO:  we should handle 'site-list' records too.
    if (array_key_exists('site-list', $alias_record)) {
      // TODO:  we should actually expand the site list and recompose it
      $result = implode(',', $alias_record['site-list']);
    }
    else {
      // There should always be a uri
      if (array_key_exists('uri', $alias_record)) {
        $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record));
      }
      // There should always be a root
      if (array_key_exists('root', $alias_record)) {
        $result = $alias_record['root'] . $result;
      }
      if (array_key_exists('remote-host', $alias_record)) {
        $result = drush_remote_host($alias_record) . $result;
      }

      // Add the database info to the specification if desired
      if ($with_db) {
        // If db-url is not supplied, look it up from the remote
        // or local site and add it to the site alias
        if (!isset($alias_record['db-url'])) {
          drush_sitealias_add_db_url($alias_record);
        }
        $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']);
      }
    }

    return $result;
}

/**
 * Search for drupal installations in the search path.
 *
 * @param search_path
 *   An array of drupal root folders
 *
 * @return
 *   An array of site specifications (/path/to/root#sitename.com)
 */
function _drush_sitealias_find_local_sites($search_path) {
  $result = array();
  foreach ($search_path as $a_drupal_root) {
    $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root));
  }
  return $result;
}

/**
 * Return a list of all of the local sites at the specified drupal root.
 */
function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) {
  $site_list = array();
  $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root );
  if (!empty($base_path)) {
    if (drush_valid_root($base_path)) {
      // If $a_drupal_root is in fact a valid drupal root, then return
      // all of the sites found inside the 'sites' folder of this drupal instance.
      $site_list = _drush_find_local_sites_in_sites_folder($base_path);
    }
    else {
      $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_SIGNATURE) . '/' , array('.', '..', 'CVS', 'examples'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1);
      foreach ($bootstrap_files as $one_bootstrap => $info) {
        $includes_dir = dirname($one_bootstrap);
        if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_SIGNATURE))) {
          $drupal_root = dirname($includes_dir);
          $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list);
        }
      }
    }
  }
  return $site_list;
}

/**
 * Return a list of all of the local sites at the specified 'sites' folder.
 */
function _drush_find_local_sites_in_sites_folder($a_drupal_root) {
  $site_list = array();

  // If anyone searches for sites at a given root, then
  // make sure that alias files stored at this root
  // directory are included in the alias search path
  drush_sitealias_add_to_alias_path($a_drupal_root);

  $base_path = $a_drupal_root . '/sites';

  // TODO:  build a cache keyed off of $base_path (realpath($base_path)?),
  // so that it is guarenteed that the lists returned will definitely be
  // exactly the same should this routine be called twice with the same path.

  $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1);
  foreach ($files as $filename => $info) {
    if ($info->basename == 'settings.php') {
      // First we'll resolve the realpath of the settings.php file,
      // so that we get the correct drupal root when symlinks are in use.
      $real_sitedir = dirname(realpath($filename));
      $real_root = drush_locate_root($filename);
      if ($real_root !== FALSE) {
        $a_drupal_site = $real_root . '#' . basename($real_sitedir);
      }
      // If the symlink points to some folder outside of any drupal
      // root, then we'll use the
      else {
        $uri = drush_sitealias_site_dir_from_filename($filename);
        $a_drupal_site = $a_drupal_root . '#' . $uri;
      }
      // Add the site if it isn't already in the array
      if (!in_array($a_drupal_site, $site_list)) {
        $site_list[] = $a_drupal_site;
      }
    }
  }
  return $site_list;
}

function drush_sitealias_create_sites_alias($a_drupal_root = '') {
  $sites_list = _drush_find_local_sites_at_root($a_drupal_root);
  _drush_sitealias_cache_alias('@sites', array('site-list' => $sites_list));
}

/**
 * Add "transient" default values to the given alias record.  The
 * difference between a static default and a transient default is
 * that static defaults -always- exist in the alias record,
 * whereas transient defaults are only added if the given drush
 * command explicitly calls this function.  The other advantage
 * of transient defaults is that it is possible to differentiate
 * between a default value and an unspecified value, since the
 * transient defaults are not added until requested.
 *
 * Since transient defaults are not cached, you should avoid doing
 * expensive operations here.  To be safe, drush commands should
 * avoid calling this function more than once.
 *
 * @param alias_record
 *   An alias record with most values already filled in
 */
function _drush_sitealias_add_transient_defaults(&$alias_record) {
  if (isset($alias_record['path-aliases'])) {
    // Add the path to the drush folder to the path aliases as !drush
    if (!array_key_exists('%drush', $alias_record['path-aliases'])) {
      if (array_key_exists('%drush-script', $alias_record['path-aliases'])) {
        $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']);
      }
      else {
        $alias_record['path-aliases']['%drush'] = dirname(drush_find_drush());
      }
    }
    // Add the path to the site folder to the path aliases as !site
    if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) {
      $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record)) . '/';
    }
  }
}

/**
 * Find the name of a local alias record that has the specified
 * root and uri.
 */
function _drush_sitealias_find_local_alias_name($root, $uri) {
  $result = '';
  $all_site_aliases =& drush_get_context('site-aliases');

  foreach ($all_site_aliases as $alias_name => $alias_values) {
    if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) {
      if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) {
        $result = $alias_name;
      }
    }
  }

  return $result;
}

/**
 * If '$alias' is the name of a folder in the sites folder of the given drupal
 * root, then build an alias record for it
 *
 * @param alias
 *   The name of the site in the 'sites' folder to convert
 * @return array
 *   An alias record, or empty if none found.
 */
function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = NULL) {
  $alias_record = array();

  // Clip off the leading '#' if it is there
  if (substr($alias,0,1) == '#') {
    $alias = substr($alias,1);
  }

  if (!isset($drupal_root)) {
    $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  }

  if (!empty($drupal_root)) {
    $alias_dir = drush_sitealias_uri_to_site_dir($alias, $drupal_root);
    $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php';
    $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root);
  }

  return $alias_record;
}

function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) {
  $alias_record = array();

  if (file_exists($site_settings_file)) {
    if (!isset($drupal_root)) {
      $drupal_root = drush_locate_root($site_settings_file);
    }

    $alias_record['root'] = $drupal_root;
    if (isset($alias)) {
      $alias_record['uri'] = $alias;
    }
    else {
      $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file));
    }
  }

  return $alias_record;
}

/**
 * Pull the site directory from the path to settings.php
 *
 * @param site_settings_file
 *   path to settings.php
 *
 * @return string
 *   the site directory component of the path to settings.php
 */
function drush_sitealias_site_dir_from_filename($site_settings_file) {
  return basename(dirname($site_settings_file));
}

/**
 * Convert from a URI to a site directory.
 *
 * @param uri
 *   A uri, such as http://domain.com:8080/drupal
 * @return string
 *   A directory, such as domain.com.8080.drupal
 */
function drush_sitealias_uri_to_site_dir($uri, $site_root = NULL) {
  $uri = str_replace('http://', '', $uri);
  $uri = str_replace('https://', '', $uri);
  if (drush_is_windows()) {
    // Handle absolute paths on windows
    $uri = str_replace(array(':/', ':\\'), array('.', '.'), $uri);
  }

  $hostname = str_replace(array('/', ':', '\\'), array('.', '.', '.'), $uri);

  // Check sites.php mappings
  $site_dir = drush_site_dir_lookup_from_hostname($hostname, $site_root);

  return $site_dir ? $site_dir : $hostname;
}

/**
 * Convert from an old-style database URL to an array of database settings.
 *
 * @param db_url
 *   A Drupal 6 db url string to convert, or an array with a 'default' element.
 * @return array
 *   An array of database values containing only the 'default' element of
 *   the db url. If the parse fails the array is empty.
 */
function drush_convert_db_from_db_url($db_url) {
  $db_spec = array();

  if (is_array($db_url)) {
    $db_url_default = $db_url['default'];
  }
  else {
    $db_url_default = $db_url;
  }

  // If it's a sqlite database, pick the database path and we're done.
  if (strpos($db_url_default, 'sqlite://') === 0) {
    $db_spec = array(
      'driver'   => 'sqlite',
      'database' => substr($db_url_default, strlen('sqlite://')),
    );
  }
  else {
    $url = parse_url($db_url_default);
    if ($url) {
      // Fill in defaults to prevent notices.
      $url += array(
        'scheme' => NULL,
        'user'   => NULL,
        'pass'   => NULL,
        'host'   => NULL,
        'port'   => NULL,
        'path'   => NULL,
      );
      $url = (object)array_map('urldecode', $url);
      $db_spec = array(
        'driver'   => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme,
        'username' => $url->user,
        'password' => $url->pass,
        'host' => $url->host,
        'port' => $url->port,
        'database' => ltrim($url->path, '/'),
      );
    }
  }

  return $db_spec;
}

/**
 * Convert from an old-style database URL to an array of database settings
 *
 * @param db_url
 *   A Drupal 6 db-url string to convert, or an array with multiple db-urls.
 * @return array
 *   An array of database values.
 */
function drush_sitealias_convert_db_from_db_url($db_url) {
  $result = array();

  if (!is_array($db_url)) {
    $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url)));
  }
  else {
    foreach ($db_url as $one_name => $one_db_url) {
      $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url));
    }
  }

  return $result;
}

/**
 * Utility function used by drush_get_alias; keys that start with
 * '%' or '!' are path aliases, the rest are entries in the alias record.
 */
function _drush_sitealias_set_record_element(&$alias_record, $key, $value) {
  if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) {
    $alias_record['path-aliases'][$key] = $value;
  }
  elseif (!empty($key)) {
    $alias_record[$key] = $value;
  }
}

/**
 * Looks up the specified alias record and calls through to
 * drush_sitealias_set_alias_context, below.
 *
 * @param alias
 *   The name of the alias record
 * @param prefix
 *   The prefix value to afix to the beginning of every
 *   key set.
 * @return boolean
 *   TRUE is an alias was found and processed.
 */
function _drush_sitealias_set_context_by_name($alias, $prefix = '') {
  if ($alias) {
    $site_alias_settings = drush_sitealias_get_record($alias);
    if (!empty($site_alias_settings)) {
      drush_sitealias_set_alias_context($site_alias_settings, $prefix);
      drush_sitealias_cache_alias_by_path($site_alias_settings);
      if (empty($prefix)) {

        // Create an alias '@self'
        // Allow 'uri' from the commandline to override
        $drush_uri = drush_get_option(array('uri', 'l'), FALSE);
        if ($drush_uri) {
          $site_alias_settings['uri'] = $drush_uri;
        }

        _drush_sitealias_cache_alias('@self', $site_alias_settings);
        // Change the selected site to match the new --root and --uri, if any were set.
        _drush_preflight_root_uri();
      }
      return $site_alias_settings;
    }
  }
  return array();
}

/**
 * Given an alias record, overwrite its values with options
 * from the command line and other drush contexts as specified
 * by the provided prefix.  For example, if the prefix is 'source-',
 * then any option 'source-foo' will set the value 'foo' in the
 * alias record.
 */
function drush_sitealias_overlay_options($site_alias_record, $prefix) {
  return array_merge($site_alias_record, drush_get_merged_prefixed_options($prefix));
}

/**
 * First return an option set via drush_sitealias_overlay_options, if
 * any, then fall back on "%" . $option from the path aliases.
 */
function drush_sitealias_get_path_option($site_alias_record, $option, $default = NULL) {
  if (isset($site_alias_record) && array_key_exists($option, $site_alias_record)) {
    return $site_alias_record[$option];
  }
  if (isset($site_alias_record) && array_key_exists('path-aliases', $site_alias_record) && array_key_exists("%$option", $site_alias_record['path-aliases'])) {
    return $site_alias_record['path-aliases']["%$option"];
  }
  else {
    return drush_get_option($option, $default);
  }
}

/**
 * Given a site alias record, copy selected fields from it
 * into the drush 'alias' context.  The 'alias' context has
 * lower precedence than the 'cli' context, so values
 * set by an alias record can be overridden by command-line
 * parameters.
 *
 * @param site_alias_settings
 *   An alias record
 * @param prefix
 *   The prefix value to affix to the beginning of every
 *   key set.  For example, if this function is called once with
 *   'source-' and again with 'destination-' prefixes, then the
 *   source database records will be stored in 'source-databases',
 *   and the destination database records will be in
 *   'destination-databases'.
 */
function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') {
  $options = drush_get_context('alias');

  // There are some items that we should just skip
  $skip_list = drush_get_special_keys();
  // If 'php-options' are set in the alias, then we will force drush
  // to redispatch via the remote dispatch mechanism even if the target is localhost.
  if ((array_key_exists('php-options', $site_alias_settings) || array_key_exists('php', $site_alias_settings)) && !drush_get_context('DRUSH_BACKEND', FALSE)) {
    if (!array_key_exists('remote-host', $site_alias_settings)) {
      $site_alias_settings['remote-host'] = 'localhost';
    }
  }
  // If 'php-options' are not set in the alias, then skip 'remote-host'
  // and 'remote-user' if 'remote-host' is actually the local machine.
  // This prevents drush from using the remote dispatch mechanism (the command
  // is just run directly on the local machine, bootstrapping to the specified alias)
  elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) {
    $skip_list[] = 'remote-host';
    $skip_list[] = 'remote-user';
  }
  // If prefix is set, then copy from the 'prefix-' version
  // of the drush special keys ('command-specific', 'path-aliases')
  // into the ordinary version.  This will allow us to set
  // 'source-command-specific' options that will only apply when
  // the alias is used as the source option for rsync or sql-sync.
  if (!empty($prefix)) {
    $special_contexts = drush_get_special_keys();
    foreach ($special_contexts as $option_name) {
      if (array_key_exists($prefix . $option_name, $site_alias_settings)) {
        $site_alias_settings[$option_name] = array_key_exists($option_name, $site_alias_settings) ? array_merge($site_alias_settings[$option_name], $site_alias_settings[$prefix . $option_name]) : $site_alias_settings[$prefix . $option_name];
      }
    }
  }
  // Transfer all options from the site alias to the drush options
  // in the 'alias' context.
  foreach ($site_alias_settings as $key => $value) {
    // Special handling for path aliases:
    if ($key == "path-aliases") {
      $path_aliases = $value;
      foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) {
        if (array_key_exists($path_key, $path_aliases)) {
          // Evaluate the path value, and substitute any path references found.
          // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder
          // 'dumps' in the Drupal root folder for the site.
          $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]);
          $options[$prefix . substr($path_key, 1)] = $evaluated_path;
        }
      }
    }
    // Special handling for command-specific
    elseif ($key == "command-specific") {
      $options[$key] = $value;
    }
    elseif (!in_array($key, $skip_list)) {
      $options[$prefix . $key] = $value;
    }
  }
  drush_set_config_options('alias', $options);
}

/**
 * Call prior to drush_sitealias_evaluate_path to insure
 * that any site-specific aliases associated with any
 * local site in $path are defined.
 */
function _drush_sitealias_preflight_path($path) {
  $alias = NULL;
  // Parse site aliases if there is a colon in the path
  // We allow:
  //   @alias:/path
  //   machine.domain.com:/path
  //   machine:/path
  // Note that paths in the form "c:/path" are converted to
  // "/cygdrive/c/path" later; we do not want them to confuse
  // us here, so we skip paths that start with a single character
  // before the colon if we are running on Windows.  Single-character
  // machine names are allowed in Linux only.
  $colon_pos = strpos($path, ':');
  if ($colon_pos > (drush_is_windows("LOCAL") ? 1 : 0)) {
    $alias = substr($path, 0, $colon_pos);
    $path = substr($path, $colon_pos + 1);
    $site_alias_settings = drush_sitealias_get_record($alias);
    if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
      return NULL;
    }
    $machine = $alias;
  }
  else {
    $machine = '';
    // if the path is a site alias or a local site...
    $site_alias_settings = drush_sitealias_get_record($path);
    if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
      return NULL;
    }
    if (!empty($site_alias_settings) || drush_is_local_host($path)) {
      $alias = $path;
      $path = '';
    }
  }
  return array('alias' => $alias, 'path' => $path, 'machine' => $machine);
}

/**
 * Given a properly-escaped options string, replace any occurance of
 * %files and so on embedded inside it with its corresponding path.
 */
function drush_sitealias_evaluate_paths_in_options($option_string) {
  $path_aliases = _core_path_aliases();
  return str_replace(array_keys($path_aliases), array_values($path_aliases), $option_string);
}

/**
 * Evaluate a path from its shorthand form to a literal path
 * usable by rsync.
 *
 * A path is "machine:/path" or "machine:path" or "/path" or "path".
 * 'machine' might instead be an alias record, or the name
 * of a site in the 'sites' folder.  'path' might be (or contain)
 * '%root' or some other path alias.  This function will examine
 * all components of the path and evaluate them as necessary to
 * come to the final path.
 *
 * @param path
 *   The path to evaluate
 * @param additional_options
 *   An array of options that overrides whatever was passed in on
 *   the command line (like the 'process' context, but only for
 *   the scope of this one call).
 * @param local_only
 *   If TRUE, force an error if the provided path points to a remote
 *   machine.
 * @param os
 *   This should be the local system os, unless evaluate path is
 *   being called for rsync, in which case it should be "CWRSYNC"
 *   if cwrsync is being used, or "rsync" to automatically select
 *   between "LOCAL" and "CWRSYNC" based on the platform.
 * @return
 *   The site record for the machine specified in the path, if any,
 *   with the path to pass to rsync (including the machine specifier)
 *   in the 'evaluated-path' item.
 */
function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE, $os = NULL, $command_specific_prefix = '') {
  $site_alias_settings = array();
  $path_aliases = array();
  $remote_user = '';

  $preflight = _drush_sitealias_preflight_path($path);
  if (!isset($preflight)) {
    return NULL;
  }

  $alias = $preflight['alias'];
  $path = $preflight['path'];
  $machine = $preflight['machine'];

  if (isset($alias)) {
    // Note that the alias settings may have an 'os' component, but we do
    // not want to use it here.  The paths passed to rsync should always be
    // escaped per the LOCAL rules, without regard to the remote platform type.
    $site_alias_settings = drush_sitealias_get_record($alias);
    if (!empty($command_specific_prefix)) {
      drush_command_set_command_specific_options($command_specific_prefix);
      drush_sitealias_command_default_options($site_alias_settings, $command_specific_prefix);
    }
  }

  if (!empty($site_alias_settings)) {
    if ($local_only && array_key_exists('remote-host', $site_alias_settings)) {
      return drush_set_error('DRUSH_REMOTE_SITE_IN_LOCAL_CONTEXT', dt("A remote site alias was used in a context where only a local alias is appropriate."));
    }

    // Apply any options from this alias that might affect our rsync
    drush_sitealias_set_alias_context($site_alias_settings);

    // Use 'remote-host' from settings if available; otherwise site is local
    if (drush_sitealias_is_remote_site($site_alias_settings)) {
      $machine = drush_remote_host($site_alias_settings);
    }
    else {
      $machine = '';
    }
  }
  else {
    // Strip the machine portion of the path if the
    // alias points to the local machine.
    if (drush_is_local_host($machine)) {
      $machine = '';
    }
    else {
      $machine = "$remote_user$machine";
    }
  }

  // TOD:  The code below is a little rube-goldberg-ish, and needs to be
  // reworked.  core-rsync will call this function twice: once to
  // evaluate the destination, and then again to evaluate the source.  Things
  // get odd with --exclude-paths, especially in conjunction with command-specific
  // and the --exclude-files option.  @see testCommandSpecific()

  // If the --exclude-other-sites option is specified, then
  // convert that into --include-paths='%site' and --exclude-sites.
  if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_context('exclude-other-sites-processed', FALSE)) {
    $include_path_option = drush_get_option_override($additional_options, 'include-paths', '');
    $additional_options['include-paths'] = '%site';
    if (!empty($include_path_option)) {
      // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
      $additional_options['include-paths'] .= PATH_SEPARATOR . $include_path_option;
    }
    $additional_options['exclude-sites'] = TRUE;
    drush_set_context('exclude-other-sites-processed', TRUE);
  }
  else {
    unset($additional_options['include-paths']);
  }
  // If the --exclude-files option is specified, then
  // convert that into --exclude-paths='%files'.
  if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) {
    $exclude_path_option = drush_get_option_override($additional_options, 'exclude-paths', '');
    $additional_options['exclude-paths'] = '%files';
    if (!empty($exclude_path_option)) {
      // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
      $additional_options['exclude-paths'] .= PATH_SEPARATOR . $exclude_path_option;
    }
    $additional_options['exclude-files-processed'] = TRUE;
  }
  else {
    unset($additional_options['exclude-paths']);
  }

  // If there was no site specification given, and the
  // machine is local, then try to look
  // up an alias record for the default drush site.
  if (empty($site_alias_settings) && empty($machine)) {
    $drush_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
    $site_alias_settings = drush_sitealias_get_record($drush_uri);
  }

  // Always add transient defaults
  _drush_sitealias_add_transient_defaults($site_alias_settings);

  // The $resolve_path variable is used by drush_sitealias_resolve_path_references
  // to test to see if there are any path references such as %site or %files
  // in it, so that resolution is only done if the path alias is referenced.
  // Therefore, we can concatenate without worrying too much about the structure of
  // this variable's contents.
  $include_path = drush_get_option_override($additional_options, 'include-paths', '');
  $exclude_path = drush_get_option_override($additional_options, 'exclude-paths', '');
  if (is_array($include_path)) {
    $include_path = implode('/', $include_path);
  }
  if (is_array($exclude_path)) {
    $include_path = implode('/', $exclude_path);
  }
  $resolve_path = "$path/$include_path/$exclude_path";
  // Resolve path aliases such as %files, if any exist in the path
  if (!empty($resolve_path)) {
    drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path);
  }

  if (array_key_exists('path-aliases', $site_alias_settings)) {
    $path_aliases = $site_alias_settings['path-aliases'];
  }

  // Get the 'root' setting from the alias; if it does not
  // exist, then get the root from the bootstrapped site.
  if (array_key_exists('root', $site_alias_settings)) {
    $drupal_root = $site_alias_settings['root'];
  }
  elseif (!drush_sitealias_is_remote_site($site_alias_settings)) {
    drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
    $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  }
  if (empty($drupal_root)) {
    $drupal_root = '';
  }
  else {
    // Add a slash to the end of the drupal root, as below.
    $drupal_root = drush_trim_path($drupal_root) . "/";
  }
  $full_path_aliases = $path_aliases;
  foreach ($full_path_aliases as $key => $value) {
    // Expand all relative path aliases to be based off of the Drupal root
    if (!drush_is_absolute_path($value, "LOCAL") && ($key != '%root')) {
      $full_path_aliases[$key] = $drupal_root . $value;
    }
    // We do not want slashes on the end of our path aliases.
    $full_path_aliases[$key] = drush_trim_path($full_path_aliases[$key]);
  }

  // Fill in path aliases in the path, the include path and the exclude path.
  $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path);
  if (!empty($include_path)) {
    drush_set_option('include-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path));
  }
  if (!empty($exclude_path)) {
    drush_set_option('exclude-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path));
  }
  // Next make the rsync path, which includes the machine
  // and path components together.
  // First make empty paths or relative paths start from the drupal root.
  if (empty($path) || (!drush_is_absolute_path($path, "LOCAL"))) {
    $path = $drupal_root . $path;
  }
  // When calculating a path for use with rsync, we must correct
  // absolute paths in the form c:\path when cwrsync is in use.
  $path = drush_correct_absolute_path_for_exec($path, $os);

  // If there is a $machine component, to the path, then
  // add it to the beginning
  $evaluated_path = drush_escapeshellarg($path, $os);
  if (!empty($machine)) {
    $evaluated_path = $machine . ':' . $evaluated_path;
  }

  //
  // Add our result paths:
  //
  //    evaluated-path:         machine:/path
  //    server-component:       machine
  //    path-component:         :/path
  //    path:                   /path
  //    user-path:              path (as specified in input parameter)
  //
  $site_alias_settings['evaluated-path'] = $evaluated_path;
  if (!empty($machine)) {
    $site_alias_settings['server-component'] = $machine;
  }
  $site_alias_settings['path-component'] = (!empty($path) ? ':' . $path : '');
  $site_alias_settings['path'] = $path;
  $site_alias_settings['user-path'] = $preflight['path'];

  return $site_alias_settings;
}

/**
 * Option keys used for site selection.
 */
function drush_sitealias_site_selection_keys() {
  return array('remote-host', 'remote-user', 'ssh-options', '#name', 'os');
}


function sitealias_find_local_drupal_root($site_list) {
  $drupal_root = NULL;

  foreach ($site_list as $site) {
    if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) {
      $drupal_root = $site['root'];
    }
  }

  return $drupal_root;
}


/**
 * Helper function to obtain the keys' names that need special handling in certain
 * cases.
 * @return
 *   A non-associative array containing the needed keys' names.
 */
function drush_get_special_keys() {
  $special_keys = array(
    'command-specific',
    'site-aliases',
  );
  return $special_keys;
}

/**
 * Read the tmp file where the persistent site setting is stored.
 *
 * @return string
 *   A valid site specification.
 */
function drush_sitealias_site_get() {
  if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) {
    $site = file_get_contents($filename);
    return $site;
  }
  else {
    return FALSE;
  }
}

/**
 * Un-set the currently use'd site alias.
 */
function drush_sitealias_site_clear() {
  if ($filename = drush_sitealias_get_envar_filename()) {
    return drush_delete_dir($filename);
  }
  return FALSE;
}

/**
 * Returns the filename for the file that stores the DRUPAL_SITE variable.
 *
 * @param string $filename_prefix
 *   An arbitrary string to prefix the filename with.
 *
 * @return string|false
 *   Returns the full path to temp file if possible, or FALSE if not.
 */
function drush_sitealias_get_envar_filename($filename_prefix = 'drush-drupal-site-') {
  $shell_pid = getenv('DRUSH_SHELL_PID');
  if (!$shell_pid && function_exists('posix_getppid')) {
    $shell_pid = posix_getppid();
  }
  if (!$shell_pid) {
    return FALSE;
  }

  $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp';
  $username = drush_get_username();

  return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid;
}

/**
 * Cache the specified alias in the alias path cache.  The
 * alias path cache creates a lookup from the site folder
 * (/path/to/drupal/sites/default) to the provided alias record.
 *
 * Only the name of the alias and the path to the file it
 * is stored in is cached; when it is retrieved, it is
 * loaded directly from the correct file.
 */
function drush_sitealias_cache_alias_by_path($alias_record) {
  if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
    $path = drush_sitealias_local_site_path($alias_record);
    if ($path) {
      $cid = drush_get_cid('alias-path-', array(), array($path));
      $alias_path_data = array(
        '#name' => $alias_record['#name'],
        '#file' => $alias_record['#file'],
      );
      drush_cache_set($cid, $alias_path_data);
    }
  }
}

/**
 * Look for a defined alias that points to the specified
 * site directory.  The cache is tested first; if nothing
 * is cached, then an exhaustive search is done for the
 * specified site.  If the exhaustive search returns a
 * match, then it is cached.
 *
 * @param $path
 *   /path/to/drupal/sites/default
 * @return
 *   An alias record for the provided path
 */
function drush_sitealias_lookup_alias_by_path($path, $allow_best_match=FALSE) {
  $result = drush_sitealias_quick_lookup_cached_alias_by_path($path);
  $fallback = array();
  if (empty($result)) {
    $aliases = _drush_sitealias_find_and_load_all_aliases();
    foreach ($aliases as $name => $alias_record) {
      if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
        if ($path == drush_sitealias_local_site_path($alias_record)) {
          $result = $alias_record;
          break;
        }
        if (substr($path, 0, strlen($alias_record['root'])) == $alias_record['root']) {
          $fallback = $alias_record;
        }
      }
    }
  }
  if (empty($result) && $allow_best_match) {
    $result = $fallback;
  }
  if (!empty($result)) {
    _drush_sitealias_add_inherited_values_to_record($result);
    drush_sitealias_cache_alias_by_path($result);
  }
  return $result;
}

/**
 * Look for a cached alias that points to the specified
 * site directory.  Nothing is returned if there is no
 * matching cached alias.
 *
 * @param $path
 *   /path/to/drupal/sites/default
 * @return
 *   An alias record for the provided path
 */
function drush_sitealias_quick_lookup_cached_alias_by_path($path) {
  $alias_record = array();
  $cid = drush_get_cid('alias-path-', array(), array($path));
  $alias_path_cache = drush_cache_get($cid);
  if (isset($alias_path_cache->data)) {
    $alias_name = $alias_path_cache->data['#name'];
    $alias_file = $alias_path_cache->data['#file'];

    $alias_record = _drush_sitealias_find_and_load_alias_from_file($alias_name, array($alias_file));
    _drush_sitealias_add_inherited_values_to_record($alias_record);
    $alias_record['#name'] = $alias_name;
  }
  return $alias_record;
}

/**
 * Return the site root, if there is one in the record.
 */
function drush_sitealias_get_root($alias_record) {
  return array_key_exists('root', $alias_record) ? $alias_record['root'] : NULL;
}

/**
 * Decide on which side to run a core-rsync.
 *
 * @param $source
 * @param $destination
 * @param $runner Where to run the rsync operation: 'destination', 'source',
 *   'auto' ('destination' if both are remote, otherwise '@self') or FALSE (@self)
 * @return mixed
 */
function drush_get_runner($source, $destination, $runner = FALSE) {
  if (is_string($source)) {
    $source = drush_sitealias_get_record($site);
  }
  if (is_string($destination)) {
    $destination = drush_sitealias_get_record($destination);
  }

  // If both sites are remote, and --runner=auto, then we'll use the destination site.
  if (drush_sitealias_is_remote_site($source) && drush_sitealias_is_remote_site($destination)) {
    if ($runner == 'auto') {
      $runner = 'destination';
    }
  }

  // If the user explicitly requests a remote site, then return the selected one.
  if ($runner == 'destination') {
    return "@" . $destination['#name'];
  }
  if ($runner == 'source') {
    return "@" . $source['#name'];
  }

  // Default to running rsync locally. When in doubt, local is best, because
  // we can always resolve aliases here.
  return '@self';
}
<?php

/**
 * @file
 *   Functions used when Drush is starting up.
 *
 * This file is included and used early in the Drush
 * startup process, before any other Drush APIs are
 * available, and before the autoloader has been included.
 */

/**
 * Get the current enviornment.
 */
function drush_env() {
  // Fetch the current environment.  To ensure that
  // $_ENV is correctly populated, make sure that
  // the value of 'variables-order' in your php.ini
  // contains "E" ("Environment").  See:
  // http://us.php.net/manual/en/ini.core.php#ini.variables-order
  $env = $_ENV;

  // If PHP is not configured correctly, $_ENV will be
  // empty.  Drush counts on the fact that environment
  // variables will always be available, though, so we
  // need to repair this situation.  We can always access
  // individual environmnet values via getenv(); however,
  // there is no PHP API that will tell us all of the
  // available values, so we will get the environment
  // variable values using 'printenv'.
  if (empty($env)) {
    exec('printenv', $env_items);
    foreach ($env_items as $item) {
      // Each $item is 'key=value' or just 'key'.
      // If $item has no value, then explode will return
      // a single array, [0 => 'key'].  We add a default
      // value of [1 => 'value'] to cover this case.  If
      // explode returns two items, the default value is ignored.
      list($key, $value) = explode('=', $item, 2) + array(1 => '');
      $env[$key] = $value;
    }
  }

  return $env;
}

/**
 * Checks the provided location and return the appropriate
 * Drush wrapper or Drush launcher script, if found.
 *
 * If the provided location looks like it might be a web
 * root (i.e., it contains an index.php), then we will search
 * in a number of locations in the general vicinity of the
 * web root for a Drush executable.
 *
 * For other locations, we will look only in that specific
 * directory, or in vendor/bin.
 */
function find_wrapper_or_launcher($location) {
  if (file_exists($location. DIRECTORY_SEPARATOR. 'index.php')) {
    return find_wrapper_or_launcher_in_vicinity($location);
  }
  return find_wrapper_or_launcher_in_specific_locations($location, ["", 'vendor'. DIRECTORY_SEPARATOR. 'bin']);
}

/**
 * We look for a "Drush wrapper" script that might
 * be stored in the root of a site.  If there is
 * no wrapper script, then we look for the
 * drush.launcher script in vendor/bin.  We try just a
 * few of the most common locations; if the user relocates
 * their vendor directory anywhere else, then they must
 * use a wrapper script to locate it.  See the comment in
 * 'examples/drush' for details.
 */
function find_wrapper_or_launcher_in_vicinity($location) {
  $sep = DIRECTORY_SEPARATOR;
  $drush_locations = [
    "",
    "vendor{$sep}bin/",
    "..{$sep}vendor{$sep}bin{$sep}",
    "sites{$sep}all{$sep}vendor{$sep}bin{$sep}",
    "sites{$sep}all{$sep}vendor{$sep}drush{$sep}drush{$sep}",
    "sites{$sep}all{$sep}drush{$sep}drush{$sep}",
    "drush{$sep}drush{$sep}",
  ];

  return find_wrapper_or_launcher_in_specific_locations($location, $drush_locations);
}

function find_wrapper_or_launcher_in_specific_locations($location, $drush_locations) {
  $sep = DIRECTORY_SEPARATOR;
  foreach ($drush_locations as $d) {
    $found_script = find_wrapper_or_launcher_at_location("$location$sep$d");
    if (!empty($found_script)) {
      return $found_script;
    }
  }
  return "";
}

/**
 * We are somewhat "loose" about whether we are looking
 * for "drush" or "drush.launcher", because in old versions
 * of Drush, the "drush launcher" was named "drush".
 * Otherwise, there wouldn't be any point in looking for
 * "drush.launcher" at the root, or "drush" in a vendor directory.
 * We also allow users to rename their drush wrapper to
 * 'drush.wrapper' to avoid conflicting with a directory named
 * 'drush' at the site root.
 */
function find_wrapper_or_launcher_at_location($location) {
  $sep = DIRECTORY_SEPARATOR;
  // Sanity-check: empty $location means that we should search
  // at the cwd, not at the root of the filesystem.
  if (empty($location)) {
    $location = ".";
  }
  foreach (array('.launcher', '.wrapper', '') as $suffix) {
    $check_location = "$location{$sep}drush$suffix";
    if (is_file($check_location)) {
      return $check_location;
    }
  }
  return "";
}

/**
 * Determine whether current OS is a Windows variant.
 */
function drush_is_windows($os = NULL) {
  // The _drush_get_os() function may not be available, so resolve "LOCAL"
  if (!$os || $os == "LOCAL") {
    $os = PHP_OS;
  }
  return strtoupper(substr($os, 0, 3)) === 'WIN';
}

function drush_escapeshellarg($arg, $os = NULL, $raw = FALSE) {
  // Short-circuit escaping for simple params (keep stuff readable)
  if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
    return $arg;
  }
  elseif (drush_is_windows($os)) {
    return _drush_escapeshellarg_windows($arg, $raw);
  }
  else {
    return _drush_escapeshellarg_linux($arg, $raw);
  }
}

/**
 * Linux version of escapeshellarg().
 *
 * This is intended to work the same way that escapeshellarg() does on
 * Linux.  If we need to escape a string that will be used remotely on
 * a Linux system, then we need our own implementation of escapeshellarg,
 * because the Windows version behaves differently.
 */
function _drush_escapeshellarg_linux($arg, $raw = FALSE) {
  // For single quotes existing in the string, we will "exit"
  // single-quote mode, add a \' and then "re-enter"
  // single-quote mode.  The result of this is that
  // 'quote' becomes '\''quote'\''
  $arg = preg_replace('/\'/', '\'\\\'\'', $arg);

  // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace.
  // Note that this replacement makes Drush's escapeshellarg work differently
  // than the built-in escapeshellarg in PHP on Linux, as these characters
  // usually are NOT replaced. However, this was done deliberately to be more
  // conservative when running _drush_escapeshellarg_linux on Windows
  // (this can happen when generating a command to run on a remote Linux server.)
  $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg);

  // Only wrap with quotes when needed.
  if(!$raw) {
    // Add surrounding quotes.
    $arg = "'" . $arg . "'";
  }

  return $arg;
}

/**
 * Windows version of escapeshellarg().
 */
function _drush_escapeshellarg_windows($arg, $raw = FALSE) {
  // Double up existing backslashes
  $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);

  // Double up double quotes
  $arg = preg_replace('/"/', '""', $arg);

  // Double up percents.
  // $arg = preg_replace('/%/', '%%', $arg);

  // Only wrap with quotes when needed.
  if(!$raw) {
    // Add surrounding quotes.
    $arg = '"' . $arg . '"';
  }

  return $arg;
}

/**
 * drush_startup is called once, by the Drush "finder"
 * script -- the "drush" script at the Drush root.
 * It finds the correct Drush "wrapper" or "launcher"
 * script to use, and executes it with process replacement.
 */
function drush_startup($argv) {
  $sep = DIRECTORY_SEPARATOR;
  $found_script = "";
  $cwd = getcwd();
  $home = getenv("HOME");
  $use_dir = "$home{$sep}.drush{$sep}use";

  // Get the arguments for the command.  Shift off argv[0],
  // which contains the name of this script.
  $arguments = $argv;
  array_shift($arguments);

  // We need to do at least a partial parsing of the options,
  // so that we can find --root / -r and so on.
  $VERBOSE=FALSE;
  $DEBUG=FALSE;
  $ROOT=FALSE;
  $COMMAND=FALSE;
  $ALIAS=FALSE;
  $VAR=FALSE;

  foreach ($arguments as $arg) {
    // If a variable to set was indicated on the
    // previous iteration, then set the value of
    // the named variable (e.g. "ROOT") to "$arg".
    if ($VAR) {
      $$VAR = "$arg";
      $VAR = FALSE;
    }
    else {
      switch ($arg) {
        case "-r":
          $VAR = "ROOT";
          break;

        case "-dv":
        case "-vd":
        case "--debug":
        case "-d":
          $DEBUG = TRUE;
          break;

        case "-dv":
        case "-vd":
        case "--verbose":
        case "-v":
          $VERBOSE = TRUE;
          break;
      }
      if (!$COMMAND && !$ALIAS && ($arg[0] == '@')) {
        $ALIAS = $arg;
      }
      elseif (!$COMMAND && ($arg[0] != '-')) {
        $COMMAND = $arg;
      }
      if (substr($arg, 0, 7) == "--root=") {
        $ROOT = substr($arg, 7);
      }
    }
  }

  $NONE=($ALIAS == "@none");

  // If we have found the site-local Drush script, then
  // do not search for it again; use the environment value
  // we set last time.
  $found_script = getenv('DRUSH_FINDER_SCRIPT');

  // If the @none alias is used, then we skip the Drush wrapper,
  // and call the Drush launcher directly.
  //
  // In this instance, we are assuming that the 'drush' that is being
  // called is:
  //
  //  a) The global 'drush', or
  //  b) A site-local 'drush' in a vendor/bin directory.
  //
  // In either event, the appropriate 'drush.launcher' should be right next
  // to this script (stored in the same directory).
  if (empty($found_script) && $NONE) {
    if (is_file(dirname(__DIR__) . "{$sep}drush.launcher")) {
      $found_script = dirname(__DIR__) . "{$sep}drush.launcher";
    }
    else {
      fwrite(STDERR, "Could not find drush.launcher in " . dirname(__DIR__) . ". Check your installation.\n");
      exit(1);
    }
  }

  // Check for a root option:
  //
  //   drush --root=/path
  //
  // If the site root is specified via a commandline option, then we
  // should always use the Drush stored at this root, if there is one.
  // We will first check for a "wrapper" script at the root, and then
  // we will look for a "launcher" script in vendor/bin.
  if (empty($found_script) && !empty($ROOT)) {
    $found_script = find_wrapper_or_launcher($ROOT);
    if (!empty($found_script)) {
      chdir($ROOT);
    }
  }

  // If there is a .drush-use file, then its contents will
  // contain the path to the Drush to use.
  if (empty($found_script)) {
    if (is_file(".drush-use")) {
      $found_script = trim(file_get_contents(".drush-use"));
    }
  }

  // Look for a 'drush' wrapper or launcher at the cwd,
  // and in each of the directories above the cwd.  If
  // we find one, use it.
  if (empty($found_script)) {
    $c = getcwd();
    // Windows can give us lots of different strings to represent the root
    // directory as it often includes the drive letter. If we get the same
    // result from dirname() twice in a row, then we know we're at the root.
    $last = '';
    while (!empty($c) && ($c != $last)) {
      $found_script = find_wrapper_or_launcher($c);
      if ($found_script) {
        chdir($c);
        break;
      }
      $last = $c;
      $c = dirname($c);
    }
  }

  if (!empty($found_script)) {
    $found_script = realpath($found_script);

    // Guard against errors:  if we have found a "drush" script
    // (that is, theoretically a drush wrapper script), and
    // there is a "drush.launcher" script in the same directory,
    // then we will skip the "drush" script and use the drush launcher
    // instead.  This is because drush "wrapper" scripts should
    // only ever exist at the root of a site, and there should
    // never be a drush "launcher" at the root of a site.
    // Therefore, if we find a "drush.launcher" next to a script
    // called "drush", we have probably found a Drush install directory,
    // not a site root.  Adjust appropriately.  Note that this
    // also catches the case where a drush "finder" script finds itself.
    if (is_file(dirname($found_script) . "{$sep}drush.launcher")) {
      $found_script = dirname($found_script) . "{$sep}drush.launcher";
    }
  }

  // Didn't find any site-local Drush, or @use'd Drush.
  // Skip the Bash niceties of the launcher and proceed to drush_main() in either case:
  // - No script was found and we are running a Phar
  // - The found script *is* the Phar https://github.com/drush-ops/drush/pull/2246.
  $phar_path = class_exists('Phar') ? Phar::running(FALSE) : '';
  if ((empty($found_script) && $phar_path) || !empty($found_script) && $found_script == $phar_path) {
    drush_run_main($DEBUG, $sep, "Phar detected. Proceeding to drush_main().");
  }

  // Didn't find any site-local Drush, or @use'd Drush, or Phar.
  // There should be a drush.launcher in same directory as this script.
  if (empty($found_script)) {
    $found_script = dirname(__DIR__) . "{$sep}drush.launcher";
  }

  if (drush_is_windows()) {
    // Sometimes we found launcher in /bin, and sometimes not. Adjust accordingly.
    if (strpos($found_script, 'bin')) {
      $found_script = dirname($found_script). $sep. 'drush.php.bat';
    }
    else {
      array_unshift($arguments, dirname($found_script). $sep. 'drush.php');
      $found_script = 'php';
    }
  }

  // Always use pcntl_exec if it exists.
  $use_pcntl_exec = function_exists("pcntl_exec") && (strpos(ini_get('disable_functions'), 'pcntl_exec') === FALSE);

  // If we have posix_getppid, then pass in the shell pid so
  // that 'site-set' et. al. can work correctly.
  if (function_exists('posix_getppid')) {
    putenv("DRUSH_SHELL_PID=" . posix_getppid());
  }

  // Set an environment variable indicating which script
  // the Drush finder found. If we end up re-entrantly calling
  // another Drush finder, then we will skip searching for
  // a site-local Drush, and always use the drush.launcher
  // found previously.  This environment variable typically should
  // not be set by clients.
  putenv("DRUSH_FINDER_SCRIPT=$found_script");

  // Emit a message in debug mode advertising the location of the
  // script we found.
  if ($DEBUG) {
    $launch_method = $use_pcntl_exec ? 'pcntl_exec' : 'proc_open';
    fwrite(STDERR, "Using the Drush script found at $found_script using $launch_method\n");
  }

  if ($use_pcntl_exec) {
    // Get the current environment for pnctl_exec.
    $env = drush_env();

    // Launch the new script in the same process.
    // If the launch succeeds, then it will not return.
    $error = pcntl_exec($found_script, $arguments, $env);
    if (!$error) {
      $errno = pcntl_get_last_error();
      $strerror = pcntl_strerror($errno);
      fwrite(STDERR, "Error has occurred executing the Drush script found at $found_script\n");
      fwrite(STDERR, "(errno {$errno}) $strerror\n");
    }
    exit(1);
  }
  else {
    $escaped_args = array_map(function($item) { return drush_escapeshellarg($item); }, $arguments);
    // Double quotes around $found_script as it can contain spaces.
    $cmd = drush_escapeshellarg($found_script). ' '. implode(' ', $escaped_args);
    if (drush_is_windows()) {
      // Windows requires double quotes around whole command.
      // @see https://bugs.php.net/bug.php?id=49139
      // @see https://bugs.php.net/bug.php?id=60181
      $cmd = '"'. $cmd. '"';
    }
    $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes, $cwd);
    $proc_status = proc_get_status($process);
    $exit_code = proc_close($process);
    exit($proc_status["running"] ? $exit_code : $proc_status["exitcode"] );
  }
}

/**
 * Run drush_main() and then exit. Used when we cannot hand over execution to
 * the launcher.
 *
 * @param bool $DEBUG
 *   Are we in debug mode
 * @param string $sep
 *   Directory separator
 * @param string $msg
 *   Debug message to log before running drush_main()
 */
function drush_run_main($DEBUG, $sep, $msg) {
// Emit a message in debug mode advertising how we proceeded.
  if ($DEBUG) {
    fwrite(STDERR, $msg. "\n");
  }
  require __DIR__ . "{$sep}preflight.inc";
  exit(drush_main());
}
<?php

namespace Drush\Boot;

use Drush\Log\LogLevel;

abstract class BaseBoot implements Boot {

  function __construct() {
  }

  function valid_root($path) {
  }

  function get_version($root) {
  }

  function command_defaults() {
  }

  function enforce_requirement(&$command) {
    drush_enforce_requirement_bootstrap_phase($command);
    drush_enforce_requirement_core($command);
    drush_enforce_requirement_drush_dependencies($command);
  }

  function report_command_error($command) {
    // Set errors related to this command.
    $args = implode(' ', drush_get_arguments());
    if (isset($command) && is_array($command)) {
      foreach ($command['bootstrap_errors'] as $key => $error) {
        drush_set_error($key, $error);
      }
      drush_set_error('DRUSH_COMMAND_NOT_EXECUTABLE', dt("The drush command '!args' could not be executed.", array('!args' => $args)));
    }
    elseif (!empty($args)) {
      drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!args' could not be found.  Run `drush cache-clear drush` to clear the commandfile cache if you have installed new extensions.", array('!args' => $args)));
    }
    // Set errors that occurred in the bootstrap phases.
    $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array());
    foreach ($errors as $code => $message) {
      drush_set_error($code, $message);
    }
  }

  function bootstrap_and_dispatch() {
    $phases = $this->bootstrap_init_phases();

    $return = '';
    $command_found = FALSE;
    _drush_bootstrap_output_prepare();
    foreach ($phases as $phase) {
      if (drush_bootstrap_to_phase($phase)) {
        $command = drush_parse_command();
        if (is_array($command)) {
          $command += $this->command_defaults();
          // Insure that we have bootstrapped to a high enough
          // phase for the command prior to enforcing requirements.
          $bootstrap_result = drush_bootstrap_to_phase($command['bootstrap']);
          $this->enforce_requirement($command);

          if ($bootstrap_result && empty($command['bootstrap_errors'])) {
            drush_log(dt("Found command: !command (commandfile=!commandfile)", array('!command' => $command['command'], '!commandfile' => $command['commandfile'])), LogLevel::BOOTSTRAP);

            $command_found = TRUE;
            // Dispatch the command(s).
            $return = drush_dispatch($command);

            // Prevent a '1' at the end of the output.
            if ($return === TRUE) {
              $return = '';
            }

            if (drush_get_context('DRUSH_DEBUG') && !drush_get_context('DRUSH_QUIET')) {
              // @todo Create version independant wrapper around Drupal timers. Use it.
              drush_print_timers();
            }
            break;
          }
        }
      }
      else {
        break;
      }
    }

    if (!$command_found) {
      // If we reach this point, command doesn't fit requirements or we have not
      // found either a valid or matching command.
      $this->report_command_error($command);
    }
    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function terminate() {
  }
}
<?php

namespace Drush\Boot;

/**
 * Defines the interface for a Boot classes.  Any CMS that wishes
 * to work with Drush should extend BaseBoot.  If the CMS has a
 * Drupal-Compatibility layer, then it should extend DrupalBoot.
 *
 * @todo Doc these methods.
 */
interface Boot {
  /**
   * This function determines if the specified path points to
   * the root directory of a CMS that can be bootstrapped by
   * the specific subclass that implements it.
   *
   * These functions should be written such that one and only
   * one class will return TRUE for any given $path.
   *
   * @param $path to a directory to test
   *
   * @return TRUE if $path is a valid root directory
   */
  function valid_root($path);


  /**
   * Given a site root directory, determine the exact version of the software.
   *
   * @param string $root
   *   The full path to the site installation, with no trailing slash.
   * @return string|NULL
   *   The version string for the current version of the software, e.g. 8.1.3
   */
  function get_version($root);

  /**
   * Main entrypoint to bootstrap the selected CMS and
   * execute the selected command.
   *
   * The implementation provided in BaseBoot should be
   * sufficient; this method usually will not need to
   * be overridden.
   */
  function bootstrap_and_dispatch();

  /**
   * Returns an array that determines what bootstrap phases
   * are necessary to bootstrap this CMS.  This array
   * should map from a numeric phase to the name of a method
   * (string) in the Boot class that handles the bootstrap
   * phase.
   *
   * @see \Drush\Boot\DrupalBoot::bootstrap_phases()
   *
   * @return array of PHASE index => method name.
   */
  function bootstrap_phases();

  /**
   * List of bootstrap phases where Drush should stop and look for commandfiles.
   *
   * This allows us to bootstrap to a minimum neccesary to find commands.
   *
   * Once a command is found, Drush will ensure a bootstrap to the phase
   * declared by the command.
   *
   * @return array of PHASE indexes.
   */
  function bootstrap_init_phases();

  /**
   * Return an array of default values that should be added
   * to every command (e.g. values needed in enforce_requirements(),
   * etc.)
   */
  function command_defaults();

  /**
   * Called by Drush when a command is selected, but
   * before it runs.  This gives the Boot class an
   * opportunity to determine if any minimum
   * requirements (e.g. minimum Drupal version) declared
   * in the command have been met.
   *
   * @return TRUE if command is valid. $command['bootstrap_errors']
   * should be populated with an array of error messages if
   * the command is not valid.
   */
  function enforce_requirement(&$command);

  /**
   * Called by Drush if a command is not found, or if the
   * command was found, but did not meet requirements.
   *
   * The implementation in BaseBoot should be sufficient
   * for most cases, so this method typically will not need
   * to be overridden.
   */
  function report_command_error($command);

  /**
   * This method is called during the shutdown of drush.
   *
   * @return void
   */
  public function terminate();
}
<?php

namespace Drush\Boot;

use Drush\Log\LogLevel;

abstract class DrupalBoot extends BaseBoot {

  function __construct() {
  }

  function valid_root($path) {
  }

  function get_version($drupal_root) {
  }

  function get_profile() {
  }

  function conf_path($require_settings = TRUE, $reset = FALSE) {
    return conf_path($require_settings = TRUE, $reset = FALSE);
  }

  /**
   * Bootstrap phases used with Drupal:
   *
   *     DRUSH_BOOTSTRAP_DRUSH                = Only Drush.
   *     DRUSH_BOOTSTRAP_DRUPAL_ROOT          = Find a valid Drupal root.
   *     DRUSH_BOOTSTRAP_DRUPAL_SITE          = Find a valid Drupal site.
   *     DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION = Load the site's settings.
   *     DRUSH_BOOTSTRAP_DRUPAL_DATABASE      = Initialize the database.
   *     DRUSH_BOOTSTRAP_DRUPAL_FULL          = Initialize Drupal fully.
   *     DRUSH_BOOTSTRAP_DRUPAL_LOGIN         = Log into Drupal with a valid user.
   *
   * The value is the name of the method of the Boot class to
   * execute when bootstrapping.  Prior to bootstrapping, a "validate"
   * method is called, if defined.  The validate method name is the
   * bootstrap method name with "_validate" appended.
   */
  function bootstrap_phases() {
    return array(
      DRUSH_BOOTSTRAP_DRUSH                  => 'bootstrap_drush',
      DRUSH_BOOTSTRAP_DRUPAL_ROOT            => 'bootstrap_drupal_root',
      DRUSH_BOOTSTRAP_DRUPAL_SITE            => 'bootstrap_drupal_site',
      DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION   => 'bootstrap_drupal_configuration',
      DRUSH_BOOTSTRAP_DRUPAL_DATABASE        => 'bootstrap_drupal_database',
      DRUSH_BOOTSTRAP_DRUPAL_FULL            => 'bootstrap_drupal_full',
      DRUSH_BOOTSTRAP_DRUPAL_LOGIN           => 'bootstrap_drupal_login');
  }

  /**
   * List of bootstrap phases where Drush should stop and look for commandfiles.
   *
   * For Drupal, we try at these bootstrap phases:
   *
   *   - Drush preflight: to find commandfiles in any system location,
   *     out of a Drupal installation.
   *   - Drupal root: to find commandfiles based on Drupal core version.
   *   - Drupal full: to find commandfiles defined within a Drupal directory.
   *
   * Once a command is found, Drush will ensure a bootstrap to the phase
   * declared by the command.
   *
   * @return array of PHASE indexes.
   */
  function bootstrap_init_phases() {
    return array(DRUSH_BOOTSTRAP_DRUSH, DRUSH_BOOTSTRAP_DRUPAL_ROOT, DRUSH_BOOTSTRAP_DRUPAL_FULL);
  }

  function enforce_requirement(&$command) {
    parent::enforce_requirement($command);
    $this->drush_enforce_requirement_drupal_dependencies($command);
  }

  function report_command_error($command) {
    // If we reach this point, command doesn't fit requirements or we have not
    // found either a valid or matching command.

    // If no command was found check if it belongs to a disabled module.
    if (!$command) {
      $command = $this->drush_command_belongs_to_disabled_module();
    }
    parent::report_command_error($command);
  }

  function command_defaults() {
    return array(
      'drupal dependencies' => array(),
      'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
    );
  }

  /**
   * @return array of strings - paths to directories where contrib
   * modules can be found
   */
  abstract function contrib_modules_paths();

  /**
   * @return array of strings - paths to directories where contrib
   * themes can be found
   */
  abstract function contrib_themes_paths();

  function commandfile_searchpaths($phase, $phase_max = FALSE) {
    if (!$phase_max) {
      $phase_max = $phase;
    }

    $searchpath = array();
    switch ($phase) {
      case DRUSH_BOOTSTRAP_DRUPAL_ROOT:
        $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
        $searchpath[] = $drupal_root . '/../drush';
        $searchpath[] = $drupal_root . '/drush';
        $searchpath[] = $drupal_root . '/sites/all/drush';
        break;
      case DRUSH_BOOTSTRAP_DRUPAL_SITE:
        // If we are going to stop bootstrapping at the site, then
        // we will quickly add all commandfiles that we can find for
        // any extension associated with the site, whether it is enabled
        // or not.  If we are, however, going to continue on to bootstrap
        // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will
        // instead wait for that phase, which will more carefully add
        // only those Drush commandfiles that are associated with
        // enabled modules.
        if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
          $searchpath = array_merge($searchpath, $this->contrib_modules_paths());

          // Adding commandfiles located within /profiles. Try to limit to one profile for speed. Note
          // that Drupal allows enabling modules from a non-active profile so this logic is kinda dodgy.
          $cid = drush_cid_install_profile();
          if ($cached = drush_cache_get($cid)) {
            $profile = $cached->data;
            $searchpath[] = "profiles/$profile/modules";
            $searchpath[] = "profiles/$profile/themes";
          }
          else {
            // If install_profile is not available, scan all profiles.
            $searchpath[] = "profiles";
            $searchpath[] = "sites/all/profiles";
          }

          $searchpath = array_merge($searchpath, $this->contrib_themes_paths());
        }
        break;
      case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION:
        // Nothing to do here anymore. Left for documentation.
        break;
      case DRUSH_BOOTSTRAP_DRUPAL_FULL:
        // Add enabled module paths, excluding the install profile. Since we are bootstrapped,
        // we can use the Drupal API.
        $ignored_modules = drush_get_option_list('ignored-modules', array());
        $cid = drush_cid_install_profile();
        if ($cached = drush_cache_get($cid)) {
          $ignored_modules[] = $cached->data;
        }
        foreach (array_diff(drush_module_list(), $ignored_modules) as $module) {
          $filepath = drupal_get_path('module', $module);
          if ($filepath && $filepath != '/') {
            $searchpath[] = $filepath;
          }
        }

        // Check all enabled themes including non-default and non-admin.
        foreach (drush_theme_list() as $key => $value) {
          $searchpath[] = drupal_get_path('theme', $key);
        }
        break;
    }

    return $searchpath;
  }

  /**
   * Check if the given command belongs to a disabled module.
   *
   * @return array
   *   Array with a command-like bootstrap error or FALSE if Drupal was not
   *   bootstrapped fully or the command does not belong to a disabled module.
   */
  function drush_command_belongs_to_disabled_module() {
    if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
      _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE, DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
      drush_get_commands(TRUE);
      $commands = drush_get_commands();
      $arguments = drush_get_arguments();
      $command_name = array_shift($arguments);
      if (isset($commands[$command_name])) {
        // We found it. Load its module name and set an error.
        if (is_array($commands[$command_name]['drupal dependencies']) && count($commands[$command_name]['drupal dependencies'])) {
          $modules = implode(', ', $commands[$command_name]['drupal dependencies']);
        }
        else {
          // The command does not define Drupal dependencies. Derive them.
          $command_files = commandfiles_cache()->get();
          $command_path = $commands[$command_name]['path'] . DIRECTORY_SEPARATOR . $commands[$command_name]['commandfile'] . '.drush.inc';
          $modules = array_search($command_path, $command_files);
        }
        return array(
          'bootstrap_errors' => array(
            'DRUSH_COMMAND_DEPENDENCY_ERROR' => dt('Command !command needs the following extension(s) enabled to run: !dependencies.', array(
              '!command' => $command_name,
              '!dependencies' => $modules,
            )),
          ),
        );
      }
    }

    return FALSE;
  }

  /**
   * Check that a command has its declared dependencies available or have no
   * dependencies.
   *
   * @param $command
   *   Command to check. Any errors  will be added to the 'bootstrap_errors' element.
   *
   * @return
   *   TRUE if command is valid.
   */
  function drush_enforce_requirement_drupal_dependencies(&$command) {
    // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will
    // allow the requirements to pass if we have not successfully
    // bootstrapped Drupal.  The combination of DRUSH_BOOTSTRAP_MAX
    // and 'drupal dependencies' indicates that the drush command
    // will use the dependent modules only if they are available.
    if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) {
      // If we have not bootstrapped, then let the dependencies pass;
      // if we have bootstrapped, then enforce them.
      if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
        return TRUE;
      }
    }
    // If there are no drupal dependencies, then do nothing
    if (!empty($command['drupal dependencies'])) {
      foreach ($command['drupal dependencies'] as $dependency) {
        drush_include_engine('drupal', 'environment');
        if(!drush_module_exists($dependency)) {
          $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following modules installed/enabled to run: !dependencies.', array('!command' => $command['command'], '!dependencies' => implode(', ', $command['drupal dependencies'])));
          return FALSE;
        }
      }
    }
    return TRUE;
  }

  /**
   * Validate the DRUSH_BOOTSTRAP_DRUPAL_ROOT phase.
   *
   * In this function, we will check if a valid Drupal directory is available.
   * We also determine the value that will be stored in the DRUSH_DRUPAL_ROOT
   * context and DRUPAL_ROOT constant if it is considered a valid option.
   */
  function bootstrap_drupal_root_validate() {
    $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');

    if (empty($drupal_root)) {
      return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found"));
    }
    if (!$signature = drush_valid_root($drupal_root)) {
      return drush_bootstrap_error('DRUSH_INVALID_DRUPAL_ROOT', dt("The directory !drupal_root does not contain a valid Drupal installation", array('!drupal_root' => $drupal_root)));
    }

    $version = drush_drupal_version($drupal_root);
    $major_version = drush_drupal_major_version($drupal_root);
    if ($major_version <= 5) {
      return drush_set_error('DRUSH_DRUPAL_VERSION_UNSUPPORTED', dt('Drush !drush_version does not support Drupal !major_version.', array('!drush_version' => DRUSH_VERSION, '!major_version' => $major_version)));
    }

    drush_bootstrap_value('drupal_root', $drupal_root);
    define('DRUSH_DRUPAL_SIGNATURE', $signature);

    return TRUE;
  }

  /**
   * Bootstrap Drush with a valid Drupal Directory.
   *
   * In this function, the pwd will be moved to the root
   * of the Drupal installation.
   *
   * The DRUSH_DRUPAL_ROOT context, DRUSH_DRUPAL_CORE context, DRUPAL_ROOT, and the
   * DRUSH_DRUPAL_CORE constants are populated from the value that we determined during
   * the validation phase.
   *
   * We also now load the drushrc.php for this specific Drupal site.
   * We can now include files from the Drupal Tree, and figure
   * out more context about the platform, such as the version of Drupal.
   */
  function bootstrap_drupal_root() {
    // Load the config options from Drupal's /drush and sites/all/drush directories.
    drush_load_config('drupal');

    $drupal_root = drush_set_context('DRUSH_DRUPAL_ROOT', drush_bootstrap_value('drupal_root'));
    chdir($drupal_root);
    $version = drush_drupal_version();
    $major_version = drush_drupal_major_version();

    $core = $this->bootstrap_drupal_core($drupal_root);

    // DRUSH_DRUPAL_CORE should point to the /core folder in Drupal 8+ or to DRUPAL_ROOT
    // in prior versions.
    drush_set_context('DRUSH_DRUPAL_CORE', $core);
    define('DRUSH_DRUPAL_CORE', $core);

    _drush_preflight_global_options();

    drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root)), LogLevel::BOOTSTRAP);
  }

  /**
   * VALIDATE the DRUSH_BOOTSTRAP_DRUPAL_SITE phase.
   *
   * In this function we determine the URL used for the command,
   * and check for a valid settings.php file.
   *
   * To do this, we need to set up the $_SERVER environment variable,
   * to allow us to use conf_path to determine what Drupal will load
   * as a configuration file.
   */
  function bootstrap_drupal_site_validate() {
    // Define the selected conf path as soon as we have identified that
    // we have selected a Drupal site.  Drush used to set this context
    // during the drush_bootstrap_drush phase.
    $drush_uri = _drush_bootstrap_selected_uri();
    drush_set_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH', drush_conf_path($drush_uri));

    $this->bootstrap_drupal_site_setup_server_global($drush_uri);
    return $this->bootstrap_drupal_site_validate_settings_present();
  }

  /**
   * Set up the $_SERVER globals so that Drupal will see the same values
   * that it does when serving pages via the web server.
   */
  function bootstrap_drupal_site_setup_server_global($drush_uri) {
    // Fake the necessary HTTP headers that Drupal needs:
    if ($drush_uri) {
      $drupal_base_url = parse_url($drush_uri);
      // If there's no url scheme set, add http:// and re-parse the url
      // so the host and path values are set accurately.
      if (!array_key_exists('scheme', $drupal_base_url)) {
        $drush_uri = 'http://' . $drush_uri;
        $drupal_base_url = parse_url($drush_uri);
      }
      // Fill in defaults.
      $drupal_base_url += array(
        'path' => '',
        'host' => NULL,
        'port' => NULL,
      );
      $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];

      if ($drupal_base_url['scheme'] == 'https') {
        $_SERVER['HTTPS'] = 'on';
      }

      if ($drupal_base_url['port']) {
        $_SERVER['HTTP_HOST'] .= ':' . $drupal_base_url['port'];
      }
      $_SERVER['SERVER_PORT'] = $drupal_base_url['port'];

      $_SERVER['REQUEST_URI'] = $drupal_base_url['path'] . '/';
    }
    else {
      $_SERVER['HTTP_HOST'] = 'default';
      $_SERVER['REQUEST_URI'] = '/';
    }

    $_SERVER['PHP_SELF'] = $_SERVER['REQUEST_URI'] . 'index.php';
    $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
    $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
    $_SERVER['REQUEST_METHOD']  = 'GET';

    $_SERVER['SERVER_SOFTWARE'] = NULL;
    $_SERVER['HTTP_USER_AGENT'] = NULL;
    $_SERVER['SCRIPT_FILENAME'] = DRUPAL_ROOT . '/index.php';
  }

  /**
   * Validate that the Drupal site has all of the settings that it
   * needs to operated.
   */
  function bootstrap_drupal_site_validate_settings_present() {
    $site = drush_bootstrap_value('site', $_SERVER['HTTP_HOST']);

    $conf_path = drush_bootstrap_value('conf_path', $this->conf_path(TRUE, TRUE));
    $conf_file = "$conf_path/settings.php";
    if (!file_exists($conf_file)) {
      return drush_bootstrap_error('DRUPAL_SITE_SETTINGS_NOT_FOUND', dt("Could not find a Drupal settings.php file at !file.",
         array('!file' => $conf_file)));
    }

    return TRUE;
  }

  /**
   * Called by bootstrap_drupal_site to do the main work
   * of the drush drupal site bootstrap.
   */
  function bootstrap_do_drupal_site() {
    $drush_uri = drush_get_context('DRUSH_SELECTED_URI');
    drush_set_context('DRUSH_URI', $drush_uri);
    $site = drush_set_context('DRUSH_DRUPAL_SITE', drush_bootstrap_value('site'));
    $conf_path = drush_set_context('DRUSH_DRUPAL_SITE_ROOT', drush_bootstrap_value('conf_path'));

    drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path)), LogLevel::BOOTSTRAP);

    _drush_preflight_global_options();
  }

  /**
   * Initialize a site on the Drupal root.
   *
   * We now set various contexts that we determined and confirmed to be valid.
   * Additionally we load an optional drushrc.php file in the site directory.
   */
  function bootstrap_drupal_site() {
    drush_load_config('site');
    $this->bootstrap_do_drupal_site();
  }

  /**
   * Initialize and load the Drupal configuration files.
   *
   * We process and store a normalized set of database credentials
   * from the loaded configuration file, so we can validate them
   * and access them easily in the future.
   *
   * Also override Drupal variables as per --variables option.
   */
  function bootstrap_drupal_configuration() {
    global $conf;

    $override = array(
      'dev_query' => FALSE, // Force Drupal6 not to store queries since we are not outputting them.
      'cron_safe_threshold' => 0, // Don't run poormanscron during Drush request (D7+).
    );

    $current_override = drush_get_option_list('variables');
    foreach ($current_override as $name => $value) {
      if (is_numeric($name) && (strpos($value, '=') !== FALSE)) {
        list($name, $value) = explode('=', $value, 2);
      }
      $override[$name] = $value;
    }
    $conf = is_array($conf) ? array_merge($conf, $override) : $conf;
  }

  /**
   * Validate the DRUSH_BOOTSTRAP_DRUPAL_DATABASE phase
   *
   * Attempt to make a working database connection using the
   * database credentials that were loaded during the previous
   * phase.
   */
  function bootstrap_drupal_database_validate() {
    if (!drush_valid_db_credentials()) {
      return drush_bootstrap_error('DRUSH_DRUPAL_DB_ERROR');
    }
    return TRUE;
  }

  /**
   * Test to see if the Drupal database has a specified
   * table or tables.
   *
   * This is a bootstrap helper function designed to be called
   * from the bootstrap_drupal_database_validate() methods of
   * derived DrupalBoot classes.  If a database exists, but is
   * empty, then the Drupal database bootstrap will fail.  To
   * prevent this situation, we test for some table that is needed
   * in an ordinary bootstrap, and return FALSE from the validate
   * function if it does not exist, so that we do not attempt to
   * start the database bootstrap.
   *
   * Note that we must manually do our own prefix testing here,
   * because the existing wrappers we have for handling prefixes
   * depend on bootstrapping to the "database" phase, and therefore
   * are not available to validate this same phase.
   *
   * @param $required_tables
   *   Array of table names, or string with one table name
   *
   * @return TRUE if all tables in input parameter exist in
   *   the database.
   */
  function bootstrap_drupal_database_has_table($required_tables) {
    try {
      $sql = drush_sql_get_class();
      $spec = $sql->db_spec();
      $prefix = isset($spec['prefix']) ? $spec['prefix'] : NULL;
      if (!is_array($prefix)) {
        $prefix = array('default' => $prefix);
      }
      $tables = $sql->listTables();
      foreach ((array)$required_tables as $required_table) {
        $prefix_key = array_key_exists($required_table, $prefix) ? $required_table : 'default';
        if (!in_array($prefix[$prefix_key] . $required_table, $tables)) {
          return FALSE;
        }
      }
    }
    catch (Exception $e) {
      // Usually the checks above should return a result without
      // throwing an exception, but we'll catch any that are
      // thrown just in case.
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Boostrap the Drupal database.
   */
  function bootstrap_drupal_database() {
    // We presume that our derived classes will connect and then
    // either fail, or call us via parent::
    drush_log(dt("Successfully connected to the Drupal database."), LogLevel::BOOTSTRAP);
  }

  /**
   * Attempt to load the full Drupal system.
   */
  function bootstrap_drupal_full() {
    drush_include_engine('drupal', 'environment');

    $this->add_logger();

    // Write correct install_profile to cache as needed. Used by _drush_find_commandfiles().
    $cid = drush_cid_install_profile();
    $install_profile = $this->get_profile();
    if ($cached_install_profile = drush_cache_get($cid)) {
      // We have a cached profile. Check it for correctness and save new value if needed.
      if ($cached_install_profile->data != $install_profile) {
        drush_cache_set($cid, $install_profile);
      }
    }
    else {
      // No cached entry so write to cache.
      drush_cache_set($cid, $install_profile);
    }

    _drush_log_drupal_messages();
  }

  /**
   * Log into the bootstrapped Drupal site with a specific
   * username or user id.
   */
  function bootstrap_drupal_login() {
    $uid_or_name = drush_set_context('DRUSH_USER', drush_get_option('user', 0));
    $userversion = drush_user_get_class();
    if (!$account = $userversion->load_by_uid($uid_or_name)) {
      if (!$account = $userversion->load_by_name($uid_or_name)) {
        if (is_numeric($uid_or_name)) {
          $message = dt('Could not login with user ID !user.', array('!user' => $uid_or_name));
          if ($uid_or_name === 0) {
            $message .= ' ' . dt('This is typically caused by importing a MySQL database dump from a faulty tool which re-numbered the anonymous user ID in the users table. See !link for help recovering from this situation.', array('!link' => 'http://drupal.org/node/1029506'));
          }
        }
        else {
          $message = dt('Could not login with user account `!user\'.', array('!user' => $uid_or_name));
        }
        return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message);
      }
    }
    $userversion->setCurrentUser($account);
    _drush_log_drupal_messages();
  }

}
<?php

namespace Drush\Boot;

class DrupalBoot6 extends DrupalBoot {

  function valid_root($path) {
    if (!empty($path) && is_dir($path) && file_exists($path . '/index.php')) {
      // Drupal 6 root.
      // We check for the absence of 'modules/field/field.module' to differentiate this from a D7 site.
      // n.b. we want D5 and earlier to match here, if possible, so that we can print a 'not supported'
      // error durring bootstrap.  If someone later adds a commandfile that adds a boot class for
      // Drupal 5, it will be tested first, so we shouldn't get here.
      $candidate = 'includes/common.inc';
      if (file_exists($path . '/' . $candidate) && file_exists($path . '/misc/drupal.js') && !file_exists($path . '/modules/field/field.module')) {
        return $candidate;
      }
    }
  }

  function get_version($drupal_root) {
    $path = $drupal_root . '/modules/system/system.module';
    if (is_file($path)) {
      require_once $path;
      if (defined('VERSION')) {
        return VERSION;
      }
    }
  }

  function get_profile() {
    return variable_get('install_profile', 'standard');
  }

  function add_logger() {
    // If needed, prod module_implements() to recognize our system_watchdog() implementation.
    $dogs = drush_module_implements('watchdog');
    if (!in_array('system', $dogs)) {
      // Note that this resets module_implements cache.
      drush_module_implements('watchdog', FALSE, TRUE);
    }
  }

  function contrib_modules_paths() {
    return array(
      $this->conf_path() . '/modules',
      'sites/all/modules',
    );
  }

  function contrib_themes_paths() {
    return array(
      $this->conf_path() . '/themes',
      'sites/all/themes',
    );
  }

  function bootstrap_drupal_core($drupal_root) {
    define('DRUPAL_ROOT', $drupal_root);
    require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
    $core = DRUPAL_ROOT;

    return $core;
  }

  function bootstrap_drupal_database_validate() {
    return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('cache');
  }

  function bootstrap_drupal_database() {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
    parent::bootstrap_drupal_database();
  }

  function bootstrap_drupal_configuration() {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);

    parent::bootstrap_drupal_configuration();
  }

  function bootstrap_drupal_full() {
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_start();
    }
    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_end_clean();
    }

    // Unset drupal error handler and restore drush's one.
    restore_error_handler();

    parent::bootstrap_drupal_full();
  }
}
<?php

namespace Drush\Boot;

class DrupalBoot7 extends DrupalBoot {

  function valid_root($path) {
    if (!empty($path) && is_dir($path) && file_exists($path . '/index.php')) {
      // Drupal 7 root.
      // We check for the presence of 'modules/field/field.module' to differentiate this from a D6 site
      $candidate = 'includes/common.inc';
      if (file_exists($path . '/' . $candidate) && file_exists($path . '/misc/drupal.js') && file_exists($path . '/modules/field/field.module')) {
        return $candidate;
      }
    }
  }

  function get_version($drupal_root) {
    $path = $drupal_root . '/includes/bootstrap.inc';
    if (is_file($path)) {
      require_once $path;
      if (defined('VERSION')) {
        return VERSION;
      }
    }
  }

  function get_profile() {
    return drupal_get_profile();
  }

  function add_logger() {
    // If needed, prod module_implements() to recognize our system_watchdog() implementation.
    $dogs = drush_module_implements('watchdog');
    if (!in_array('system', $dogs)) {
      // Note that we must never clear the module_implements() cache because
      // that would trigger larger cache rebuilds with system_cache_tables on
      // every drush invocation. Instead we inject our system_watchdog()
      // implementation direclty into the static cache.
      $implementations = &drupal_static('module_implements');
      $implementations['watchdog']['system'] = FALSE;
      $verified_implementations = &drupal_static('module_implements:verified');
      $verified_implementations['watchdog'] = TRUE;
    }
  }

  function contrib_modules_paths() {
    return array(
      $this->conf_path() . '/modules',
      'sites/all/modules',
    );
  }

  function contrib_themes_paths() {
    return array(
      $this->conf_path() . '/themes',
      'sites/all/themes',
    );
  }

  function bootstrap_drupal_core($drupal_root) {
    define('DRUPAL_ROOT', $drupal_root);
    require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
    $core = DRUPAL_ROOT;

    return $core;
  }

  function bootstrap_drupal_database_validate() {
    return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('blocked_ips');
  }

  function bootstrap_drupal_database() {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
    parent::bootstrap_drupal_database();
  }

  function bootstrap_drupal_configuration() {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);

    // Unset drupal error handler and restore drush's one.
    restore_error_handler();

    parent::bootstrap_drupal_configuration();
  }

  function bootstrap_drupal_full() {
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_start();
    }
    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_end_clean();
    }

    parent::bootstrap_drupal_full();
  }
}
<?php

namespace Drush\Boot;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\DrupalKernel;
use Drush\Drupal\DrupalKernel as DrushDrupalKernel;
use Drush\Drupal\DrushServiceModifier;

use Drush\Log\LogLevel;

class DrupalBoot8 extends DrupalBoot {

  /**
   * @var \Drupal\Core\DrupalKernelInterface
   */
  protected $kernel;

  /**
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $request;

  function valid_root($path) {
    if (!empty($path) && is_dir($path) && file_exists($path . '/autoload.php')) {
      // Additional check for the presence of core/composer.json to
      // grant it is not a Drupal 7 site with a base folder named "core".
      $candidate = 'core/includes/common.inc';
      if (file_exists($path . '/' . $candidate) && file_exists($path . '/core/core.services.yml')) {
        if (file_exists($path . '/core/misc/drupal.js') || file_exists($path . '/core/assets/js/drupal.js')) {
          return $candidate;
        }
      }
    }
  }

  function get_version($drupal_root) {
    // Load the autoloader so we can access the class constants.
    drush_drupal_load_autoloader($drupal_root);
    // Drush depends on bootstrap being loaded at this point.
    require_once $drupal_root .'/core/includes/bootstrap.inc';
    if (defined('\Drupal::VERSION')) {
      return \Drupal::VERSION;
    }
  }

  function get_profile() {
    return drupal_get_profile();
  }

  function conf_path($require_settings = TRUE, $reset = FALSE, Request $request = NULL) {
    if (!isset($request)) {
      if (\Drupal::hasRequest()) {
        $request = \Drupal::request();
      }
      // @todo Remove once external CLI scripts (Drush) are updated.
      else {
        $request = Request::createFromGlobals();
      }
    }
    if (\Drupal::hasService('kernel')) {
      $site_path = \Drupal::service('kernel')->getSitePath();
    }
    if (!isset($site_path) || empty($site_path)) {
      $site_path = DrupalKernel::findSitePath($request, $require_settings);
    }
    return $site_path;
  }

  function add_logger() {
    // If we're running on Drupal 8 or later, we provide a logger which will send
    // output to drush_log(). This should catch every message logged through every
    // channel.
    $container = \Drupal::getContainer();
    $parser = $container->get('logger.log_message_parser');
    $drushLogger = drush_get_context('DRUSH_LOG_CALLBACK');
    $logger = new \Drush\Log\DrushLog($parser, $drushLogger);
    $container->get('logger.factory')->addLogger($logger);
  }

  function contrib_modules_paths() {
    return array(
      $this->conf_path() . '/modules',
      'sites/all/modules',
      'modules',
    );
  }

  /**
   * @return array of strings - paths to directories where contrib
   * themes can be found
   */
  function contrib_themes_paths() {
    return array(
      $this->conf_path() . '/themes',
      'sites/all/themes',
      'themes',
    );
  }

  function bootstrap_drupal_core($drupal_root) {
    $core = DRUPAL_ROOT . '/core';

    return $core;
  }

  function bootstrap_drupal_database_validate() {
    return parent::bootstrap_drupal_database_validate() && $this->bootstrap_drupal_database_has_table('key_value');
  }

  function bootstrap_drupal_database() {
    // D8 omits this bootstrap level as nothing special needs to be done.
    parent::bootstrap_drupal_database();
  }

  function bootstrap_drupal_configuration() {
    $this->request = Request::createFromGlobals();
    $classloader = drush_drupal_load_autoloader(DRUPAL_ROOT);
    // @todo - use Request::create() and then no need to set PHP superglobals
    $kernelClass = new \ReflectionClass('\Drupal\Core\DrupalKernel');
    if ($kernelClass->hasMethod('addServiceModifier')) {
      $this->kernel = DrupalKernel::createFromRequest($this->request, $classloader, 'prod');
    }
    else {
      $this->kernel = DrushDrupalKernel::createFromRequest($this->request, $classloader, 'prod');
    }
    // @see Drush\Drupal\DrupalKernel::addServiceModifier()
    $this->kernel->addServiceModifier(new DrushServiceModifier());

    // Unset drupal error handler and restore Drush's one.
    restore_error_handler();

    // Disable automated cron if the module is enabled.
    $GLOBALS['config']['automated_cron.settings']['interval'] = 0;

    parent::bootstrap_drupal_configuration();
  }

  function bootstrap_drupal_full() {
    drush_log(dt('About to bootstrap the Drupal 8 Kernel.'), LogLevel::DEBUG);
    // TODO: do we need to do ob_start any longer?
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_start();
    }
    $this->kernel->boot();
    $this->kernel->prepareLegacyRequest($this->request);
    if (!drush_get_context('DRUSH_QUIET', FALSE)) {
      ob_end_clean();
    }
    drush_log(dt('Finished bootstraping the Drupal 8 Kernel.'), LogLevel::DEBUG);

    parent::bootstrap_drupal_full();

    // Get a list of the modules to ignore
    $ignored_modules = drush_get_option_list('ignored-modules', array());

    // We have to get the service command list from the container, because
    // it is constructed in an indirect way during the container initialization.
    // The upshot is that the list of console commands is not available
    // until after $kernel->boot() is called.
    $container = \Drupal::getContainer();
    if ($container->has('drush.service.consolecommands')) {
      $serviceCommandlist = $container->get('drush.service.consolecommands');
      foreach ($serviceCommandlist->getCommandList() as $command) {
        if (!$this->commandIgnored($command, $ignored_modules)) {
          drush_log(dt('Add a command: !name', ['!name' => $command->getName()]), LogLevel::DEBUG);
          annotationcommand_adapter_cache_module_console_commands($command);
        }
      }
    }
    // Do the same thing with the annotation commands.
    if ($container->has('drush.service.consolidationcommands')) {
      $serviceCommandlist = $container->get('drush.service.consolidationcommands');
      foreach ($serviceCommandlist->getCommandList() as $commandhandler) {
        if (!$this->commandIgnored($commandhandler, $ignored_modules)) {
          drush_log(dt('Add a commandhandler: !name', ['!name' => get_class($commandhandler)]), LogLevel::DEBUG);
          annotationcommand_adapter_cache_module_service_commands($commandhandler);
        }
      }
    }
  }

  public function commandIgnored($command, $ignored_modules) {
    if (empty($ignored_modules)) {
      return false;
    }
    $ignored_regex = '#\\\\(' . implode('|', $ignored_modules) . ')\\\\#';
    $class = new \ReflectionClass($command);
    $commandNamespace = $class->getNamespaceName();
    return preg_match($ignored_regex, $commandNamespace);
  }

  /**
   * {@inheritdoc}
   */
  public function terminate() {
    parent::terminate();

    if ($this->kernel) {
      $response = Response::create('');
      $this->kernel->terminate($this->request, $response);
    }
  }
}
<?php

namespace Drush\Boot;

/**
 * This is a do-nothing 'Boot' class that is used when there
 * is no site at --root, or when no root is specified.
 *
 * The 'empty' boot must be careful to never change state,
 * in case bootstrap code might later come along and set
 * a site (e.g. in command completion).
 */
class EmptyBoot extends BaseBoot {

  function __construct() {
  }

  function valid_root($path) {
    return FALSE;
  }

  function bootstrap_phases() {
    return array(
      DRUSH_BOOTSTRAP_DRUSH => '_drush_bootstrap_drush',
    );
  }

  function bootstrap_init_phases() {
    return array(DRUSH_BOOTSTRAP_DRUSH);
  }

  function command_defaults() {
    return array(
      // TODO: Historically, commands that do not explicitly specify
      // their bootstrap level default to DRUSH_BOOTSTRAP_DRUPAL_LOGIN.
      // This isn't right any more, but we can't just change this to
      // DRUSH_BOOTSTRAP_DRUSH, or we will start running commands that
      // needed a full bootstrap with no bootstrap, and that won't work.
      // For now, we will continue to force this to 'login'.  Any command
      // that does not declare 'bootstrap' is declaring that it is a Drupal
      // command.
      'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
    );
  }
}
<?php

/**
 * @file
 * Definition of Drush\Cache\CacheInterface.
 */

namespace Drush\Cache;

/**
 * Interface for cache implementations.
 *
 * All cache implementations have to implement this interface.
 * JSONCache provides the default implementation, which can be
 * consulted as an example.
 *
 * To make Drush use your implementation for a certain cache bin, you have to
 * set a variable with the name of the cache bin as its key and the name of
 * your class as its value. For example, if your implementation of
 * CacheInterface was called MyCustomCache, the following line in
 * drushrc.php would make Drush use it for the 'example' bin:
 * @code
 *  $options['cache-class-example'] = 'MyCustomCache;
 * @endcode
 *
 * Additionally, you can register your cache implementation to be used by
 * default for all cache bins by setting the option 'cache-default-class' to
 * the name of your implementation of the CacheInterface, e.g.
 * @code
 *  $options['cache-default-class'] = 'MyCustomCache;
 * @endcode
 *
 * @see _drush_cache_get_object()
 * @see \Drupal\Core\Cache\CacheBackendInterface
 */
interface CacheInterface {

/**
 * Constructor.
 *
 * @param $bin
 *   The cache bin for which the object is created.
*/
function __construct($bin);

/**
 * Return data from the persistent cache.
 *
 * @param string $cid
 *   The cache ID of the data to retrieve.
 *
 * @return
 *   The cache or FALSE on failure.
 */
function get($cid);

/**
 * Return data from the persistent cache when given an array of cache IDs.
 *
 * @param array $cids
 *   An array of cache IDs for the data to retrieve. This is passed by
 *   reference, and will have the IDs successfully returned from cache
 *   removed.
 *
 * @return
 *   An array of the items successfully returned from cache indexed by cid.
 */
function getMultiple(&$cids);

/**
 * Store data in the persistent cache.
 *
 * @param string $cid
 *   The cache ID of the data to store.
 * @param array $data
 *   The data to store in the cache.
 * @param $expire
 *   One of the following values:
 *   - DRUSH_CACHE_PERMANENT: Indicates that the item should never be removed unless
 *     explicitly told to using _drush_cache_clear_all() with a cache ID.
 *   - DRUSH_CACHE_TEMPORARY: Indicates that the item should be removed at the next
 *     general cache wipe.
 *   - A Unix timestamp: Indicates that the item should be kept at least until
 *     the given time, after which it behaves like CACHE_TEMPORARY.
 */
function set($cid, $data, $expire = DRUSH_CACHE_PERMANENT);

/**
 * Expire data from the cache. If called without arguments, expirable
 * entries will be cleared from all known cache bins.
 *
 * @param string $cid
 *   If set, the cache ID to delete. Otherwise, all cache entries that can
 *   expire are deleted.
 * @param bool $wildcard
 *   If set to TRUE, the $cid is treated as a substring
 *   to match rather than a complete ID. The match is a right hand
 *   match. If '*' is given as $cid, the bin $bin will be emptied.
 */
function clear($cid = NULL, $wildcard = FALSE);

/**
 * Check if a cache bin is empty.
 *
 * A cache bin is considered empty if it does not contain any valid data for
 * any cache ID.
 *
 * @return
 *   TRUE if the cache bin specified is empty.
 */
function isEmpty();
}
<?php

/**
 * @file
 * Definition of Drush\Cache\FileCache.
 */

namespace Drush\Cache;

/**
 * Default cache implementation.
 *
 * This cache implementation uses plain text files
 * containing serialized php to store cached data. Each cache bin corresponds
 * to a directory by the same name.
 */
class FileCache implements CacheInterface {
  const EXTENSION = '.cache';
  protected $bin;

  function __construct($bin) {
    $this->bin = $bin;
    $this->directory = $this->cacheDirectory();
  }

   /**
    * Returns the cache directory for the given bin.
    *
    * @param string $bin
    */
  function cacheDirectory($bin = NULL) {
    $bin = $bin ? $bin : $this->bin;
    return drush_directory_cache($bin);
  }

  function get($cid) {
    $cids = array($cid);
    $cache = $this->getMultiple($cids);
    return reset($cache);
  }

  function getMultiple(&$cids) {
    try {
      $cache = array();
      foreach ($cids as $cid) {
        $filename = $this->getFilePath($cid);
        if (!file_exists($filename)) throw new \Exception;

        $item = $this->readFile($filename);
        if ($item) {
          $cache[$cid] = $item;
        }
      }
      $cids = array_diff($cids, array_keys($cache));
      return $cache;
    }
    catch (\Exception $e) {
      return array();
    }
  }

  /**
   * Returns the contents of the given filename unserialized.
   *
   * @param string $filename
   *   Absolute path to filename to read contents from.
   */
  function readFile($filename) {
    $item = file_get_contents($filename);
    return $item ? unserialize($item) : FALSE;
  }

  function set($cid, $data, $expire = DRUSH_CACHE_PERMANENT) {
    $created = time();

    $cache = new \stdClass;
    $cache->cid = $cid;
    $cache->data = is_object($data) ? clone $data : $data;
    $cache->created = $created;
    if ($expire == DRUSH_CACHE_TEMPORARY) {
      $cache->expire = $created + 2591999;
    }
    // Expire time is in seconds if less than 30 days, otherwise is a timestamp.
    elseif ($expire != DRUSH_CACHE_PERMANENT && $expire < 2592000) {
      $cache->expire = $created + $expire;
    }
    else {
      $cache->expire = $expire;
    }

    // Ensure the cache directory still exists, in case a backend process
    // cleared the cache after the cache was initialized.
    drush_mkdir($this->directory);

    $filename = $this->getFilePath($cid);
    return $this->writeFile($filename, $cache);
  }

  /**
   * Serializes data and write it to the given filename.
   *
   * @param string $filename
   *   Absolute path to filename to write cache data.
   * @param $cache
   *   Cache data to serialize and write to $filename.
   */
  function writeFile($filename, $cache) {
    return file_put_contents($filename, serialize($cache));
  }

  function clear($cid = NULL, $wildcard = FALSE) {
    $bin_dir = $this->cacheDirectory();
    $files = array();
    if (empty($cid)) {
      drush_delete_dir($bin_dir, TRUE);
    }
    else {
      if ($wildcard) {
        if ($cid == '*') {
          drush_delete_dir($bin_dir, TRUE);
        }
        else {
          $matches = drush_scan_directory($bin_dir, "/^$cid/", array('.', '..'));
          $files = $files + array_keys($matches);
        }
      }
      else {
        $files[] = $this->getFilePath($cid);
      }

      foreach ($files as $f) {
        if (file_exists($f)) {
          unlink($f);
        }
      }
    }
  }

  function isEmpty() {
    $files = drush_scan_directory($this->directory, "//", array('.', '..'));
    return empty($files);
  }

  /**
   * Converts a cache id to a full path.
   *
   * @param $cid
   *   The cache ID of the data to retrieve.
   *
   * @return
   *   The full path to the cache file.
   */
  protected function getFilePath($cid) {
    return $this->directory . '/' . str_replace(array(':', '\\', '/'), '.', $cid) . self::EXTENSION;
  }
}
<?php

/**
 * @file
 * Definition of Drush\Cache\JSONCache.
 */

namespace Drush\Cache;

/**
 * JSON cache storage backend.
 */
class JSONCache extends FileCache {
  const EXTENSION = '.json';

  function readFile($filename) {
    $item = file_get_contents($filename);
    return $item ? (object)drush_json_decode($item) : FALSE;
  }

  function writeFile($filename, $cache) {
    return file_put_contents($filename, drush_json_encode($cache));
  }
}
<?php

/**
 * @file
 * Definition of Drush\Command\Commandfiles.
 */

namespace Drush\Command;

/**
 * Default commandfiles implementation.
 *
 * This class manages the list of commandfiles that are active
 * in Drush for the current command invocation.
 */
class Commandfiles implements CommandfilesInterface {
  protected $cache;
  protected $deferred;

  function __construct() {
    $this->cache = array();
    $this->deferred = array();
  }

  function get() {
  	return $this->cache;
  }

  function deferred() {
  	return $this->deferred;
  }

  function sort() {
  	ksort($this->cache);
  }

  function add($commandfile) {
	  $load_command = FALSE;

	  $module = basename($commandfile);
	  $module = preg_replace('/\.*drush[0-9]*\.inc/', '', $module);
	  $module_versionless = preg_replace('/\.d([0-9]+)$/', '', $module);
	  if (!isset($this->cache[$module_versionless])) {
	    $drupal_version = '';
	    if (preg_match('/\.d([0-9]+)$/', $module, $matches)) {
	      $drupal_version = $matches[1];
	    }
	    if (empty($drupal_version)) {
	      $load_command = TRUE;
	    }
	    else {
	      if (function_exists('drush_drupal_major_version') && ($drupal_version == drush_drupal_major_version())) {
	      	$load_command = TRUE;
	      }
	      else {
		    // Signal that we should try again on
		    // the next bootstrap phase.
		    $this->deferred[$module] = $commandfile; 	
	      }
	    }
	    if ($load_command) {
	      $this->cache[$module_versionless] = $commandfile;
	      require_once $commandfile;
	      unset($this->deferred[$module]);
	    }
	  }
	  return $load_command;
  }
}
<?php

namespace Drush\Command;

interface CommandfilesInterface {
  function add($commandfile);
  function get();
  function deferred();
  function sort();
}
<?php

/**
 * @file
 * Definition of Drush\Command\DrushInputAdapter.
 */

namespace Drush\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputDefinition;

/**
 * Adapter for Symfony Console InputInterface
 *
 * This class can serve as a stand-in wherever an InputInterface
 * is needed.  It calls through to ordinary Drush procedural functions.
 * This object should not be used directly; it exists only in
 * the Drush 8.x branch.
 *
 * We use this class rather than using an ArrayInput for two reasons:
 * 1) We do not want to convert our options array back to '--option=value'
 *    or '--option value' just to have them re-parsed again.
 * 2) We do not want Symfony to attempt to validate our options or arguments
 *    for us.
 */
class DrushInputAdapter implements InputInterface
{
    protected $arguments;
    protected $options;
    protected $interactive;

    public function __construct($arguments, $options, $command = false, $interactive = true)
    {
        $this->arguments = $arguments;
        $this->options = $options;

        // If a command name is provided as a parameter, then push
        // it onto the front of the arguments list as a service
        if ($command) {
            $this->arguments = array_merge(
                [ 'command' => $command ],
                $this->arguments
            );
        }
        // Is it interactive, or is it not interactive?
        // Call drush_get_option() here if value not passed in?
        $this->interactive = $interactive;
    }

    /**
     *  {@inheritdoc}
     */
    public function getFirstArgument()
    {
        return reset($this->arguments);
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($values as $value) {
            if (array_key_exists($value, $this->options)) {
                return true;
            }
        }

        return false;
    }

    /**
     *  {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($values as $value) {
            if (array_key_exists($value, $this->options)) {
                return $this->getOption($value);
            }
        }

        return $default;
    }

    /**
     *  {@inheritdoc}
     */
    public function bind(InputDefinition $definition)
    {
        // no-op: this class exists to avoid validation
    }

    /**
     *  {@inheritdoc}
     */
    public function validate()
    {
        // no-op: this class exists to avoid validation
    }

    /**
     *  {@inheritdoc}
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     *  {@inheritdoc}
     */
    public function getArgument($name)
    {
        // TODO: better to throw if an argument that does not exist is requested?
        return isset($this->arguments[$name]) ? $this->arguments[$name] : '';
    }

    /**
     *  {@inheritdoc}
     */
    public function setArgument($name, $value)
    {
        $this->arguments[$name] = $value;
    }

    /**
     *  {@inheritdoc}
     */
    public function hasArgument($name)
    {
        return isset($this->arguments[$name]);
    }

    /**
     *  {@inheritdoc}
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     *  {@inheritdoc}
     */
    public function getOption($name)
    {
        return $this->options[$name];
    }

    /**
     *  {@inheritdoc}
     */
    public function setOption($name, $value)
    {
        $this->options[$name] = $value;
    }

    /**
     *  {@inheritdoc}
     */
    public function hasOption($name)
    {
        return isset($this->options[$name]);
    }

    /**
     *  {@inheritdoc}
     */
    public function isInteractive()
    {
        return $this->interactive;
    }

    /**
     *  {@inheritdoc}
     */
    public function setInteractive($interactive)
    {
        $this->interactive = $interactive;
    }
}
<?php

/**
 * @file
 * Definition of Drush\Command\DrushOutputAdapter.
 */

namespace Drush\Command;

use Symfony\Component\Console\Output\Output;

/**
 * Adapter for Symfony Console OutputInterface
 *
 * This class can serve as a stand-in wherever an OutputInterface
 * is needed.  It calls through to drush_print().
 * This object should not be used directly; it exists only in
 * the Drush 8.x branch.
 */
class DrushOutputAdapter extends Output {
    protected function doWrite($message, $newline)
    {
        drush_print($message, 0, null, $newline);
    }
}
<?php
namespace Drush\Command;

use Drush\Log\LogLevel;

/**
 * Keep a list of all of the service commands that we can find when the
 * Drupal Kernel is booted.
 */
class ServiceCommandlist {
    protected $commandList = [];

    public function addCommandReference($command)
    {
        drush_log(dt("add command reference"), LogLevel::DEBUG);
        $this->commandList[] = $command;
    }

    public function getCommandList()
    {
        return $this->commandList;
    }
}
<?php
namespace Drush\CommandFiles;

/**
 * @file
 *   Set up local Drush configuration.
 */

use Drush\Log\LogLevel;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;

use Consolidation\AnnotatedCommand\CommandData;

class ExampleCommandFile
{
    /**
     * Demonstrate Robo formatters.  Default format is 'table'.
     *
     * @field-labels
     *   first: I
     *   second: II
     *   third: III
     * @default-string-field second
     * @usage example:formatters --format=yaml
     * @usage example:formatters --format=csv
     * @usage example:formatters --fields=first,third
     * @usage example:formatters --fields=III,II
     * @aliases tf
     *
     * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
     */
    public function exampleTable($options = ['format' => 'table', 'fields' => ''])
    {
        $outputData = [
            'en' => [ 'first' => 'One',  'second' => 'Two',  'third' => 'Three' ],
            'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei'  ],
            'jp' => [ 'first' => 'Ichi', 'second' => 'Ni',   'third' => 'San'   ],
            'es' => [ 'first' => 'Uno',  'second' => 'Dos',  'third' => 'Tres'  ],
        ];
        return new RowsOfFields($outputData);
    }

    /**
     * Demonstrate an alter hook with an option
     *
     * @hook alter example:table
     * @option french Add a row with French numbers.
     * @usage example:formatters --french
     */
    public function alterFormatters($result, CommandData $commandData)
    {
        if ($commandData->input()->getOption('french')) {
            $result['fr'] = [ 'first' => 'Un',  'second' => 'Deux',  'third' => 'Trois'  ];
        }

        return $result;
    }
}
<?php
namespace Drush\CommandFiles\core;

use Consolidation\AnnotatedCommand\CommandData;

class DrupliconCommands {
  protected $printed = false;

  /**
   * Print druplicon as post-command output.
   *
   * @hook post-command *
   * @option druplicon Shows the druplicon as glorious ASCII art.
   */
  public function druplicon($result, CommandData $commandData) {
    // If one command does a drush_invoke to another command,
    // then this hook will be called multiple times. Only print
    // once.  (n.b. If drush_invoke_process passes along the
    // --druplicon option, then we will still get mulitple output)
    if ($this->printed) {
      return;
    }
    $this->printed = true;
    $annotationData = $commandData->annotationData();
    $commandName = $annotationData['command'];
    // For some reason, Drush help uses drush_invoke_process to call helpsingle
    if ($commandName == 'helpsingle') {
      return;
    }
    drush_log(dt('Displaying Druplicon for "!command" command.', array('!command' => $commandName)));
    if ($commandData->input()->getOption('druplicon')) {
      $misc_dir = DRUSH_BASE_PATH . '/misc';
      if (drush_get_context('DRUSH_NOCOLOR')) {
        $content = file_get_contents($misc_dir . '/druplicon-no_color.txt');
      }
      else {
        $content = file_get_contents($misc_dir . '/druplicon-color.txt');
      }
      drush_print($content);
    }
  }
}
<?php

namespace Drush\CommandFiles\Core;

class BrowseCommands {

  /**
   * Display a link to a given path or open link in a browser.
   *
   * @todo Document new @handle-remote-commands and @bootstrap annotations.
   *
   * @param string|null $path Path to open. If omitted, the site front page will be opened.
   * @option string $browser Specify a particular browser (defaults to operating system default). Use --no-browser to suppress opening a browser.
   * @option integer $redirect-port The port that the web server is redirected to (e.g. when running within a Vagrant environment).
   * @usage drush browse
   *   Open default web browser (if configured or detected) to the site front page.
   * @usage drush browse node/1
   *   Open web browser to the path node/1.
   * @usage drush @example.prod
   *   Open a browser to the web site specified in a site alias.
   * @usage drush browse --browser=firefox admin
   *   Open Firefox web browser to the path 'admin'.
   * @bootstrap DRUSH_BOOTSTRAP_NONE
   * @handle-remote-commands true
   * @complete \Drush\CommandFiles\Core\BrowseCommands::complete
   */
  public function browse($path = '', $options = ['browser' => NULL, 'redirect-port' => NULL]) {
    // Redispatch if called against a remote-host so a browser is started on the
    // the *local* machine.
    $alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
    if (drush_sitealias_is_remote_site($alias)) {
      $site_record = drush_sitealias_get_record($alias);
      $return = drush_invoke_process($site_record, 'browse', array($path), drush_redispatch_get_options(), array('integrate' => TRUE));
      if ($return['error_status']) {
        return drush_set_error('Unable to execute browse command on remote alias.');
      }
      else {
        $link = $return['object'];
      }
    }
    else {
      if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
        // Fail gracefully if unable to bootstrap Drupal. drush_bootstrap() has
        // already logged an error.
        return FALSE;
      }
      $link = drush_url($path, array('absolute' => TRUE));
    }

    drush_start_browser($link);
    return $link;
  }

  /*
   * An argument completion provider
   */
  static function complete() {
    return ['values' => ['admin', 'admin/content', 'admin/reports', 'admin/structure', 'admin/people', 'admin/modules', 'admin/config']];
  }
}<?php

namespace Drush\Commands\core;

use Drupal\Component\Utility\Random;
use Drupal\Core\Database\Database;
use Drush\Commands\DrushCommands;

/**
 * Class SanitizeCommands
 * @package Drush\Commands\core
 */
class SanitizeCommands {

  /**
   * @var bool
   *   Whether database table names should be wrapped in brackets for prefixing.
   */
  protected $wrap;

  /**
   * Sets $this->wrap to TRUE if a db-prefix is set with drush.
   */
  protected function setWrap() {
    $this->wrap = $wrap_table_name = (bool) drush_get_option('db-prefix');
  }


  /**
   * Sanitize the database by removed and obfuscating user data.
   *
   * @command sql-sanitize
   *
   * @todo "drush dependencies" array('sqlsync')
   *
   * @bootstrap DRUSH_BOOTSTRAP_NONE
   * @description Run sanitization operations on the current database.
   * @option db-prefix Enable replacement of braces in sanitize queries.
   * @option db-url A Drupal 6 style database URL. E.g.,
   *   mysql://root:pass@127.0.0.1/db
   * @option sanitize-email The pattern for test email addresses in the
   *   sanitization operation, or "no" to keep email addresses unchanged. May
   *   contain replacement patterns %uid, %mail or %name. Example value:
   *   user+%uid@localhost
   * @option sanitize-password The password to assign to all accounts in the
   *   sanitization operation, or "no" to keep passwords unchanged. Example
   *   value: password
   * @option whitelist-fields A comma delimited list of fields exempt from sanitization.
   * @aliases sqlsan
   * @usage drush sql-sanitize --sanitize-password=no
   *   Sanitize database without modifying any passwords.
   * @usage drush sql-sanitize --whitelist-fields=field_biography,field_phone_number
   *   Sanitizes database but exempts two user fields from modification.
   * @see hook_drush_sql_sync_sanitize() for adding custom sanitize routines.
   */
  public function sqlSanitize($options = [
    'db-prefix' => FALSE,
    'db-url' => '',
    'sanitize-email' => '',
    'sanitize-password' => '',
    'whitelist-fields' => '',
    ]) {
    drush_sql_bootstrap_further();
    if ($options['db-prefix']) {
      drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
    }

    // Drush itself implements this via sql_drush_sql_sync_sanitize().
    drush_command_invoke_all('drush_sql_sync_sanitize', 'default');
    $operations = drush_get_context('post-sync-ops');
    if (!empty($operations)) {
      if (!drush_get_context('DRUSH_SIMULATE')) {
        $messages = _drush_sql_get_post_sync_messages();
        if ($messages) {
          drush_print();
          drush_print($messages);
        }
      }
      $queries = array_column($operations, 'query');
      $sanitize_query = implode(" ", $queries);
    }
    if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) {
      return drush_user_abort();
    }

    if ($sanitize_query) {
      $sql = drush_sql_get_class();
      $sanitize_query = $sql->query_prefix($sanitize_query);
      $result = $sql->query($sanitize_query);
      if (!$result) {
        throw new \Exception(dt('Sanitize query failed.'));
      }
    }
  }

  /**
   * Performs database sanitization.
   *
   * @param int $major_version
   *   E.g., 6, 7, or 8.
   */
  public function doSanitize($major_version) {
    $this->setWrap();
    $this->sanitizeSessions();

    if ($major_version == 8) {
      $this->sanitizeComments();
      $this->sanitizeUserFields();
    }
  }

  /**
   * Sanitize string fields associated with the user.
   *
   * We've got to do a good bit of SQL-foo here because Drupal services are
   * not yet available.
   */
  public function sanitizeUserFields() {
    /** @var SqlBase $sql_class */
    $sql_class = drush_sql_get_class();
    $tables = $sql_class->listTables();
    $whitelist_fields = (array) explode(',', drush_get_option('whitelist-fields'));

    foreach ($tables as $table) {
      if (strpos($table, 'user__field_') === 0) {
        $field_name = substr($table, 6, strlen($table));
        if (in_array($field_name, $whitelist_fields)) {
          continue;
        }

        $output = $this->query("SELECT data FROM config WHERE name = 'field.field.user.user.$field_name';");
        $field_config = unserialize($output[0]);
        $field_type = $field_config['field_type'];
        $randomizer = new Random();

        switch ($field_type) {

          case 'email':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->name(10) . '@example.com');
            break;

          case 'string':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->name(255));
            break;

          case 'string_long':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->sentences(1));
            break;

          case 'telephone':
            $this->sanitizeTableColumn($table,  $field_name . '_value', '15555555555');
            break;

          case 'text':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->paragraphs(2));
            break;

          case 'text_long':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->paragraphs(10));
            break;

          case 'text_with_summary':
            $this->sanitizeTableColumn($table,  $field_name . '_value', $randomizer->paragraphs(2));
            $this->sanitizeTableColumn($table,  $field_name . '_summary', $randomizer->name(255));
            break;
        }
      }
    }
  }

  /**
   * Replaces all values in given table column with the specified value.
   *
   * @param string $table
   *   The database table name.
   * @param string $column
   *   The database column to be updated.
   * @param $value
   *   The new value.
   */
  public function sanitizeTableColumn($table, $column, $value) {
    $table_name_wrapped = $this->wrapTableName($table);
    $sql = "UPDATE $table_name_wrapped SET $column='$value';";
    drush_sql_register_post_sync_op($table.$column, dt("Replaces all values in $table table with the same random long string."), $sql);
  }

  /**
   * Truncates the session table.
   */
  public function sanitizeSessions() {
    // Seems quite portable (SQLite?) - http://en.wikipedia.org/wiki/Truncate_(SQL)
    $table_name = $this->wrapTableName('sessions');
    $sql_sessions = "TRUNCATE TABLE $table_name;";
    drush_sql_register_post_sync_op('sessions', dt('Truncate Drupal\'s sessions table'), $sql_sessions);
  }

  /**
   * Sanitizes comments_field_data table.
   */
  public function sanitizeComments() {

    $comments_enabled = $this->query("SHOW TABLES LIKE 'comment_field_data';");
    if (!$comments_enabled) {
      return;
    }

    $comments_table = $this->wrapTableName('comment_field_data');
    $sql_comments = "UPDATE $comments_table SET name='Anonymous', mail='', homepage='http://example.com' WHERE uid = 0;";
    drush_sql_register_post_sync_op('anon_comments', dt('Remove names and email addresses from anonymous user comments.'), $sql_comments);

    $sql_comments = "UPDATE $comments_table SET name=CONCAT('User', `uid`), mail=CONCAT('user+', `uid`, '@example.com'), homepage='http://example.com' WHERE uid <> 0;";
    drush_sql_register_post_sync_op('auth_comments', dt('Replace names and email addresses from authenticated user comments.'), $sql_comments);
  }

  /**
   * Wraps a table name in brackets if a database prefix is being used.
   *
   * @param string $table_name
   *   The name of the database table.
   *
   * @return string
   *   The (possibly wrapped) table name.
   */
  public function wrapTableName($table_name) {
    if ($this->wrap) {
      $processed = '{' . $table_name . '}';
    }
    else {
      $processed = $table_name;
    }

    return $processed;
  }

  /**
   * Executes a sql command using drush sqlq and returns the output.
   *
   * @param string $query
   *   The SQL query to execute. Must end in a semicolon!
   *
   * @return string
   *   The output of the query.
   */
  protected function query($query) {
    $current = drush_get_context('DRUSH_SIMULATE');
    drush_set_context('DRUSH_SIMULATE', FALSE);
    $sql = drush_sql_get_class();
    $success = $sql->query($query);
    $output = drush_shell_exec_output();
    drush_set_context('DRUSH_SIMULATE', $current);

    return $output;
  }

}

<?php

namespace Drush\Commands\core;

use Consolidation\OutputFormatters\StructuredData\AssociativeList;

class StatusCommands {

  /**
   * @command new-status
   *
   * @field-labels
   *   drupal-version: Drupal version
   *   uri: Site URI
   *   db-driver: Database driver
   *   db-hostname: Database hostname
   *   db-port: Database port
   *   db-username: Database username
   *   db-password: Database password
   *   db-name: Database name
   *   db-status: Database
   *   bootstrap: Drupal bootstrap
   *   user: Drupal user
   *   theme: Default theme
   *   admin-theme: Administration theme
   *   php-bin: PHP executable
   *   php-conf: PHP configuration
   *   php-os: PHP OS
   *   drush-script: Drush script
   *   drush-version: Drush version
   *   drush-temp: Drush temp directory
   *   drush-conf: Drush configuration
   *   drush-alias-files: Drush alias files
   *   install-profile: Install profile
   *   root: Drupal root
   *   drupal-settings-file: Drupal Settings File
   *   site-path: Site path
   *   root: Drupal root
   *   site: Site path
   *   themes: Themes path
   *   modules: Modules path
   *   files: File directory path
   *   private: Private file directory path
   *   temp: Temporary file directory path
   *   config-sync: Sync config path
   *   files-path: File directory path
   *   temp-path: Temporary file directory path
   *   %paths: Other paths
   *
   * @topics docs-readme
   */
  public function status($options = ['project' => '', 'format' => 'table', 'fields' => '']) {
    $data = _core_site_status_table($options['project']);

    return new AssociativeList($data);
  }
}

<?php
namespace Drush\Drupal;

use Drush\Log\LogLevel;
use Drupal\Core\DrupalKernel as DrupalDrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;

class DrupalKernel extends DrupalDrupalKernel {
  /** @var ServiceModifierInterface[] */
  protected $serviceModifiers = [];

  /**
   * @inheritdoc
   */
  public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE, $app_root = NULL) {
    drush_log(dt("Create from request"), LogLevel::DEBUG);
    $kernel = new static($environment, $class_loader, $allow_dumping);
    static::bootEnvironment();
    $kernel->initializeSettings($request);
    return $kernel;
  }

  /**
   * Add a service modifier to the container builder.
   *
   * The container is not compiled until $kernel->boot(), so there is a chance
   * for clients to add compiler passes et. al. before then.
   */
  public function addServiceModifier(ServiceModifierInterface $serviceModifier) {
    drush_log(dt("add service modifier"), LogLevel::DEBUG);
    $this->serviceModifiers[] = $serviceModifier;
  }

  /**
   * @inheritdoc
   */
  protected function getContainerBuilder() {
    drush_log(dt("get container builder"), LogLevel::DEBUG);
    $container = parent::getContainerBuilder();
    foreach ($this->serviceModifiers as $serviceModifier) {
      $serviceModifier->alter($container);
    }
    return $container;
  }
  /**
   * Initializes the service container.
   *
   * @return \Symfony\Component\DependencyInjection\ContainerInterface
   */
  protected function initializeContainer() {
    if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
      $container_definition = $this->getCachedContainerDefinition();
      foreach ($this->serviceModifiers as $serviceModifier) {
        if (!$serviceModifier->check($container_definition)) {
          $this->invalidateContainer();
          break;
        }
      }
    }
    return parent::initializeContainer();
  }
}
<?php

namespace Drush\Drupal;

use Drush\Log\LogLevel;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;

class DrushServiceModifier implements ServiceModifierInterface
{
    /**
     * @inheritdoc
     */
    public function alter(ContainerBuilder $container) {
        drush_log(dt("service modifier alter"), LogLevel::DEBUG);
        // http://symfony.com/doc/2.7/components/dependency_injection/tags.html#register-the-pass-with-the-container
        $container->register('drush.service.consolecommands', 'Drush\Command\ServiceCommandlist');
        $container->addCompilerPass(new FindCommandsCompilerPass('drush.service.consolecommands', 'drush.command'));
        $container->register('drush.service.consolidationcommands', 'Drush\Command\ServiceCommandlist');
        $container->addCompilerPass(new FindCommandsCompilerPass('drush.service.consolidationcommands', 'consolidation.commandhandler'));
    }
  /**
   * Checks existing service definitions for the presence of modification.
   *
   * @param $container_definition
   *   Cached container definition
   * @return bool
   */
    public function check($container_definition) {
      return isset($container_definition['services']['drush.service.consolecommands']) &&
        isset($container_definition['services']['drush.service.consolidationcommands']);
    }
}
<?php

namespace Drush\Drupal;

use Drupal\Core\Extension\ExtensionDiscovery as DrupalExtensionDiscovery;

class ExtensionDiscovery extends DrupalExtensionDiscovery {
  static public function reset() {
    static::$files = array();
  }
}

<?php
namespace Drush\Drupal;

use Drush\Log\LogLevel;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

/**
 * This compiler pass is added to Drupal's ContainerBuilder by our own
 * subclass of DrupalKernel.  Our DrupalKernel subclass knows which
 * compiler passes to add because they are registered to it via its
 * 'alter()' method. This happens in DrupalBoot8 immediately after the
 * DrupalKernel object is created.
 *
 * Having been thus added, this compiler pass will then be called during
 * $kernel->boot(), when Drupal's dependency injection container is being
 * compiled.  Since we cannot use the container at this point (since its
 * initialization is not yet complete), we instead alter the definition of
 * a storage class in the container to add more setter injection method
 * calls to 'addCommandReference'.
 *
 * Later, after the container has been completely initialized, we can
 * fetch the storage class from the DI container (perhaps also via
 * injection from a reference in the container).  At that point, we can
 * request the list of Console commands that were added via the
 * (delayed) call(s) to addCommandReference.
 *
 * Documentation:
 *
 * http://symfony.com/doc/2.7/components/dependency_injection/tags.html#create-a-compilerpass
 */
class FindCommandsCompilerPass implements CompilerPassInterface
{
    protected $storageClassId;
    protected $tagId;

    public function __construct($storageClassId, $tagId)
    {
        $this->storageClassId = $storageClassId;
        $this->tagId = $tagId;
    }

    public function process(ContainerBuilder $container)
    {
        drush_log(dt("process !storage !tag", ['!storage' => $this->storageClassId, '!tag' => $this->tagId]), LogLevel::DEBUG);
        // We expect that our called registered the storage
        // class under the storage class id before adding this
        // compiler pass, but we will test this presumption to be sure.
        if (!$container->has($this->storageClassId)) {
            drush_log(dt("storage class not registered"), LogLevel::DEBUG);
            return;
        }

        $definition = $container->findDefinition(
            $this->storageClassId
        );

        $taggedServices = $container->findTaggedServiceIds(
            $this->tagId
        );
        foreach ($taggedServices as $id => $tags) {
            drush_log(dt("found tagged service !id", ['!id' => $id]), LogLevel::DEBUG);
            $definition->addMethodCall(
                'addCommandReference',
                array(new Reference($id))
            );
        }
    }
}
<?php

/**
 * @file
 * Contains \Drush\Log\DrushLog.
 *
 * This class is only used to convert logging calls made
 * inside of Drupal into a logging format that is usable
 * by Drush.  This code is ONLY usable within the context
 * of a bootstrapped Drupal 8 site.
 *
 * See Drush\Log\Logger for our actuall LoggerInterface
 * implementation, that does the work of logging messages
 * that originate from Drush.
 */

namespace Drush\Log;

use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Drupal\Core\Logger\RfcLogLevel;
use Psr\Log\LoggerInterface;

/**
 * Redirects Drupal logging messages to Drush log.
 *
 * Note that Drupal extends the LoggerInterface, and
 * needlessly replaces Psr\Log\LogLevels with Drupal\Core\Logger\RfcLogLevel.
 * Doing this arguably violates the Psr\Log contract,
 * but we can't help that here -- we just need to convert back.
 */
class DrushLog implements LoggerInterface {

  use RfcLoggerTrait;

  /**
   * The message's placeholders parser.
   *
   * @var \Drupal\Core\Logger\LogMessageParserInterface
   */
  protected $parser;

  /**
   * The logger that messages will be passed through to.
   */
  protected $logger;

  /**
   * Constructs a DrushLog object.
   *
   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
   *   The parser to use when extracting message variables.
   */
  public function __construct(LogMessageParserInterface $parser, LoggerInterface $logger) {
    $this->parser = $parser;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public function log($level, $message, array $context = array()) {
    // Translate the RFC logging levels into their Drush counterparts, more or
    // less.
    // @todo ALERT, CRITICAL and EMERGENCY are considered show-stopping errors,
    // and they should cause Drush to exit or panic. Not sure how to handle this,
    // though.
    switch ($level) {
      case RfcLogLevel::ALERT:
      case RfcLogLevel::CRITICAL:
      case RfcLogLevel::EMERGENCY:
      case RfcLogLevel::ERROR:
        $error_type = LogLevel::ERROR;
        break;

      case RfcLogLevel::WARNING:
        $error_type = LogLevel::WARNING;
        break;

      // TODO: RfcLogLevel::DEBUG should be 'debug' rather than 'notice'?
      case RfcLogLevel::DEBUG:
      case RfcLogLevel::INFO:
      case RfcLogLevel::NOTICE:
        $error_type = LogLevel::NOTICE;
        break;

      // TODO: Unknown log levels that are not defined
      // in Psr\Log\LogLevel or Drush\Log\LogLevel SHOULD NOT be used.  See
      // https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
      // We should convert these to 'notice'.
      default:
        $error_type = $level;
        break;
    }

    // Populate the message placeholders and then replace them in the message.
    $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context);

    // Filter out any placeholders that can not be cast to strings.
    $message_placeholders = array_filter($message_placeholders, function ($element) {
      return is_scalar($element) || is_callable([$element, '__toString']);
    });

    $message = empty($message_placeholders) ? $message : strtr($message, $message_placeholders);

    $this->logger->log($error_type, $message, $context);
  }

}
<?php

namespace Drush\Log;

/**
 * Additional log levels that Drush uses for historical reasons.
 * Standard log levels should be preferred.
 */
class LogLevel extends \Psr\Log\LogLevel
{
    // Things that happen early on.  Like 'notice'
    const BOOTSTRAP = 'bootstrap';
    const PREFLIGHT = 'preflight';

    // Notice that the user is cancelling an operation. Like 'warning'
    const CANCEL = 'cancel';

    // Various 'success' messages.  Like 'notice'
    const OK = 'ok';

    // Highly verbose messages that are not always interesting.
    // Displayed only when --debug and --verbose specified together.
    const DEBUG_NOTIFY = 'debugnotify';

    // Means the command was successful. Should appear at most once
    // per command (perhaps more if subcommands are executed, though).
    // Like 'notice'.
    const SUCCESS = 'success';

    // Batch processes. Like 'notice'
    const BATCH = 'batch';
}
<?php

/**
 * @file
 * Contains \Drush\Log\Logger.
 *
 * This is the actual Logger for Drush that is responsible
 * for logging messages.
 *
 * This logger is designed such that it can be provided to
 * other libraries that log to a Psr\Log\LoggerInterface.
 * As such, it takes responsibility for passing log messages
 * to backend invoke, as necessary (c.f. drush_backend_packet()).
 *
 * Drush supports all of the required log levels from Psr\Log\LogLevel,
 * and also defines its own. See Drush\Log\LogLevel.
 *
 * Those who may wish to change the way logging works in Drush
 * should therefore NOT attempt to replace this logger with their
 * own LoggerInterface, as it will not work.  It would be okay
 * to extend Drush\Log\Logger, or perhaps we could provide a way
 * to set an output I/O object here, in case output redirection
 * was the only thing that needed to be swapped out.
 */

namespace Drush\Log;

use Drush\Log\LogLevel;
use Psr\Log\AbstractLogger;

class Logger extends AbstractLogger {

    public function log($level, $message, array $context = array()) {
      // Convert to old $entry array for b/c calls
      $entry = $context;
      $entry['type'] = $level;
      $entry['message'] = $message;
      if (!isset($entry['memory'])) {
        $entry['memory'] = memory_get_usage();
      }

      // Drush\Log\Logger should take over all of the responsibilities
      // of drush_log, including caching the log messages and sending
      // log messages along to backend invoke.
      // TODO: move these implementations inside this class.
      $log =& drush_get_context('DRUSH_LOG', array());
      $log[] = $entry;
      if ($level != LogLevel::DEBUG_NOTIFY) {
        drush_backend_packet('log', $entry);
      }

      if (drush_get_context('DRUSH_NOCOLOR')) {
        $red = "[%s]";
        $yellow = "[%s]";
        $green = "[%s]";
      }
      else {
        $red = "\033[31;40m\033[1m[%s]\033[0m";
        $yellow = "\033[1;33;40m\033[1m[%s]\033[0m";
        $green = "\033[1;32;40m\033[1m[%s]\033[0m";
      }

      $verbose = drush_get_context('DRUSH_VERBOSE');
      $debug = drush_get_context('DRUSH_DEBUG');
      $debugnotify = drush_get_context('DRUSH_DEBUG_NOTIFY');

      switch ($level) {
        case LogLevel::WARNING :
        case LogLevel::CANCEL :
          $type_msg = sprintf($yellow, $level);
          break;
        case 'failed' : // Obsolete; only here in case contrib is using it.
        case LogLevel::EMERGENCY : // Not used by Drush
        case LogLevel::ALERT : // Not used by Drush
        case LogLevel::ERROR :
          $type_msg = sprintf($red, $level);
          break;
        case LogLevel::OK :
        case 'completed' : // Obsolete; only here in case contrib is using it.
        case LogLevel::SUCCESS :
        case 'status': // Obsolete; only here in case contrib is using it.
          // In quiet mode, suppress progress messages
          if (drush_get_context('DRUSH_QUIET')) {
            return TRUE;
          }
          $type_msg = sprintf($green, $level);
          break;
        case LogLevel::NOTICE :
        case 'message' : // Obsolete; only here in case contrib is using it.
        case LogLevel::INFO :
          if (!$verbose) {
            // print nothing. exit cleanly.
            return TRUE;
          }
          $type_msg = sprintf("[%s]", $level);
          break;
        case LogLevel::DEBUG_NOTIFY :
          $level = LogLevel::DEBUG; // Report 'debug', handle like 'preflight'
        case LogLevel::PREFLIGHT :
          if (!$debugnotify) {
            // print nothing unless --debug AND --verbose. exit cleanly.
            return TRUE;
          }
          $type_msg = sprintf("[%s]", $level);
          break;
        case LogLevel::BOOTSTRAP :
        case LogLevel::DEBUG :
        default :
          if (!$debug) {
            // print nothing. exit cleanly.
            return TRUE;
          }
          $type_msg = sprintf("[%s]", $level);
          break;
      }

      // When running in backend mode, log messages are not displayed, as they will
      // be returned in the JSON encoded associative array.
      if (drush_get_context('DRUSH_BACKEND')) {
        return;
      }

      $columns = drush_get_context('DRUSH_COLUMNS', 80);

      $width[1] = 11;
      // Append timer and memory values.
      if ($debug) {
        $timer = sprintf('[%s sec, %s]', round($entry['timestamp']-DRUSH_REQUEST_TIME, 2), drush_format_size($entry['memory']));
        $entry['message'] = $entry['message'] . ' ' . $timer;
      }

      $width[0] = ($columns - 11);

      $format = sprintf("%%-%ds%%%ds", $width[0], $width[1]);

      // Place the status message right aligned with the top line of the error message.
      $message = wordwrap($entry['message'], $width[0]);
      $lines = explode("\n", $message);
      $lines[0] = sprintf($format, $lines[0], $type_msg);
      $message = implode("\n", $lines);
      drush_print($message, 0, STDERR);

    }


}
<?php

/**
 * @file
 * Parser for INI format.
 */

namespace Drush\Make\Parser;

class ParserIni implements ParserInterface {

  /**
   * Regex for parsing INI format.
   */
  private static $iniRegex = '
    @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
    ((?:
      [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
      \[[^\[\]]*\]                  # unless they are balanced and not nested
    )+?)
    \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
    (?:
      ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
      (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
      ([^\r\n]*?)                   # Non-quoted string
    )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
    @msx';

  /**
   * {@inheritdoc}
   */
  public static function supportedFile($filename) {
    $info = pathinfo($filename);
    return isset($info['extension']) && $info['extension'] === 'make';
  }

  /**
   * {@inheritdoc}
   */
  public static function parse($data) {
    if (preg_match_all(self::$iniRegex, $data, $matches, PREG_SET_ORDER)) {
      $info = array();
      foreach ($matches as $match) {
        // Fetch the key and value string.
        $i = 0;
        foreach (array('key', 'value1', 'value2', 'value3') as $var) {
          $$var = isset($match[++$i]) ? $match[$i] : '';
        }
        $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;

        // Parse array syntax.
        $keys = preg_split('/\]?\[/', rtrim($key, ']'));
        $last = array_pop($keys);
        $parent = &$info;

        // Create nested arrays.
        foreach ($keys as $key) {
          if ($key == '') {
            $key = count($parent);
          }
          if (isset($merge_item) && isset($parent[$key]) && !is_array($parent[$key])) {
            $parent[$key] = array($merge_item => $parent[$key]);
          }
          if (!isset($parent[$key]) || !is_array($parent[$key])) {
            $parent[$key] = array();
          }
          $parent = &$parent[$key];
        }

        // Handle PHP constants.
        if (defined($value)) {
          $value = constant($value);
        }

        // Insert actual value.
        if ($last == '') {
          $last = count($parent);
        }
        if (isset($merge_item) && isset($parent[$last]) && is_array($parent[$last])) {
          $parent[$last][$merge_item] = $value;
        }
        else {
          $parent[$last] = $value;
        }
      }
      return $info;
    }
  }

}
<?php

/**
 * @file
 * Interface for make file parsing.
 */

namespace Drush\Make\Parser;

interface ParserInterface {

  /**
   * Determine if a given file is supported.
   *
   * @param string $filename
   *
   * @return bool
   */
  public static function supportedFile($filename);

  /**
   * Parse an input string into an array.
   *
   * @param string $data
   *
   * @return array
   *   Makefile data as an array.
   */
  public static function parse($data);

}
<?php

/**
 * @file
 * Parser for YAML format.
 */

namespace Drush\Make\Parser;

use Symfony\Component\Yaml\Yaml;

class ParserYaml implements ParserInterface {

  /**
   * {@inheritdoc}
   */
  public static function supportedFile($filename) {
    $info = pathinfo($filename);
    return isset($info['extension']) && $info['extension'] === 'yml';
  }

  /**
   * {@inheritdoc}
   */
  public static function parse($data) {
    return Yaml::parse($data);
  }

}
<?php

/**
 * @file
 * Contains \Drush\Psysh\Caster.
 */

namespace Drush\Psysh;

use Symfony\Component\VarDumper\Caster\Caster as BaseCaster;

/**
 * Caster class for VarDumper casters for the shell.
 */
class Caster {

  /**
   * Casts \Drupal\Core\Entity\ContentEntityInterface classes.
   */
  public static function castContentEntity($entity, $array, $stub, $isNested) {
    if (!$isNested) {
      foreach ($entity as $property => $item) {
        $array[BaseCaster::PREFIX_PROTECTED . $property] = $item;
      }
    }

    return $array;
  }

  /**
   * Casts \Drupal\Core\Field\FieldItemListInterface classes.
   */
  public static function castFieldItemList($list_item, $array, $stub, $isNested) {
    if (!$isNested) {
      foreach ($list_item as $delta => $item) {
        $array[BaseCaster::PREFIX_VIRTUAL . $delta] = $item;
      }
    }

    return $array;
  }

  /**
   * Casts \Drupal\Core\Field\FieldItemInterface classes.
   */
  public static function castFieldItem($item, $array, $stub, $isNested) {
    if (!$isNested) {
      $array[BaseCaster::PREFIX_VIRTUAL . 'value'] = $item->getValue();
    }

    return $array;
  }

  /**
   * Casts \Drupal\Core\Config\Entity\ConfigEntityInterface classes.
   */
  public static function castConfigEntity($entity, $array, $stub, $isNested) {
    if (!$isNested) {
      foreach ($entity->toArray() as $property => $value) {
        $array[BaseCaster::PREFIX_PROTECTED . $property] = $value;
      }
    }

    return $array;
  }

  /**
   * Casts \Drupal\Core\Config\ConfigBase classes.
   */
  public static function castConfig($config, $array, $stub, $isNested) {
    if (!$isNested) {
      foreach ($config->get() as $property => $value) {
        $array[BaseCaster::PREFIX_VIRTUAL . $property] = $value;
      }
    }

    return $array;
  }

  /**
   * Casts \Drupal\Component\DependencyInjection\Container classes.
   */
  public static function castContainer($container, $array, $stub, $isNested) {
    if (!$isNested) {
      $service_ids = $container->getServiceIds();
      sort($service_ids);
      foreach ($service_ids as $service_id) {
        $service = $container->get($service_id);
        $array[BaseCaster::PREFIX_VIRTUAL . $service_id] = is_object($service) ? get_class($service) : $service;
      }
    }

    return $array;
  }

}
<?php
/**
 * @file
 * Contains \Drush\Psysh\DrushCommand.
 *
 * DrushCommand is a PsySH proxy command which accepts a Drush command config
 * array and tries to build an appropriate PsySH command for it.
 */

namespace Drush\Psysh;

use Psy\Command\Command as BaseCommand;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Main Drush command.
 */
class DrushCommand extends BaseCommand {

  /**
   * @var array
   */
  private $config;

  /**
   * @var string
   */
  private $category = '';

  /**
   * DrushCommand constructor.
   *
   * This accepts the Drush command configuration array and does a pretty
   * decent job of building a PsySH command proxy for it. Wheee!
   *
   * @param array $config
   *   Drush command configuration array.
   */
  public function __construct(array $config) {
    $this->config = $config;
    parent::__construct();
  }

  /**
   * Get Category of this command.
   */
  public function getCategory() {
    return $this->category;
  }

  /**
   * Sets the category title.
   *
   * @param string $category_title
   */
  public function setCategory($category_title) {
    $this->category = $category_title;
  }

  /**
   * {@inheritdoc}
   */
  protected function configure() {
    $this
      ->setName($this->config['command'])
      ->setAliases($this->buildAliasesFromConfig())
      ->setDefinition($this->buildDefinitionFromConfig())
      ->setDescription($this->config['description'])
      ->setHelp($this->buildHelpFromConfig());
  }

  /**
   * {@inheritdoc}
   */
  protected function execute(InputInterface $input, OutputInterface $output) {
    $args = $input->getArguments();
    $first = array_shift($args);

    // If the first argument is an alias, assign the next argument as the
    // command.
    if (strpos($first, '@') === 0) {
      $alias = $first;
      $command = array_shift($args);
    }
    // Otherwise, default the alias to '@self' and use the first argument as the
    // command.
    else {
      $alias = '@self';
      $command = $first;
    }

    $options = $input->getOptions();
    // Force the 'backend' option to TRUE.
    $options['backend'] = TRUE;

    $return = drush_invoke_process($alias, $command, array_values($args), $options, ['interactive' => TRUE]);

    if ($return['error_status'] > 0) {
      foreach ($return['error_log'] as $error_type => $errors) {
        $output->write($errors);
      }
      // Add a newline after so the shell returns on a new line.
      $output->writeln('');
    }
    else {
      $output->page(drush_backend_get_result());
    }
  }

  /**
   * Extract Drush command aliases from config array.
   *
   * @return array
   *   The command aliases.
   */
  protected function buildAliasesFromConfig() {
    return !empty($this->config['aliases']) ? $this->config['aliases'] : [];
  }

  /**
   * Build a command definition from Drush command configuration array.
   *
   * Currently, adds all non-hidden arguments and options, and makes a decent
   * effort to guess whether an option accepts a value or not. It isn't always
   * right :P
   *
   * @return array
   *   the command definition.
   */
  protected function buildDefinitionFromConfig() {
    $definitions = [];

    if (isset($this->config['arguments']) && !empty($this->config['arguments'])) {
      $required_args = $this->config['required-arguments'];

      if ($required_args === FALSE) {
        $required_args = 0;
      }
      elseif ($required_args === TRUE) {
        $required_args = count($this->config['arguments']);
      }

      foreach ($this->config['arguments'] as $name => $argument) {
        if (!is_array($argument)) {
          $argument = ['description' => $argument];
        }

        if (!empty($argument['hidden'])) {
          continue;
        }

        $input_type = ($required_args-- > 0) ? InputArgument::REQUIRED : InputArgument::OPTIONAL;

        $definitions[] = new InputArgument($name, $input_type, $argument['description'], NULL);
      }
    }

    // First create all global options.
    $options = $this->config['options'] + drush_get_global_options();

    // Add command specific options.
    $definitions = array_merge($definitions, $this->createInputOptionsFromConfig($options));

    return $definitions;
  }

  /**
   * Creates input definitions from command options.
   *
   * @param array $options_config
   *
   * @return \Symfony\Component\Console\Input\InputInterface[]
   */
  protected function createInputOptionsFromConfig(array $options_config) {
    $definitions = [];

    foreach ($options_config as $name => $option) {
      // Some commands will conflict.
      if (in_array($name, ['help', 'command'])) {
        continue;
      }

      if (!is_array($option)) {
        $option = ['description' => $option];
      }

      if (!empty($option['hidden'])) {
        continue;
      }

      // @todo: Figure out if there's a way to detect InputOption::VALUE_NONE
      // (i.e. flags) via the config array.
      if (isset($option['value']) && $option['value'] === 'required') {
        $input_type = InputOption::VALUE_REQUIRED;
      }
      else {
        $input_type = InputOption::VALUE_OPTIONAL;
      }

      $definitions[] = new InputOption($name, !empty($option['short-form']) ? $option['short-form'] : '', $input_type, $option['description']);
    }

    return $definitions;
  }

  /**
   * Build a command help from the Drush configuration array.
   *
   * Currently it's a word-wrapped description, plus any examples provided.
   *
   * @return string
   *   The help string.
   */
  protected function buildHelpFromConfig() {
    $help = wordwrap($this->config['description']);

    $examples = [];
    foreach ($this->config['examples'] as $ex => $def) {
      // Skip empty examples and things with obvious pipes...
      if (($ex === '') || (strpos($ex, '|') !== FALSE)) {
        continue;
      }

      $ex = preg_replace('/^drush\s+/', '', $ex);
      $examples[$ex] = $def;
    }

    if (!empty($examples)) {
      $help .= "\n\ne.g.";

      foreach ($examples as $ex => $def) {
        $help .= sprintf("\n<return>// %s</return>\n", wordwrap(OutputFormatter::escape($def), 75, "</return>\n<return>// "));
        $help .= sprintf("<return>>>> %s</return>\n", OutputFormatter::escape($ex));
      }
    }

    return $help;
  }

}
<?php

/**
 * @file
 * Contains \Drush\Psysh\DrushCommand.
 */

namespace Drush\Psysh;

use Psy\Command\Command as BaseCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Help command.
 *
 * Lists available commands, and gives command-specific help when asked nicely.
 *
 * This replaces the PsySH help command to list commands by category.
 */
class DrushHelpCommand extends BaseCommand {

  /**
   * Label for PsySH commands.
   */
  const PSYSH_CATEGORY = 'PsySH commands';

  /**
   * The currently set subcommand.
   *
   * @var \Symfony\Component\Console\Command\Command
   */
  protected $command;

  /**
   * {@inheritdoc}
   */
  protected function configure() {
    $this
      ->setName('help')
      ->setAliases(['?'])
      ->setDefinition([
        new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', NULL),
      ])
      ->setDescription('Show a list of commands. Type `help [foo]` for information about [foo].');
  }

  /**
   * Helper for setting a subcommand to retrieve help for.
   *
   * @param \Symfony\Component\Console\Command\Command $command
   */
  public function setCommand(Command $command) {
    $this->command = $command;
  }

  /**
   * {@inheritdoc}
   */
  protected function execute(InputInterface $input, OutputInterface $output) {
    if ($this->command !== NULL) {
      // Help for an individual command.
      $output->page($this->command->asText());
      $this->command = NULL;
    }
    elseif ($name = $input->getArgument('command_name')) {
      // Help for an individual command.
      $output->page($this->getApplication()->get($name)->asText());
    }
    else {
      $categories = [];

      // List available commands.
      $commands = $this->getApplication()->all();

      // Find the alignment width.
      $width = 0;
      foreach ($commands as $command) {
        $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
      }
      $width += 2;

      foreach ($commands as $name => $command) {
        if ($name !== $command->getName()) {
          continue;
        }

        if ($command->getAliases()) {
          $aliases = sprintf('  <comment>Aliases:</comment> %s', implode(', ', $command->getAliases()));
        }
        else {
          $aliases = '';
        }

        if ($command instanceof DrushCommand) {
          $category = (string) $command->getCategory();
        }
        else {
          $category = static::PSYSH_CATEGORY;
        }

        if (!isset($categories[$category])) {
          $categories[$category] = [];
        }

        $categories[$category][] = sprintf("    <info>%-${width}s</info> %s%s", $name, $command->getDescription(), $aliases);
      }

      $messages = [];

      foreach ($categories as $name => $category) {
        $messages[] = '';
        $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape($name));
        foreach ($category as $message) {
          $messages[] = $message;
        }
      }

      $output->page($messages);
    }
  }

}
<?php

/**
 * @file
 * Contains \Drush\Psysh\Shell.
 */

namespace Drush\Psysh;

use Psy\Shell as BaseShell;
use Symfony\Component\Console\Input\StringInput;

class Shell extends BaseShell {

  /**
   * Get a command (if one exists) for the current input string.
   *
   * @param string $input
   *
   * @return null|Command
   */
  protected function getCommand($input) {
    if ($name = $this->getCommandFromInput($input)) {
      return $this->get($name);
    }
  }

  /**
   * Check whether a command is set for the current input string.
   *
   * @param string $input
   *
   * @return bool True if the shell has a command for the given input.
   */
  protected function hasCommand($input) {
    if ($name = $this->getCommandFromInput($input)) {
      return $this->has($name);
    }

    return false;
  }

  /**
   * Get the command from the current input, takes aliases into account.
   *
   * @param string $input
   *   The raw input
   *
   * @return string|NULL
   *   The current command.
   */
  protected function getCommandFromInput($input) {
    // Remove the alias from the start of the string before parsing and
    // returning the command. Essentially, when choosing a command, we're
    // ignoring the site alias.
    $input = preg_replace('|^\@[^\s]+|', '', $input);

    $input = new StringInput($input);
    return $input->getFirstArgument();
  }

}
<?php

namespace Drush\Queue;

class Queue6 extends Queue7 {

  public function __construct() {
    // Drupal 6 has no core queue capabilities, and thus requires contrib.
    if (!module_exists('drupal_queue')) {
      throw new QueueException(dt('The drupal_queue module need to be installed/enabled.'));
    }
    else {
      drupal_queue_include();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getQueues() {
    if (!isset(static::$queues)) {
      static::$queues = module_invoke_all('cron_queue_info');
      drupal_alter('cron_queue_info', static::$queues);
    }
    return static::$queues;
  }

}
<?php

namespace Drush\Queue;

use Drush\Log\LogLevel;
use DrupalQueue;

class Queue7 extends QueueBase {

  /**
   * {@inheritdoc}
   */
  public function getQueues() {
    if (!isset(static::$queues)) {
      static::$queues = module_invoke_all('cron_queue_info');
      drupal_alter('cron_queue_info', static::$queues);
      // Merge in queues from modules that implement hook_queue_info.
      // Currently only defined by the queue_ui module.
      $info_queues = module_invoke_all('queue_info');
      foreach ($info_queues as $name => $queue) {
        static::$queues[$name]['worker callback'] = $queue['cron']['callback'];
        if (isset($queue['cron']['time'])) {
          static::$queues[$name]['time'] = $queue['cron']['time'];
        }
      }
    }
    return static::$queues;
  }

  /**
   * {@inheritdoc}
   *
   * @return \DrupalQueueInterface
   */
  public function getQueue($name) {
    return DrupalQueue::get($name);
  }

  /**
   * {@inheritdoc}
   */
  public function run($name, $time_limit = 0) {
    $info = $this->getInfo($name);
    $function = $info['worker callback'];
    $end = time() + $time_limit;
    $queue = $this->getQueue($name);
    $count = 0;

    while ((!$time_limit || time() < $end) && ($item = $queue->claimItem())) {
      try {
        drush_log(dt('Processing item @id from @name queue.', array('@name' => $name, 'id' => $item->item_id)), LogLevel::INFO);
        $function($item->data);
        $queue->deleteItem($item);
        $count++;
      }
      catch (\Exception $e) {
        // In case of exception log it and leave the item in the queue
        // to be processed again later.
        drush_set_error('DRUSH_QUEUE_EXCEPTION', $e->getMessage());
      }
    }

    return $count;
  }

}
<?php

namespace Drush\Queue;

use Drush\Log\LogLevel;
use Drupal\Core\Queue\QueueWorkerManager;
use Drupal\Core\Queue\RequeueException;
use Drupal\Core\Queue\SuspendQueueException;

class Queue8 extends QueueBase {

  /**
   * @var \Drupal\Core\Queue\QueueWorkerManager
   */
  protected $workerManager;

  /**
   * Set the queue worker manager.
   */
  public function __construct(QueueWorkerManager $manager = NULL) {
    $this->workerManager = $manager ?: \Drupal::service('plugin.manager.queue_worker');
  }

  /**
   * {@inheritdoc}
   */
  public function getQueues() {
    if (!isset(static::$queues)) {
      static::$queues = array();
      foreach ($this->workerManager->getDefinitions() as $name => $info) {
        static::$queues[$name] = $info;
      }
    }
    return static::$queues;
  }

  /**
   * {@inheritdoc}
   *
   * @return \Drupal\Core\Queue\QueueInterface
   */
  public function getQueue($name) {
    return \Drupal::queue($name);
  }

  /**
   * {@inheritdoc}
   */
  public function run($name, $time_limit = 0) {
    $worker = $this->workerManager->createInstance($name);
    $end = time() + $time_limit;
    $queue = $this->getQueue($name);
    $count = 0;

    while ((!$time_limit || time() < $end) && ($item = $queue->claimItem())) {
      try {
        drush_log(dt('Processing item @id from @name queue.', array('@name' => $name, '@id' => $item->item_id)), LogLevel::INFO);
        $worker->processItem($item->data);
        $queue->deleteItem($item);
        $count++;
      }
      catch (RequeueException $e) {
        // The worker requested the task to be immediately requeued.
        $queue->releaseItem($item);
      }
      catch (SuspendQueueException $e) {
        // If the worker indicates there is a problem with the whole queue,
        // release the item and skip to the next queue.
        $queue->releaseItem($item);
        drush_set_error('DRUSH_SUSPEND_QUEUE_EXCEPTION', $e->getMessage());
      }
      catch (\Exception $e) {
        // In case of any other kind of exception, log it and leave the item
        // in the queue to be processed again later.
        drush_set_error('DRUSH_QUEUE_EXCEPTION', $e->getMessage());
      }
    }

    return $count;
  }

}
<?php

namespace Drush\Queue;

abstract class QueueBase implements QueueInterface {

  /**
   * Keep track of queue definitions.
   *
   * @var array
   */
  protected static $queues;

  /**
   * Lists all available queues.
   */
  public function listQueues() {
    $result = array();
    foreach (array_keys($this->getQueues()) as $name) {
      $q = $this->getQueue($name);
      $result[$name] = array(
        'queue' => $name,
        'items' => $q->numberOfItems(),
        'class' => get_class($q),
      );
    }
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getInfo($name) {
    $queues = $this->getQueues();
    if (!isset($queues[$name])) {
      throw new QueueException(dt('Could not find the !name queue.', array('!name' => $name)));
    }
    return $queues[$name];
  }

}
<?php

namespace Drush\Queue;

class QueueException extends \Exception {}
<?php

namespace Drush\Queue;

/**
 * Defines an interface for interacting with queues.
 */
interface QueueInterface {

  /**
   * Returns all queues.
   */
  public function getQueues();

  /**
   * Runs a given queue.
   *
   * @param string $name
   *   The name of the queue to run.
   * @param int $time_limit
   *   The maximum number of seconds that the queue can run. By default the
   *   queue will be run as long as possible.
   *
   * @return int
   *   The number of items successfully processed from the queue.
   */
  public function run($name, $time_limit = 0);

  /**
   * Returns a given queue definition.
   *
   * @param string $name
   *   The name of the queue to run.
   */
  public function getQueue($name);

  /**
   * Returns a given queue definition.
   *
   * @param string $name
   *   The name of the queue to run.
   */
  public function getInfo($name);

}
<?php

namespace Drush\Role;

class Role6 extends RoleBase {
  public $perms = array();

  public function getPerms() {
    if (empty($this->perms)) {
      $perms = db_result(db_query("SELECT perm FROM {permission} pm LEFT JOIN {role} r ON r.rid = pm.rid WHERE r.rid = '%d'", $this->rid));
      $role_perms = explode(", ", $perms);
      $this->perms = array_filter($role_perms);
    }
    return $this->perms;
  }

  public function getModulePerms($module) {
    return module_invoke($module, 'perm');
  }

  public function role_create($role_machine_name, $role_human_readable_name = '') {
    $this->_admin_user_role_op($role_machine_name, t('Add role'));
    return TRUE;
  }

  public function delete() {
    $this->_admin_user_role_op($this->rid, t('Delete role'));
  }

  function _admin_user_role_op($role_machine_name, $op) {
    // c.f. http://drupal.org/node/283261
    require_once(drupal_get_path('module', 'user') . "/user.admin.inc");

    $form_id = "user_admin_new_role";
    $form_values = array();
    $form_values["name"] = $role_machine_name;
    $form_values["op"] = $op;
    $form_state = array();
    $form_state["values"] = $form_values;

    drupal_execute($form_id, $form_state);
  }

  public function grant_permissions($perms_to_add) {
    $perms = $this->getPerms();
    $this->perms = array_unique(array_merge($this->perms, $perms_to_add));
    $this->updatePerms();
  }

  public function revoke_permissions($perms_to_remove) {
    $perms = $this->getPerms();
    $this->perms = array_diff($this->perms, $perms_to_remove);
    $this->updatePerms();
  }

  function updatePerms() {
    $new_perms = implode(", ", $this->perms);
    drush_op('db_query', "UPDATE {permission} SET perm = '%s' WHERE rid= %d", $new_perms, $this->rid);
  }
}
<?php

namespace Drush\Role;

class Role7 extends RoleBase {
  public function getPerms() {
    $perms = user_role_permissions(array($this->rid => $this->name));
    return array_keys($perms[$this->rid]);
  }

  public function getModulePerms($module) {
    $perms = module_invoke($module, 'permission');
    return $perms ? array_keys($perms) : array();
  }

  public function role_create($role_machine_name, $role_human_readable_name = '') {
    return user_role_save((object)array('name' => $role_machine_name));
  }

  public function delete() {
    user_role_delete($this->rid);
  }

  public function grant_permissions($perms) {
    return drush_op('user_role_grant_permissions', $this->rid, $perms);
  }

  public function revoke_permissions($perms) {
    return drush_op('user_role_revoke_permissions', $this->rid, $perms);
  }
}
<?php

namespace Drush\Role;

use Drupal\user\Entity\Role;

class Role8 extends Role7 {
  public function role_create($role_machine_name, $role_human_readable_name = '') {
    // In D6 and D7, when we create a new role, the role
    // machine name is specified, and the numeric rid is
    // auto-assigned (next available id); in D8, when we
    // create a new role, we need to specify both the rid,
    // which is now the role machine name, and also a human-readable
    // role name.  If the client did not provide a human-readable
    // name, then we'll use the role machine name in its place.
    if (empty($role_human_readable_name)) {
      $role_human_readable_name = ucfirst($role_machine_name);
    }
    $role = new Role(array(
      'id' => $role_machine_name,
      'label' => $role_human_readable_name,
    ), 'user_role');
    $role->save();
    return $role;
  }

  public function getPerms() {
    $role = entity_load('user_role', $this->rid);
    $perms = $role->getPermissions();
    // $perms = user_role_permissions(array($this->rid => $this->name));
    return $perms;
  }

  public function getAllModulePerms() {
    $perms = \Drupal::service('user.permissions')->getPermissions();
    return array_keys($perms);
  }

  public function getModulePerms($module) {
    $module_perms = array();
    $perms = \Drupal::service('user.permissions')->getPermissions();
    foreach ($perms as $name => $perm) {
      if ($perm['provider'] == $module) {
        $module_perms[] = $name;
      }
    }
    return $module_perms;
  }

  public function delete() {
    $role = entity_load('user_role', $this->rid);
    $role->delete();
  }

  public function grant_permissions($perms) {
    return drush_op('user_role_grant_permissions', $this->rid, $perms);
  }

  public function revoke_permissions($perms) {
    return drush_op('user_role_revoke_permissions', $this->rid, $perms);
  }
}
<?php

namespace Drush\Role;

abstract class RoleBase {
  /**
   * Drupal 6 and Drupal 7:
   *   'rid' is numeric
   *   'name' is machine name (e.g. 'anonymous user')
   *
   * Drupal 8:
   *   'rid' is machine name (e.g. 'anonymous')
   *   'name' is human-readable name (e.g. 'Anonymous user').
   *
   * c.f. http://drupal.org/node/1619504
   */
  public $name;
  public $rid;

  /**
   * This is initialized to the result of the user_roles()
   * function, which returns an associative array of
   * rid => name pairs.
   */
  public $roles;

  /**
   * This constructor will allow the role to be selected either
   * via the role id or via the role name.
   */
  public function __construct($rid = DRUPAL_ANONYMOUS_RID) {
    $this->roles = user_roles();
    if (!is_numeric($rid)) {
      $role_name = $rid;
      if (in_array($role_name, $this->roles)) {
        $rid = array_search($role_name, $this->roles);
      }
    }

    if (isset($this->roles[$rid])) {
      $this->rid = $rid;
      // In D8+ Role is an object.
      $this->name = is_object($this->roles[$rid]) ? $this->roles[$rid]->label() : $this->roles[$rid];
    }
    else {
      throw new RoleException(dt('Could not find the role: !role', array('!role' => $rid)));
    }
  }

  /*
   * Get all perms for a given Role.
   */
  public function getPerms() {
    return array();
  }

  /*
   * Get all perms for a given module.
   */
  public function getModulePerms($module) {
    return array();
  }

  /*
   * Get all permissions site-wide.
   */
  public function getAllModulePerms() {
    $permissions = array();
    drush_include_engine('drupal', 'environment');
    $module_list = drush_module_list();
    ksort($module_list);
    foreach ($module_list as $module) {
      if ($perms = $this->getModulePerms($module)) {
        $permissions = array_merge($permissions, $perms);
      }
    }
    return $permissions;
  }

  public function role_create($role_machine_name, $role_human_readable_name = '') {
  }

  public function delete() {
  }

  public function add($perm) {
    $perms = $this->getPerms();
    if (!in_array($perm, $perms)) {
      $this->grant_permissions(array($perm));
      return TRUE;
    }
    else {
      drush_log(dt('"!role" already has the permission "!perm"', array(
        '!perm' => $perm,
        '!role' => $this->name,
      )), 'ok');
      return FALSE;
    }
  }

  public function remove($perm) {
    $perms = $this->getPerms();
    if (in_array($perm, $perms)) {
      $this->revoke_permissions(array($perm));
      return TRUE;
    }
    else {
      drush_log(dt('"!role" does not have the permission "!perm"', array(
        '!perm' => $perm,
        '!role' => $this->name,
      )), 'ok');
      return FALSE;
    }
  }

  public function grant_permissions($perms) {
  }

  public function revoke_permissions($perms) {
  }
}
<?php

namespace Drush\Role;

class RoleException extends \Exception {}
<?php

namespace Drush\Sql;

use Drush\Log\LogLevel;

class Sql6 extends SqlVersion {
  public function get_db_spec() {
    $db_spec = NULL;
    if ($url = isset($GLOBALS['db_url']) ? $GLOBALS['db_url'] : drush_get_option('db-url', NULL)) {
      $database = drush_get_option('database', 'default');
      $url =  is_array($url) ? $url[$database] : $url;
      $db_spec = drush_convert_db_from_db_url($url);
      $db_spec['db_prefix'] = isset($GLOBALS['db_prefix']) ? $GLOBALS['db_prefix'] : drush_get_option('db-prefix', NULL);
      // For uniformity with code designed for Drupal 7/8 db_specs, copy the 'db_prefix' to 'prefix'.
      $db_spec['prefix'] = $db_spec['db_prefix'];
    }
    return $db_spec;
  }

  public function getAll() {
    if (isset($GLOBALS['db_url'])) {
      return drush_sitealias_convert_db_from_db_url($GLOBALS['db_url']);
    }
  }

  public function valid_credentials($db_spec) {
    $type = $db_spec['driver'];
    // Check for Drupal support of configured db type.
    if (file_exists('./includes/install.'. $type .'.inc')) {
      require_once './includes/install.'. $type .'.inc';
      $function = $type .'_is_available';
      if (!$function()) {
        drush_log(dt('!type extension for PHP is not installed. Check your php.ini to see how you can enable it.', array('!type' => $type)), LogLevel::BOOTSTRAP);
        return FALSE;
      }
    }
    else {
      drush_log(dt('!type database type is unsupported.', array('!type' => $type)), LogLevel::BOOTSTRAP);
      return FALSE;
    }
    return TRUE;
  }

}
<?php

namespace Drush\Sql;

class Sql7 extends SqlVersion {
  public function get_db_spec() {
    $db_spec = NULL;
    drush_sql_bootstrap_database_configuration();
    $database = drush_get_option('database', 'default');
    $target = drush_get_option('target', 'default');
    // We don't use DB API here `sql-sync` would have to messily addConnection.
    if (!isset($GLOBALS['databases']) || !array_key_exists($database, $GLOBALS['databases']) || !array_key_exists($target, $GLOBALS['databases'][$database])) {
      // Do nothing
    }
    else {
      $db_spec = $GLOBALS['databases'][$database][$target];;
    }
    return $db_spec;
  }

  public function getAll() {
    if (isset($GLOBALS['databases'])) {
      return $GLOBALS['databases'];
    }
  }
}
<?php
namespace Drush\Sql;

use Drupal\Core\Database\Database;

class Sql8 extends Sql7 {
  public function get_db_spec() {
    $db_spec = NULL;
    if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) {
      $database = drush_get_option('database', 'default');
      $target = drush_get_option('target', 'default');
      if ($info = Database::getConnectionInfo($database)) {
        return $info[$target];
      }
    }
    return $db_spec;
  }

  public function getAll() {
    return Database::getAllConnectionInfo();
  }
}
<?php

namespace Drush\Sql;

use Drupal\Core\Database\Database;
use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;

class SqlBase {

  // An Drupal style array containing specs for connecting to database.
  public $db_spec;

  // Default code appended to sql-query connections.
  public $query_extra = '';

  // The way you pass a sql file when issueing a query.
  public $query_file = '<';

  /**
   * Typically, SqlBase objects are contructed via drush_sql_get_class().
   */
  public function __construct($db_spec = NULL) {
    $this->db_spec = $db_spec;
  }

  /*
   * Get the current $db_spec.
   */
  public function db_spec() {
    return $this->db_spec;
  }

  /**
   * The unix command used to connect to the database.
   * @return string
   */
  public function command() {}

  /**
   * A string for connecting to a database.
   *
   * @param bool $hide_password
   *  If TRUE, DBMS should try to hide password from process list.
   *  On mysql, that means using --defaults-extra-file to supply the user+password.
   *
   * @return string
   */
  public function connect($hide_password = TRUE) {
    return trim($this->command() . ' ' . $this->creds($hide_password) . ' ' . drush_get_option('extra', $this->query_extra));
  }


  /*
   * Execute a SQL dump and return the path to the resulting dump file.
   *
   * @param string|bool @file
   *   The path where the dump file should be stored. If TRUE, generate a path
   *   based on usual backup directory and current date.
   */
  public function dump($file = '') {
    $file_suffix = '';
    $table_selection = $this->get_expanded_table_selection();
    $file = $this->dumpFile($file);
    $cmd = $this->dumpCmd($table_selection);
    // Gzip the output from dump command(s) if requested.
    if (drush_get_option('gzip')) {
      $cmd .= ' | gzip -f';
      $file_suffix .= '.gz';
    }
    if ($file) {
      $file .= $file_suffix;
      $cmd .= ' > ' . drush_escapeshellarg($file);
    }

    // Avoid the php memory of the $output array in drush_shell_exec().
    if (!$return = drush_op_system($cmd)) {
      if ($file) {
        drush_log(dt('Database dump saved to !path', array('!path' => $file)), LogLevel::SUCCESS);
        drush_backend_set_result($file);
      }
    }
    else {
      return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed');
    }
  }

  /*
   * Build bash for dumping a database.
   *
   * @param array $table_selection
   *   Supported keys: 'skip', 'structure', 'tables'.
   * @return string
   *   One or more mysqldump/pg_dump/sqlite3/etc statements that are ready for executing.
   *   If multiple statements are needed, enclose in parenthesis.
   */
  public function dumpCmd($table_selection) {}

  /*
   * Generate a path to an output file for a SQL dump when needed.
   *
   * @param string|bool @file
   *   If TRUE, generate a path based on usual backup directory and current date.
   *   Otherwise, just return the path that was provided.
   */
  public function dumpFile($file) {
    $database = $this->db_spec['database'];

    // $file is passed in to us usually via --result-file.  If the user
    // has set $options['result-file'] = TRUE, then we
    // will generate an SQL dump file in the same backup
    // directory that pm-updatecode uses.
    if ($file) {
      if ($file === TRUE) {
        // User did not pass a specific value for --result-file. Make one.
        $backup = drush_include_engine('version_control', 'backup');
        $backup_dir = $backup->prepare_backup_dir($database);
        if (empty($backup_dir)) {
          $backup_dir = drush_find_tmp();
        }
        $file = Path::join($backup_dir, '@DATABASE_@DATE.sql');
      }
      $file = str_replace(array('@DATABASE', '@DATE'), array($database, gmdate('Ymd_His')), $file);
    }
    return $file;
  }

  /**
   * Execute a SQL query.
   *
   * Note: This is an API function. Try to avoid using drush_get_option() and instead
   * pass params in. If you don't want to query results to print during --debug then
   * provide a $result_file whose value can be drush_bit_bucket().
   *
   * @param string $query
   *   The SQL to be executed. Should be NULL if $input_file is provided.
   * @param string $input_file
   *   A path to a file containing the SQL to be executed.
   * @param string $result_file
   *   A path to save query results to. Can be drush_bit_bucket() if desired.
   *
   * @return
   *   TRUE on success, FALSE on failure
   */
  public function query($query, $input_file = NULL, $result_file = '') {
    $input_file_original = $input_file;
    if ($input_file && drush_file_is_tarball($input_file)) {
      if (drush_shell_exec('gzip -d %s', $input_file)) {
        $input_file = trim($input_file, '.gz');
      }
      else {
        return drush_set_error(dt('Failed to decompress input file.'));
      }
    }

    // Save $query to a tmp file if needed. We will redirect it in.
    if (!$input_file) {
      $query = $this->query_prefix($query);
      $query = $this->query_format($query);
      $input_file = drush_save_data_to_temp_file($query);
    }

    $parts = array(
      $this->command(),
      $this->creds(),
      $this->silent(), // This removes column header and various helpful things in mysql.
      drush_get_option('extra', $this->query_extra),
      $this->query_file,
      drush_escapeshellarg($input_file),
    );
    $exec = implode(' ', $parts);

    if ($result_file) {
      $exec .= ' > '. drush_escapeshellarg($result_file);
    }

    // In --verbose mode, drush_shell_exec() will show the call to mysql/psql/sqlite,
    // but the sql query itself is stored in a temp file and not displayed.
    // We show the query when --debug is used and this function created the temp file.
    if ((drush_get_context('DRUSH_DEBUG') || drush_get_context('DRUSH_SIMULATE')) && empty($input_file_original)) {
      drush_log('sql-query: ' . $query, LogLevel::NOTICE);
    }

    $success = drush_shell_exec($exec);

    if ($success && drush_get_option('file-delete')) {
      drush_op('drush_delete_dir', $input_file);
    }

    return $success;
  }

  /*
   * A string to add to the command when queries should not print their results.
   */
  public function silent() {}


  public function query_prefix($query) {
    // Inject table prefixes as needed.
    if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) {
      // Enable prefix processing which can be dangerous so off by default. See http://drupal.org/node/1219850.
      if (drush_get_option('db-prefix')) {
        if (drush_drupal_major_version() >= 7) {
          $query = Database::getConnection()->prefixTables($query);
        }
        else {
          $query = db_prefix_tables($query);
        }
      }
    }
    return $query;
  }


  public function query_format($query) {
    return $query;
  }

  /**
   * Drop specified database.
   *
   * @param array $tables
   *   An array of table names
   * @return boolean
   *   True if successful, FALSE if failed.
   */
  public function drop($tables) {
    $return = TRUE;
    if ($tables) {
      $sql = 'DROP TABLE '. implode(', ', $tables);
      $return = $this->query($sql);
    }
    return $return;
  }

  /**
   * Build a SQL string for dropping and creating a database.
   *
   * @param string dbname
   *   The database name.
   * @param boolean $quoted
   *   Quote the database name. Mysql uses backticks to quote which can cause problems
   *   in a Windows shell. Set TRUE if the CREATE is not running on the bash command line.
   */
  public function createdb_sql($dbname, $quoted = FALSE) {}

  /**
   * Create a new database.
   *
   * @param boolean $quoted
   *   Quote the database name. Mysql uses backticks to quote which can cause problems
   *   in a Windows shell. Set TRUE if the CREATE is not running on the bash command line.
   * @return boolean
   *   True if successful, FALSE otherwise.
   */
  public function createdb($quoted = FALSE) {
    $dbname = $this->db_spec['database'];
    $sql = $this->createdb_sql($dbname, $quoted);
    // Adjust connection to allow for superuser creds if provided.
    $this->su();
    return $this->query($sql);
  }

  /**
   * Drop all tables (if DB exists) or CREATE target database.
   *
   * return boolean
   *   TRUE or FALSE depending on success.
   */
  public function drop_or_create() {
    if ($this->db_exists()) {
      return $this->drop($this->listTables());
    }
    else {
      return $this->createdb();
    }
  }

  /*
   * Determine if the specified DB already exists.
   *
   * @return bool
   */
  public function db_exists() {}

  public function delete() {}

  /**
   * Build a fragment connection parameters.
   *
   * @param bool $hide_password
   *  If TRUE, DBMS should try to hide password from process list.
   *  On mysql, that means using --defaults-extra-file to supply the user+password.
   * @return string
   */
  public function creds($hide_password = TRUE) {}

  /**
   * The active database driver.
   * @return string
   */
  public function scheme() {
    return $this->db_spec['driver'];
  }

  /**
   * Get a list of all table names and expand input that may contain
   * wildcards (`*`) if necessary so that the array returned only contains valid
   * table names i.e. actual tables that exist, without a wildcard.
   *
   * @return array
   *   An array of tables with each table name in the appropriate
   *   element of the array.
   */
  public function get_expanded_table_selection() {
    $table_selection = drush_sql_get_table_selection();
    // Get the existing table names in the specified database.
    $db_tables = $this->listTables();
    if (isset($table_selection['skip'])) {
      $table_selection['skip'] = _drush_sql_expand_and_filter_tables($table_selection['skip'], $db_tables);
    }
    if (isset($table_selection['structure'])) {
      $table_selection['structure'] = _drush_sql_expand_and_filter_tables($table_selection['structure'], $db_tables);
    }
    if (isset($table_selection['tables'])) {
      $table_selection['tables'] = _drush_sql_expand_and_filter_tables($table_selection['tables'], $db_tables);
    }
    return $table_selection;
  }

  /**
   * Extract the name of all existing tables in the given database.
   *
   * @return array
   *   An array of table names which exist in the current database.
   */
  public function listTables() {}

  /*
   * Helper method to turn associative array into options with values.
   *
   * @return string
   *   A bash fragment.
   */
  public function params_to_options($parameters) {
    // Turn each parameter into a valid parameter string.
    $parameter_strings = array();
    foreach ($parameters as $key => $value) {
      // Only escape the values, not the keys or the rest of the string.
      $value = drush_escapeshellarg($value);
      $parameter_strings[] = "--$key=$value";
    }

    // Join the parameters and return.
    return implode(' ', $parameter_strings);
  }

  /**
   * Adjust DB connection with superuser credentials if provided.
   *
   * The options 'db-su' and 'db-su-pw' will be retreived from the
   * specified site alias record, if it exists and contains those items.
   * If it does not, they will be fetched via drush_get_option.
   *
   * Note that in the context of sql-sync, the site alias record will
   * be taken from the target alias (e.g. `drush sql-sync @source @target`),
   * which will be overlayed with any options that begin with 'target-';
   * therefore, the commandline options 'target-db-su' and 'target-db-su-pw'
   * may also affect the operation of this function.
   *
   * @return null
   */
  public function su() {
    $create_db_target = $this->db_spec;

    $create_db_target['database'] = '';
    $db_superuser = drush_get_option('db-su');
    if (isset($db_superuser)) {
      $create_db_target['username'] = $db_superuser;
    }
    $db_su_pw = drush_get_option('db-su-pw');
    // If --db-su-pw is not provided and --db-su is, default to empty password.
    // This way db cli command will take password from .my.cnf or .pgpass.
    if (!empty($db_su_pw)) {
      $create_db_target['password'] = $db_su_pw;
    }
    elseif (isset($db_superuser)) {
      unset($create_db_target['password']);
    }
    $this->db_spec = $create_db_target;
  }
}
<?php

namespace Drush\Sql;

class SqlException extends \Exception {}
<?php

namespace Drush\Sql;

use Drush\Log\LogLevel;

class SqlVersion {
  /*
   * Determine $db_spec by inspecting the global environment (D6/7) or the DB API (D8+).
   *
   * @return array $db_spec
   *   An array specifying a database connection.
   */
  public function get_db_spec() {}

  /*
   * Return all configured DB connections by inspecting the global environment (D6/7) or the DB API (D8+).
   *
   * @return array $all
   *   An array specifying one or more database connections.
   */
  public function getAll() {}

  /*
   * Validate that Drupal can connect to the DB without actually using Drupal to do so. Called
   * by drush_valid_db_credentials().
   */
  public function valid_credentials($db_spec) {
    // Drupal >=7 requires PDO and Drush requires php 5.4+ which ships with PDO
    // but it may be compiled with --disable-pdo.
    if (!class_exists('\PDO')) {
      drush_log(dt('PDO support is required.'), LogLevel::BOOTSTRAP);
      return FALSE;
    }
    return TRUE;
  }
}
<?php

namespace Drush\Sql;

use PDO;

class Sqlmysql extends SqlBase {

  public function command() {
    return 'mysql';
  }

  public function creds($hide_password = TRUE) {
    if ($hide_password) {
      // EMPTY password is not the same as NO password, and is valid.
      $contents = <<<EOT
#This file was written by Drush's Sqlmysql.php.
[client]
user="{$this->db_spec['username']}"
password="{$this->db_spec['password']}"
EOT;

      $file = drush_save_data_to_temp_file($contents);
      $parameters['defaults-extra-file'] = $file;
    }
    else {
      // User is required. Drupal calls it 'username'. MySQL calls it 'user'.
      $parameters['user'] = $this->db_spec['username'];
      // EMPTY password is not the same as NO password, and is valid.
      if (isset($this->db_spec['password'])) {
        $parameters['password'] = $this->db_spec['password'];
      }
    }

    // Some drush commands (e.g. site-install) want to connect to the
    // server, but not the database.  Connect to the built-in database.
    $parameters['database'] = empty($this->db_spec['database']) ? 'information_schema' : $this->db_spec['database'];

    // Default to unix socket if configured.
    if (!empty($this->db_spec['unix_socket'])) {
      $parameters['socket'] = $this->db_spec['unix_socket'];
    }
    // EMPTY host is not the same as NO host, and is valid (see unix_socket).
    elseif (isset($this->db_spec['host'])) {
      $parameters['host'] = $this->db_spec['host'];
    }

    if (!empty($this->db_spec['port'])) {
      $parameters['port'] = $this->db_spec['port'];
    }

    if (!empty($this->db_spec['pdo']['unix_socket'])) {
      $parameters['socket'] = $this->db_spec['pdo']['unix_socket'];
    }

    if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CA])) {
      $parameters['ssl-ca'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CA];
    }

    if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CAPATH])) {
      $parameters['ssl-capath'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CAPATH];
    }

    if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CERT])) {
      $parameters['ssl-cert'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CERT];
    }

    if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CIPHER])) {
      $parameters['ssl-cipher'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_CIPHER];
    }

    if (!empty($this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_KEY])) {
      $parameters['ssl-key'] = $this->db_spec['pdo'][PDO::MYSQL_ATTR_SSL_KEY];
    }

    return $this->params_to_options($parameters);
  }

  public function silent() {
    return '--silent';
  }

  public function createdb_sql($dbname, $quoted = FALSE) {
    if ($quoted) {
      $dbname = '`' . $dbname . '`';
    }
    $sql[] = sprintf('DROP DATABASE IF EXISTS %s;', $dbname);
    $sql[] = sprintf('CREATE DATABASE %s /*!40100 DEFAULT CHARACTER SET utf8 */;', $dbname);
    $db_superuser = drush_get_option('db-su');
    if (isset($db_superuser)) {
      // - For a localhost database, create a localhost user.  This is important for security.
      //   localhost is special and only allows local Unix socket file connections.
      // - If the database is on a remote server, create a wilcard user with %.
      //   We can't easily know what IP adderss or hostname would represent our server.
      $domain = ($this->db_spec['host'] == 'localhost') ? 'localhost' : '%';
      $sql[] = sprintf('GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@\'%s\'', $dbname, $this->db_spec['username'], $domain);
      $sql[] = sprintf("IDENTIFIED BY '%s';", $this->db_spec['password']);
      $sql[] = 'FLUSH PRIVILEGES;';
    }
    return implode(' ', $sql);
  }

  public function db_exists() {
    $current = drush_get_context('DRUSH_SIMULATE');
    drush_set_context('DRUSH_SIMULATE', FALSE);
    // Suppress output. We only care about return value.
    $return = $this->query("SELECT 1;", NULL, drush_bit_bucket());
    drush_set_context('DRUSH_SIMULATE', $current);
    return $return;
  }

  public function listTables() {
    $current = drush_get_context('DRUSH_SIMULATE');
    drush_set_context('DRUSH_SIMULATE', FALSE);
    $return = $this->query('SHOW TABLES;');
    $tables = drush_shell_exec_output();
    drush_set_context('DRUSH_SIMULATE', $current);
    return $tables;
  }

  public function dumpCmd($table_selection) {
    $parens = FALSE;
    $skip_tables = $table_selection['skip'];
    $structure_tables = $table_selection['structure'];
    $tables = $table_selection['tables'];

    $ignores = array();
    $skip_tables  = array_merge($structure_tables, $skip_tables);
    $data_only = drush_get_option('data-only');
    // The ordered-dump option is only supported by MySQL for now.
    // @todo add documention once a hook for drush_get_option_help() is available.
    // @see drush_get_option_help() in drush.inc
    $ordered_dump = drush_get_option('ordered-dump');

    $exec = 'mysqldump ';
    // mysqldump wants 'databasename' instead of 'database=databasename' for no good reason.
    $only_db_name = str_replace('--database=', ' ', $this->creds());
    $exec .= $only_db_name;

    // We had --skip-add-locks here for a while to help people with insufficient permissions,
    // but removed it because it slows down the import a lot.  See http://drupal.org/node/1283978
    $extra = ' --no-autocommit --single-transaction --opt -Q';
    if (isset($data_only)) {
      $extra .= ' --no-create-info';
    }
    if (isset($ordered_dump)) {
      $extra .= ' --skip-extended-insert --order-by-primary';
    }
    if ($option = drush_get_option('extra', $this->query_extra)) {
      $extra .= " $option";
    }
    $exec .= $extra;

    if (!empty($tables)) {
      $exec .= ' ' . implode(' ', $tables);
    }
    else {
      // Append the ignore-table options.
      foreach ($skip_tables as $table) {
        $ignores[] = '--ignore-table=' . $this->db_spec['database'] . '.' . $table;
        $parens = TRUE;
      }
      $exec .= ' '. implode(' ', $ignores);

      // Run mysqldump again and append output if we need some structure only tables.
      if (!empty($structure_tables)) {
        $exec .= " && mysqldump " . $only_db_name . " --no-data $extra " . implode(' ', $structure_tables);
        $parens = TRUE;
      }
    }
    return $parens ? "($exec)" : $exec;
  }
}
<?php

namespace Drush\Sql;

use Drush\Log\LogLevel;

class Sqloracle extends SqlBase {

  // The way you pass a sql file when issueing a query.
  public $query_file = '@';

  public function command() {
    // use rlwrap if available for readline support
    if ($handle = popen('rlwrap -v', 'r')) {
      $command = 'rlwrap sqlplus';
      pclose($handle);
    }
    else {
      $command = 'sqlplus';
    }
    return $command;
  }

  public function creds() {
    return ' ' . $this->db_spec['username'] . '/' . $this->db_spec['password'] . ($this->db_spec['host'] == 'USETNS' ? '@' . $this->db_spec['database'] : '@//' . $this->db_spec['host'] . ':' . ($db_spec['port'] ? $db_spec['port'] : '1521') . '/' . $this->db_spec['database']);
  }

  public function createdb_sql($dbname) {
    return drush_log("Unable to generate CREATE DATABASE sql for $dbname", LogLevel::ERROR);
  }

  // @todo $suffix = '.sql';
  public function query_format($query) {
    // remove trailing semicolon from query if we have it
    $query = preg_replace('/\;$/', '', $query);

    // some sqlplus settings
    $settings[] = "set TRIM ON";
    $settings[] = "set FEEDBACK OFF";
    $settings[] = "set UNDERLINE OFF";
    $settings[] = "set PAGES 0";
    $settings[] = "set PAGESIZE 50000";

    // are we doing a describe ?
    if (!preg_match('/^ *desc/i', $query)) {
      $settings[] = "set LINESIZE 32767";
    }

    // are we doing a show tables ?
    if (preg_match('/^ *show tables/i', $query)) {
      $settings[] = "set HEADING OFF";
      $query = "select object_name from user_objects where object_type='TABLE' order by object_name asc";
    }

    // create settings string
    $sqlp_settings = implode("\n", $settings) . "\n";

    // important for sqlplus to exit correctly
    return "${sqlp_settings}${query};\nexit;\n";
  }

  public function listTables() {
    $return = $this->query("SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')");
    $tables = drush_shell_exec_output();
    if (!empty($tables)) {
      // Shift off the header of the column of data returned.
      array_shift($tables);
      return $tables;
    }
  }

  // @todo $file is no longer provided. We are supposed to return bash that can be piped to gzip.
  // Probably Oracle needs to override dump() entirely - http://stackoverflow.com/questions/2236615/oracle-can-imp-exp-go-to-stdin-stdout.
  public function dumpCmd($table_selection) {
    $create_db = drush_get_option('create-db');
    $exec = 'exp ' . $this->creds();
    // Change variable '$file' by reference in order to get drush_log() to report.
    if (!$file) {
      $file = $this->db_spec['username'] . '.dmp';
    }
    $exec .= ' file=' . $file;

    if (!empty($tables)) {
      $exec .= ' tables="(' . implode(',', $tables) . ')"';
    }
    $exec .= ' owner=' . $this->db_spec['username'];
    if ($option = drush_get_option('extra', $this->query_extra)) {
      $exec .= " $option";
    }
    return array($exec, $file);
  }
}
<?php

namespace Drush\Sql;

define('PSQL_SHOW_TABLES', "SELECT tablename FROM pg_tables WHERE schemaname='public';");

class Sqlpgsql extends SqlBase {

  public $query_extra = "--no-align --field-separator=\"\t\" --pset tuples_only=on";

  public $query_file = "--file";

  private $password_file = NULL;

  private function password_file() {
    if (!isset($password_file) && isset($this->db_spec['password'])) {
      $pgpass_parts = array(
        empty($this->db_spec['host']) ? 'localhost' : $this->db_spec['host'],
        empty($this->db_spec['port']) ? '5432' : $this->db_spec['port'],
        // Database
        '*',
        $this->db_spec['username'],
        $this->db_spec['password']
      );
      // Escape colon and backslash characters in entries.
      // @see http://www.postgresql.org/docs/9.1/static/libpq-pgpass.html
      array_walk($pgpass_parts, function (&$part) {
        // The order of the replacements is important so that backslashes are
        // not replaced twice.
        $part = str_replace(array('\\', ':'), array('\\\\', '\:'), $part);
      });
      $pgpass_contents = implode(':', $pgpass_parts);
      $password_file = drush_save_data_to_temp_file($pgpass_contents);
      chmod($password_file, 0600);
    }
    return $password_file;
  }

  public function command() {
    $environment = "";
    $pw_file = $this->password_file();
    if (isset($pw_file)) {
      $environment = "PGPASSFILE={$pw_file} ";
    }
    return "{$environment}psql -q";
  }

  /*
   * @param $hide_password
   *   Not used in postgres. Use .pgpass file instead. See http://drupal.org/node/438828.
   */
  public function creds($hide_password = TRUE) {
    // Some drush commands (e.g. site-install) want to connect to the
    // server, but not the database.  Connect to the built-in database.
    $parameters['dbname'] = empty($this->db_spec['database']) ? 'template1' : $this->db_spec['database'];

    // Host and port are optional but have defaults.
    $parameters['host'] = empty($this->db_spec['host']) ? 'localhost' : $this->db_spec['host'];
    $parameters['port'] = empty($this->db_spec['port']) ? '5432' : $this->db_spec['port'];

    // Username is required.
    $parameters['username'] = $this->db_spec['username'];

    // Don't set the password.
    // @see http://drupal.org/node/438828

    return $this->params_to_options($parameters);
  }

  public function createdb_sql($dbname, $quoted = FALSE) {
    if ($quoted) {
      $dbname = '`' . $dbname . '`';
    }
    $sql[] = sprintf('drop database if exists %s;', $dbname);
    $sql[] = sprintf("create database %s ENCODING 'UTF8';", $dbname);
    return implode(' ', $sql);
  }

  public function db_exists() {
    $database = $this->db_spec['database'];
    // Get a new class instance that has no 'database'.
    $db_spec_no_db = $this->db_spec;
    unset($db_spec_no_db['database']);
    $sql_no_db = drush_sql_get_class($db_spec_no_db);
    $query = "SELECT 1 AS result FROM pg_database WHERE datname='$database'";
    drush_shell_exec($sql_no_db->connect() . ' -t -c %s', $query);
    $output = drush_shell_exec_output();
    return (bool)$output[0];
  }

  public function query_format($query) {
    if (strtolower($query) == 'show tables;') {
      return PSQL_SHOW_TABLES;
    }
    return $query;
  }

  public function listTables() {
    $return = $this->query(PSQL_SHOW_TABLES);
    $tables = drush_shell_exec_output();
    if (!empty($tables)) {
      return $tables;
    }
    return array();
  }

  public function dumpCmd($table_selection) {
    $parens = FALSE;
    $skip_tables = $table_selection['skip'];
    $structure_tables = $table_selection['structure'];
    $tables = $table_selection['tables'];

    $ignores = array();
    $skip_tables  = array_merge($structure_tables, $skip_tables);
    $data_only = drush_get_option('data-only');

    $create_db = drush_get_option('create-db');
    $exec = 'pg_dump ';
    // Unlike psql, pg_dump does not take a '--dbname=' before the database name.
    $extra = str_replace('--dbname=', ' ', $this->creds());
    if (isset($data_only)) {
      $extra .= ' --data-only';
    }
    if ($option = drush_get_option('extra', $this->query_extra)) {
      $extra .= " $option";
    }
    $exec .= $extra;
    $exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : '');

    if (!empty($tables)) {
      foreach ($tables as $table) {
        $exec .= " --table=$table";
      }
    }
    else {
      foreach ($skip_tables as $table) {
        $ignores[] = "--exclude-table=$table";
      }
      $exec .= ' '. implode(' ', $ignores);
      // Run pg_dump again and append output if we need some structure only tables.
      if (!empty($structure_tables)) {
        $parens = TRUE;
        $schemaonlies = array();
        foreach ($structure_tables as $table) {
          $schemaonlies[] = "--table=$table";
        }
        $exec .= " && pg_dump --schema-only " . implode(' ', $schemaonlies) . $extra;
        $exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : '');
      }
    }
    return $parens ? "($exec)" : $exec;
  }
}
<?php

namespace Drush\Sql;

use Drush\Log\LogLevel;

class Sqlsqlite extends SqlBase {
  public function command() {
    return 'sqlite3';
  }

  public function creds($hide_password = TRUE) {
    // SQLite doesn't do user management, instead relying on the filesystem
    // for that. So the only info we really need is the path to the database
    // file, and not as a "--key=value" parameter.
    return ' '  .  $this->db_spec['database'];
  }

  public function createdb_sql($dbname, $quoted = false) {
    return '';
  }

  /**
   * Create a new database.
   *
   * @param boolean $quoted
   *   Quote the database name. Mysql uses backticks to quote which can cause problems
   *   in a Windows shell. Set TRUE if the CREATE is not running on the bash command line.
   */
  public function createdb($quoted = FALSE) {
    $file = $this->db_spec['database'];
    if (file_exists($file)) {
      drush_log("SQLITE: Deleting existing database '$file'", LogLevel::DEBUG);
      drush_delete_dir($file, TRUE);
    }

    // Make sure sqlite can create file
    $path = dirname($file);
    drush_log("SQLITE: creating '$path' for creating '$file'", LogLevel::DEBUG);
    drush_mkdir($path);
    if (!file_exists($path)) {
      drush_log("SQLITE: Cannot create $path", LogLevel::ERROR);
      return FALSE;
    }
    else {
      return TRUE;
    }
  }

  public function db_exists() {
    return file_exists($this->db_spec['database']);
  }

  public function listTables() {
    $return = $this->query('.tables');
    $tables_raw = drush_shell_exec_output();
    // SQLite's '.tables' command always outputs the table names in a column
    // format, like this:
    // table_alpha    table_charlie    table_echo
    // table_bravo    table_delta      table_foxtrot
    // …and there doesn't seem to be a way to fix that. So we need to do some
    // clean-up.
    foreach ($tables_raw as $line) {
      preg_match_all('/[^\s]+/', $line, $matches);
      if (!empty($matches[0])) {
        foreach ($matches[0] as $match) {
          $tables[] = $match;
        }
      }
    }
    return $tables;
  }

  public function drop($tables) {
    $sql = '';
    // SQLite only wants one table per DROP TABLE command (so we have to do
    // "DROP TABLE foo; DROP TABLE bar;" instead of "DROP TABLE foo, bar;").
    foreach ($tables as $table) {
      $sql .= "DROP TABLE $table; ";
    }
    return $this->query($sql);
  }

  public function dumpCmd($table_selection) {
    // Dumping is usually not necessary in SQLite, since all database data
    // is stored in a single file which can be copied just
    // like any other file. But it still has a use in migration purposes and
    // building human-readable diffs and such, so let's do it anyway.
    $exec = $this->connect();
    // SQLite's dump command doesn't support many of the features of its
    // Postgres or MySQL equivalents. We may be able to fake some in the
    // future, but for now, let's just support simple dumps.
    $exec .= ' ".dump"';
    if ($option = drush_get_option('extra', $this->query_extra)) {
      $exec .= " $option";
    }
    return $exec;
  }


}
<?php

namespace Drush\Sql;

class Sqlsqlsrv extends SqlBase {

  // The way you pass a sql file when issueing a query.
  public $query_file = '-h -1 -i';

  public function command() {
    return 'sqlcmd';
  }

  public function creds() {
    // Some drush commands (e.g. site-install) want to connect to the
    // server, but not the database.  Connect to the built-in database.
    $database = empty($this->db_spec['database']) ? 'master' : $this->db_spec['database'];
    // Host and port are optional but have defaults.
    $host = empty($this->db_spec['host']) ? '.\SQLEXPRESS' : $this->db_spec['host'];
    if ($this->db_spec['username'] == '') {
      return ' -S ' . $host . ' -d ' . $database;
    }
    else {
      return ' -S ' . $host . ' -d ' . $database . ' -U ' . $this->db_spec['username'] . ' -P ' . $this->db_spec['password'];
    }
  }

  public function db_exists() {
    // TODO: untested, but the gist is here.
    $database = $this->db_spec['database'];
    // Get a new class instance that has no 'database'.
    $db_spec_no_db = $this->db_spec;
    unset($db_spec_no_db['database']);
    $sql_no_db = drush_sql_get_class($db_spec_no_db);
    $query = "if db_id('$database') IS NOT NULL print 1";
    drush_shell_exec($sql_no_db->connect() . ' -Q %s', $query);
    $output = drush_shell_exec_output();
    return $output[0] == 1;
  }

  public function listTables() {
    $return = $this->query('SELECT TABLE_NAME FROM information_schema.tables');
    $tables = drush_shell_exec_output();
    if (!empty($tables)) {
      // Shift off the header of the column of data returned.
      array_shift($tables);
      return $tables;
    }
  }

  // @todo $file is no longer provided. We are supposed to return bash that can be piped to gzip.
  // Probably sqlsrv needs to override dump() entirely.
  public function dumpCmd($table_selection) {
    if (!$file) {
      $file = $this->db_spec['database'] . '_' . date('Ymd_His') . '.bak';
    }
    $exec = "sqlcmd -U \"" . $this->db_spec['username'] . "\" -P \"" . $this->db_spec['password'] . "\" -S \"" . $this->db_spec['host'] . "\" -Q \"BACKUP DATABASE [" . $this->db_spec['database'] . "] TO DISK='" . $file . "'\"";
    if ($option = drush_get_option('extra', $this->query_extra)) {
      $exec .= " $option";
    }
    return array($exec, $file);
  }


}
<?php

namespace Drush\UpdateService;

use Drush\Log\LogLevel;

/**
 * Representation of a project's release info from the update service.
 */
class Project {
  private $parsed;


  /**
   * Constructor.
   *
   * @param string $project_name
   *    Project name.
   *
   * @param \SimpleXMLElement $xml
   *    XML data.
   */
  function __construct(\SimpleXMLElement $xml) {
    // Check if the xml contains an error on the project.
    if ($error = $xml->xpath('/error')) {
      $error = (string)$error[0];
      if (strpos($error, 'No release history available for') === 0) {
        $project_status = 'unsupported';
      }
      elseif (strpos($error, 'No release history was found for the requested project') === 0) {
        $project_status = 'unknown';
      }
      // Any other error we are not aware of.
      else {
        $project_status = 'unknown';
      }
    }
    // The xml has a project, but still it can have errors.
    else {
      $this->parsed = self::parseXml($xml);
      if (empty($this->parsed['releases'])) {
        $error = dt('No available releases found for the requested project (!name).', array('!name' => $this->parsed['short_name']));
        $project_status = 'unknown';
      }
      else {
        $error = FALSE;
        $project_status = $xml->xpath('/project/project_status');
        $project_status = (string)$project_status[0];
      }
    }

    $this->project_status = $project_status;
    $this->error = $error;
    if ($error) {
      drush_set_error('DRUSH_RELEASE_INFO_ERROR', $error);
    }
  }

  /**
   * Downloads release info xml from update service.
   *
   * @param array $request
   *   A request array.
   * @param int $cache_duration
   *   Cache lifetime.
   *
   * @return \Drush\UpdateService\Project
   */
  public static function getInstance(array $request, $cache_duration = ReleaseInfo::CACHE_LIFETIME) {
    $url = self::buildFetchUrl($request);
    drush_log(dt('Downloading release history from !url', array('!url' => $url)));

    $path = drush_download_file($url, drush_tempnam($request['name']), $cache_duration);
    $xml = simplexml_load_file($path);
    if (!$xml) {
      $error = dt('Failed to get available update data from !url', array('!url' => $url));
      return drush_set_error('DRUSH_RELEASE_INFO_ERROR', $error);
    }

    return new Project($xml);
  }

  /**
   * Returns URL to the updates service for the given request.
   *
   * @param array $request
   *   A request array.
   *
   * @return string
   *   URL to the updates service.
   *
   * @see \Drupal\update\UpdateFetcher::buildFetchUrl()
   */
  public static function buildFetchUrl(array $request) {
    $status_url = isset($request['status url']) ? $request['status url'] : ReleaseInfo::DEFAULT_URL;
    return $status_url . '/' . $request['name'] . '/' . $request['drupal_version'];
  }

  /**
   * Parses update service xml.
   *
   * @param \SimpleXMLElement $xml
   *   XML element from the updates service.
   *
   * @return array
   *   Project update information.
   */
  private static function parseXml(\SimpleXMLElement $xml) {
    $project_info = array();

    // Extract general project info.
    $items = array('title', 'short_name', 'dc:creator', 'type', 'api_version',
      'recommended_major', 'supported_majors', 'default_major',
      'project_status', 'link',
    );
    foreach ($items as $item) {
      if (array_key_exists($item, (array)$xml)) {
        $value = $xml->xpath($item);
        $project_info[$item] = (string)$value[0];
      }
    }

    // Parse project type.
    $project_types = array(
      'core' => 'project_core',
      'profile' => 'project_distribution',
      'module' => 'project_module',
      'theme' => 'project_theme',
      'theme engine' => 'project_theme_engine',
      'translation' => 'project_translation',
      'utility' => 'project_drupalorg',
    );
    $type = $project_info['type'];
    // Probably unused but kept for possible legacy compat.
    $type = ($type == 'profile-legacy') ? 'profile' : $type;
    $project_info['project_type'] = array_search($type, $project_types);

    // Extract project terms.
    $project_info['terms'] = array();
    if ($xml->terms) {
      foreach ($xml->terms->children() as $term) {
        $term_name = (string) $term->name;
        $term_value = (string) $term->value;
        if (!isset($project_info[$term_name])) {
          $project_info['terms'][$term_name] = array();
        }
        $project_info['terms'][$term_name][] = $term_value;
      }
    }

    // Extract and parse releases info.
    // In addition to the info in the update service, here we calculate
    // release statuses as Recommended, Security, etc.

    $recommended_major = empty($project_info['recommended_major']) ? '' : $project_info['recommended_major'];
    $supported_majors = empty($project_info['supported_majors']) ? array() : array_flip(explode(',', $project_info['supported_majors']));

    $items = array(
      'name', 'date', 'status', 'type',
      'version', 'tag', 'version_major', 'version_patch', 'version_extra',
      'release_link', 'download_link', 'mdhash', 'filesize',
    );

    $releases = array();
    $releases_xml = @$xml->xpath("/project/releases/release[status='published']");
    foreach ($releases_xml as $release) {
      $release_info = array();
      $statuses = array();

      // Extract general release info.
      foreach ($items as $item) {
        if (array_key_exists($item, $release)) {
          $value = $release->xpath($item);
          $release_info[$item] = (string)$value[0];
        }
      }

      // Extract release terms.
      $release_info['terms'] = array();
      if ($release->terms) {
        foreach ($release->terms->children() as $term) {
          $term_name = (string) $term->name;
          $term_value = (string) $term->value;
          if (!isset($release_info['terms'][$term_name])) {
            $release_info['terms'][$term_name] = array();
          }
          $release_info['terms'][$term_name][] = $term_value;

          // Add "Security" for security updates, and nothing
          // for the other kinds.
          if (strpos($term_value, "Security") !== FALSE) {
            $statuses[] = "Security";
          }
        }
      }

      // Extract files.
      $release_info['files'] = array();
      foreach ($release->files->children() as $file) {
        // Normalize keys to match the ones in the release info.
        $item = array(
          'download_link' => (string) $file->url,
          'date'          => (string) $file->filedate,
          'mdhash'        => (string) $file->md5,
          'filesize'      => (string) $file->size,
          'archive_type'  => (string) $file->archive_type,
        );
        if (!empty($file->variant)) {
          $item['variant'] = (string) $file->variant;
        }
        $release_info['files'][] = $item;
      }

      // Calculate statuses.
      if (array_key_exists($release_info['version_major'], $supported_majors)) {
        $statuses[] = "Supported";
        unset($supported_majors[$release_info['version_major']]);
      }
      if ($release_info['version_major'] == $recommended_major) {
        if (!isset($latest_version)) {
          $latest_version = $release_info['version'];
        }
        // The first stable version (no 'version extra') in the recommended major
        // is the recommended release
        if (empty($release_info['version_extra']) && (!isset($recommended_version))) {
          $statuses[] = "Recommended";
          $recommended_version = $release_info['version'];
        }
      }
      if (!empty($release_info['version_extra']) && ($release_info['version_extra'] == "dev")) {
        $statuses[] = "Development";
      }

      $release_info['release_status'] = $statuses;
      $releases[$release_info['version']] = $release_info;
    }

    // If there's no "Recommended major version", we want to recommend
    // the most recent release.
    if (!$recommended_major) {
      $latest_version = key($releases);
    }

    // If there is no -stable- release in the recommended major,
    // then take the latest version in the recommended major to be
    // the recommended release.
    if (!isset($recommended_version) && isset($latest_version)) {
      $recommended_version = $latest_version;
      $releases[$recommended_version]['release_status'][] = "Recommended";
    }

    $project_info['releases'] = $releases;
    if (isset($recommended_version)) {
      $project_info['recommended'] = $recommended_version;
    }

    return $project_info;
  }

  /**
   * Gets the project type.
   *
   * @return string
   *   Type of the project.
   */
  public function getType() {
    return $this->parsed['project_type'];
  }

  /**
   * Gets the project status in the update service.
   *
   * This is the project status in drupal.org: insecure, revoked, published etc.
   *
   * @return string
   */
  public function getStatus() {
    return $this->project_status;
  }

  /**
   * Whether this object represents a project in the update service or an error.
   */
  public function isValid() {
    return ($this->error === FALSE);
  }

  /**
   * Gets the parsed xml.
   *
   * @return array or FALSE if the xml has an error.
   */
  public function getInfo() {
    return (!$this->error) ? $this->parsed : FALSE;
  }

  /**
   * Helper to pick the best release in a list of candidates.
   *
   * The best one is the first stable release if there are stable
   * releases; otherwise, it will be the first of the candidates.
   *
   * @param array $releases
   *   Array of release arrays.
   *
   * @return array|bool
   */
  public static function getBestRelease(array $releases) {
    if (empty($releases)) {
      return FALSE;
    }
    else {
      // If there are releases found, let's try first to fetch one with no
      // 'version_extra'. Otherwise, use all.
      $stable_releases = array();
      foreach ($releases as $one_release) {
        if (!array_key_exists('version_extra', $one_release)) {
          $stable_releases[] = $one_release;
        }
      }
      if (!empty($stable_releases)) {
        $releases = $stable_releases;
      }
    }

    // First published release is just the first value in $releases.
    return reset($releases);
  }

  private function searchReleases($key, $value) {
    $releases = array();
    foreach ($this->parsed['releases'] as $version => $release) {
      if ($release['status'] == 'published' && isset($release[$key]) && strcmp($release[$key], $value) == 0) {
        $releases[$version] = $release;
      }
    }
    return $releases;
  }

  /**
   * Returns the specific release that matches the request version.
   *
   * @param string $version
   *    Version of the release to pick.
   * @return array|bool
   *    The release or FALSE if no version specified or no release found.
   */
  public function getSpecificRelease($version = NULL) {
    if (!empty($version)) {
      $matches = array();
      // See if we only have a branch version.
      if (preg_match('/^\d+\.x-(\d+)$/', $version, $matches)) {
        $releases = $this->searchReleases('version_major', $matches[1]);
      }
      else {
        // In some cases, the request only says something like '7.x-3.x' but the
        // version strings include '-dev' on the end, so we need to append that
        // here for the xpath to match below.
        if (substr($version, -2) == '.x') {
          $version .= '-dev';
        }
        $releases = $this->searchReleases('version', $version);
      }
      if (empty($releases)) {
        return FALSE;
      }
      return self::getBestRelease($releases);
    }
    return array();
  }

  /**
   * Pick the first dev release from XML list.
   *
   * @return array|bool
   *    The selected release xml object or FALSE.
   */
  public function getDevRelease() {
    $releases = $this->searchReleases('version_extra', 'dev');
    return self::getBestRelease($releases);
  }

  /**
   * Pick most appropriate release from XML list.
   *
   * @return array|bool
   *    The selected release xml object or FALSE.
   */
  public function getRecommendedOrSupportedRelease() {
    $majors = array();

    $recommended_major = empty($this->parsed['recommended_major']) ? 0 : $this->parsed['recommended_major'];
    if ($recommended_major != 0) {
      $majors[] = $this->parsed['recommended_major'];
    }
    if (!empty($this->parsed['supported_majors'])) {
      $supported = explode(',', $this->parsed['supported_majors']);
      foreach ($supported as $v) {
        if ($v != $recommended_major) {
          $majors[] = $v;
        }
      }
    }
    $releases = array();
    foreach ($majors as $major) {
      $releases = $this->searchReleases('version_major', $major);
      if (!empty($releases)) {
        break;
      }
    }

    return self::getBestRelease($releases);
  }

  /**
   * Comparison routine to order releases by date.
   *
   * @param array $a
   *   Release to compare.
   * @param array $b
   *   Release to compare.
   *
   * @return int
   * -1, 0 or 1 whether $a is greater, equal or lower than $b.
   */
  private static function compareDates(array $a, array $b) {
    if ($a['date'] == $b['date']) {
      return ($a['version_major'] > $b['version_major']) ? -1 : 1;
    }
    if ($a['version_major'] == $b['version_major']) {
      return ($a['date'] > $b['date']) ? -1 : 1;
    }
    return ($a['version_major'] > $b['version_major']) ? -1 : 1;
  }

  /**
   * Comparison routine to order releases by version.
   *
   * @param array $a
   *   Release to compare.
   * @param array $b
   *   Release to compare.
   *
   * @return int
   * -1, 0 or 1 whether $a is greater, equal or lower than $b.
   */
  private static function compareVersions(array $a, array $b) {
    $defaults = array(
      'version_patch' => '',
      'version_extra' => '',
      'date' => 0,
    );
    $a += $defaults;
    $b += $defaults;
    if ($a['version_major'] != $b['version_major']) {
      return ($a['version_major'] > $b['version_major']) ? -1 : 1;
    }
    else if ($a['version_patch'] != $b['version_patch']) {
      return ($a['version_patch'] > $b['version_patch']) ? -1 : 1;
    }
    else if ($a['version_extra'] != $b['version_extra']) {
      // Don't rely on version_extra alphabetical order.
      return ($a['date'] > $b['date']) ? -1 : 1;
    }

    return 0;
  }

  /**
   * Filter project releases by a criteria and returns a list.
   *
   * If no filter is provided, the first Recommended, Supported, Security
   * or Development release on each major version will be shown.
   *
   * @param string $filter
   *   Valid values:
   *     - 'all': Select all releases.
   *     - 'dev': Select all development releases.
   * @param string $installed_version
   *   Version string. If provided, Select all releases in the same
   *   version_major branch until the provided one is found.
   *   On any other branch, the default behaviour will be applied.
   *
   * @return array
   *   List of releases matching the filter criteria.
   */
  function filterReleases($filter = '', $installed_version = NULL) {
    $releases = $this->parsed['releases'];
    usort($releases, array($this, 'compareDates'));

    $installed_version = pm_parse_version($installed_version);

    // Iterate through and filter out the releases we're interested in.
    $options = array();
    $limits_list = array();
    foreach ($releases as $release) {
      $eligible = FALSE;

      // Mark as eligible if the filter criteria matches.
      if ($filter == 'all') {
        $eligible = TRUE;
      }
      elseif ($filter == 'dev') {
        if (!empty($release['version_extra']) && ($release['version_extra'] == 'dev')) {
          $eligible = TRUE;
        }
      }
      // The Drupal core version scheme (ex: 7.31) is different to
      // other projects (ex 7.x-3.2). We need to manage this special case.
      elseif (($this->getType() != 'core') && ($installed_version['version_major'] == $release['version_major'])) {
        // In case there's no filter, select all releases until the installed one.
        // Always show the dev release.
        if (isset($release['version_extra']) && ($release['version_extra'] == 'dev')) {
          $eligible = TRUE;
        }
        else {
          if (self::compareVersions($release, $installed_version) < 1) {
            $eligible = TRUE;
          }
        }
      }
      // Otherwise, pick only the first release in each status.
      // For example after we pick out the first security release,
      // we won't pick any other. We do this on a per-major-version basis,
      // though, so if a project has three major versions, then we will
      // pick out the first security release from each.
      else {
        foreach ($release['release_status'] as $one_status) {
          $test_key = $release['version_major'] . $one_status;
          if (empty($limits_list[$test_key])) {
            $limits_list[$test_key] = TRUE;
            $eligible = TRUE;
          }
        }
      }

      if ($eligible) {
        $options[$release['version']] = $release;
      }
    }

    // Add Installed status.
    if (!is_null($installed_version) && isset($options[$installed_version['version']])) {
      $options[$installed_version['version']]['release_status'][] = 'Installed';
    }

    return $options;
  }

  /**
   * Prints release notes for given projects.
   *
   * @param string $version
   *   Version of the release to get notes.
   * @param bool $print_status
   *   Whether to print a informative note.
   * @param string $tmpfile
   *   If provided, a file that contains contents to show before the
   *   release notes.
   */
  function getReleaseNotes($version = NULL, $print_status = TRUE, $tmpfile = NULL) {
    $project_name = $this->parsed['short_name'];
    if (!isset($tmpfile)) {
      $tmpfile = drush_tempnam('rln-' . $project_name . '.');
    }

    // Select versions to show.
    $versions = array();
    if (!is_null($version)) {
      $versions[] = $version;
    }
    else {
      // If requested project is installed,
      // show release notes for the installed version and all newer versions.
      if (isset($this->parsed['recommended'], $this->parsed['installed'])) {
        $releases = array_reverse($this->parsed['releases']);
        foreach($releases as $version => $release) {
          if ($release['date'] >= $this->parsed['releases'][$this->parsed['installed']]['date']) {
            $release += array('version_extra' => '');
            $this->parsed['releases'][$this->parsed['installed']] += array('version_extra' => '');
            if ($release['version_extra'] == 'dev' && $this->parsed['releases'][$this->parsed['installed']]['version_extra'] != 'dev') {
              continue;
            }
            $versions[] = $version;
          }
        }
      }
      else {
        // Project is not installed and user did not specify a version,
        // so show the release notes for the recommended version.
        $versions[] = $this->parsed['recommended'];
      }
    }

    foreach ($versions as $version) {
      if (!isset($this->parsed['releases'][$version]['release_link'])) {
        drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $project_name, '!version' => $version)), LogLevel::WARNING);
        continue;
      }

      // Download the release node page and get the html as xml to explore it.
      $release_link = $this->parsed['releases'][$version]['release_link'];
      $filename = drush_download_file($release_link, drush_tempnam($project_name));
      @$dom = \DOMDocument::loadHTMLFile($filename);
      if ($dom) {
        drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $project_name, '!version' => $version)), LogLevel::NOTICE);
      }
      else {
        drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $project_name)), LogLevel::ERROR);
        continue;
      }
      $xml = simplexml_import_dom($dom);

      // Extract last update time and the notes.
      $last_updated = $xml->xpath('//div[contains(@class,"views-field-changed")]');
      $last_updated = $last_updated[0]->asXML();
      $notes = $xml->xpath('//div[contains(@class,"field-name-body")]');
      $notes = (!empty($notes)) ? $notes[0]->asXML() : dt("There're no release notes.");

      // Build the notes header.
      $header = array();
      $header[] = '<hr>';
      $header[] = dt("> RELEASE NOTES FOR '!name' PROJECT, VERSION !version:", array('!name' => strtoupper($project_name), '!version' => $version));
      $header[] = dt("> !last_updated.", array('!last_updated' => trim(drush_html_to_text($last_updated))));
      if ($print_status) {
        $header[] = '> ' . implode(', ', $this->parsed['releases'][$version]['release_status']);
      }
      $header[] = '<hr>';

      // Finally add the release notes for the requested project to the tmpfile.
      $content = implode("\n", $header) . "\n" . $notes . "\n";
      #TODO# accept $html as a method argument
      if (!drush_get_option('html', FALSE)) {
        $content = drush_html_to_text($content, array('br', 'p', 'ul', 'ol', 'li', 'hr'));
      }
      file_put_contents($tmpfile, $content, FILE_APPEND);
    }

    #TODO# don't print! Just return the filename
    drush_print_file($tmpfile);
  }
}
<?php

/**
 * @file
 * Drush release info engine for update.drupal.org and compatible services.
 *
 * This engine does connect directly to the update service. It doesn't depend
 * on a bootstrapped site.
 */

namespace Drush\UpdateService;

use Drush\Log\LogLevel;

/**
 * Release info engine class.
 */
class ReleaseInfo {
  const DEFAULT_URL = 'https://updates.drupal.org/release-history';

  // Cache release xml files for 24h by default.
  const CACHE_LIFETIME = 86400;

  private $cache;
  private $engine_config;

  /**
   * Constructor.
   */
  public function __construct($type, $engine, $config) {
    $this->engine_type = $type;
    $this->engine = $engine;

    if (is_null($config)) {
      $config = array();
    }
    $config += array(
      'cache-duration' => drush_get_option('cache-duration-releasexml', self::CACHE_LIFETIME),
    );
    $this->engine_config = $config;
    $this->cache = array();
  }

  /**
   * Returns configured cache duration.
   */
  public function getCacheDuration() {
    return $this->engine_config['cache-duration'];
  }

  /**
   * Returns a project's release info from the update service.
   *
   * @param array $request
   *   A request array.
   *
   * @param bool $refresh
   *   Whether to discard cached object.
   *
   * @return \Drush\UpdateService\Project
   */
  public function get($request, $refresh = FALSE) {
    if ($refresh || !isset($this->cache[$request['name']])) {
      $project_release_info = Project::getInstance($request, $this->getCacheDuration());
      if ($project_release_info && !$project_release_info->isValid()) {
        $project_release_info = FALSE;
      }
      $this->cache[$request['name']] = $project_release_info;
    }
    return $this->cache[$request['name']];
  }

  /**
   * Delete cached update service file of a project.
   *
   * @param array $request
   *   A request array.
   */
  public function clearCached(array $request) {
    if (isset($this->cache[$request['name']])) {
      unset($this->cache[$request['name']]);
    }
    $url = Project::buildFetchUrl($request);
    $cache_file = drush_download_file_name($url);
    if (file_exists($cache_file)) {
      unlink($cache_file);
    }
  }

  /**
   * Select the most appropriate release for a project, based on a strategy.
   *
   * @param Array &$request
   *   A request array.
   *   The array will be expanded with the project type.
   * @param String $restrict_to
   *   One of:
   *     'dev': Forces choosing a -dev release.
   *     'version': Forces choosing a point release.
   *     '': No restriction.
   *   Default is ''.
   * @param String $select
   *   Strategy for selecting a release, should be one of:
   *    - auto: Try to select the latest release, if none found allow the user
   *            to choose.
   *    - always: Force the user to choose a release.
   *    - never: Try to select the latest release, if none found then fail.
   *    - ignore: Ignore and return NULL.
   *   If no supported release is found, allow to ask the user to choose one.
   * @param Boolean $all
   *   In case $select = TRUE this indicates that all available releases will be
   *  offered the user to choose.
   *
   * @return array
   *  The selected release.
   */
  public function selectReleaseBasedOnStrategy($request, $restrict_to = '', $select = 'never', $all = FALSE, $version = NULL) {
    if (!in_array($select, array('auto', 'never', 'always', 'ignore'))) {
      return drush_set_error('DRUSH_PM_UNKNOWN_SELECT_STRATEGY', dt("Error: select strategy must be one of: auto, never, always, ignore", array()));
    }

    $project_release_info = $this->get($request);
    if (!$project_release_info) {
      return FALSE;
    }

    if ($select != 'always') {
      if (isset($request['version'])) {
        $release = $project_release_info->getSpecificRelease($request['version']);
        if ($release === FALSE) {
          return drush_set_error('DRUSH_PM_COULD_NOT_FIND_VERSION', dt("Could not locate !project version !version.", array(
            '!project' => $request['name'],
            '!version' => $request['version'],
          )));
        }
      }
      if ($restrict_to == 'dev') {
        // If you specified a specific release AND --dev, that is either
        // redundant (okay), or contradictory (error).
        if (!empty($release)) {
          if ($release['version_extra'] != 'dev') {
            return drush_set_error('DRUSH_PM_COULD_NOT_FIND_VERSION', dt("You requested both --dev and !project version !version, which is not a '-dev' release.", array(
              '!project' => $request['name'],
              '!version' => $request['version'],
           )));
         }
        }
        else {
          $release = $project_release_info->getDevRelease();
          if ($release === FALSE) {
            return drush_set_error('DRUSH_PM_NO_DEV_RELEASE', dt('There is no development release for project !project.', array('!project' => $request['name'])));
          }
        }
      }
      // If there was no specific release requested, try to identify the most appropriate release.
      if (empty($release)) {
        $release = $project_release_info->getRecommendedOrSupportedRelease();
      }
      if ($release) {
        return $release;
      }
      else {
        $message = dt('There are no stable releases for project !project.', array('!project' => $request['name']));
        if ($select == 'never') {
          return drush_set_error('DRUSH_PM_NO_STABLE_RELEASE', $message);
        }
        drush_log($message, LogLevel::WARNING);
        if ($select == 'ignore') {
          return NULL;
        }
      }
    }

    // At this point the only chance is to ask the user to choose a release.
    if ($restrict_to == 'dev') {
      $filter = 'dev';
    }
    elseif ($all) {
      $filter = 'all';
    }
    else {
      $filter = '';
    }
    $releases = $project_release_info->filterReleases($filter, $version);

    // Special checking: Drupal 6 is EOL, so there are no stable
    // releases for ANY contrib project. In this case, we'll default
    // to the best release, unless the user specified --select.
    $version_major = drush_drupal_major_version();
    if (($select != 'always') && ($version_major < 7)) {
      $bestRelease = Project::getBestRelease($releases);
      if (!empty($bestRelease)) {
        $message = dt('Drupal !major has reached EOL, so there are no stable releases for any contrib projects. Selected the best release, !project.', array('!major' => $version_major, '!project' => $bestRelease['name']));
        drush_log($message, LogLevel::WARNING);
        return $bestRelease;
      }
    }

    $options = array();
    foreach($releases as $release) {
      $options[$release['version']] = array($release['version'], '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status']));
    }
    $choice = drush_choice($options, dt('Choose one of the available releases for !project:', array('!project' => $request['name'])));
    if (!$choice) {
      return drush_user_abort();
    }

    return $releases[$choice];
  }

  /**
   * Check if a project is available in the update service.
   *
   * Optionally check for consistency by comparing given project type and
   * the type obtained from the update service.
   *
   * @param array $request
   *   A request array.
   * @param string $type
   *   Optional. If provided, will do a consistent check of the project type.
   *
   * @return boolean
   *   True if the project exists and type matches.
   */
  public function checkProject($request, $type = NULL) {
    $project_release_info = $this->get($request);
    if (!$project_release_info) {
      return FALSE;
    }
    if ($type) {
      if ($project_release_info->getType() != $type) {
        return FALSE;
      }
    }

    return TRUE;
  }
}
<?php

/**
 * @file
 * Implementation of 'drupal' update_status engine for Drupal 6.
 */

namespace Drush\UpdateService;

class StatusInfoDrupal6 extends StatusInfoDrupal7 {

  /**
   * {@inheritdoc}
   */
  function beforeGetStatus(&$projects, $check_disabled) {
    // If check-disabled option was provided, alter Drupal settings temporarily.
    // There's no other way to hook into this.
    if (!is_null($check_disabled)) {
      global $conf;
      $this->update_check_disabled = $conf['update_advanced_check_disabled'];
      $conf['update_advanced_check_disabled'] = $check_disabled;
    }
  }

  /**
   * {@inheritdoc}
   */
  function afterGetStatus(&$update_info, $projects, $check_disabled) {
    // Restore Drupal settings.
    if (!is_null($check_disabled)) {
      global $conf;
      $conf['update_advanced_check_disabled'] = $this->update_check_disabled;
      unset($this->update_check_disabled);
    }

    // update_advanced.module sets a different project type
    // for disabled projects. Here we normalize it.
    if ($check_disabled) {
      foreach ($update_info as $key => $project) {
        if (in_array($project['project_type'], array('disabled-module', 'disabled-theme'))) {
          $update_info[$key]['project_type'] = substr($project['project_type'], strpos($project['project_type'], '-') + 1);
        }
      }
    }
  }

  /**
   * Obtains release info for all installed projects via update.module.
   *
   * @see update_get_available().
   * @see update_manual_status().
   */
  protected function getAvailableReleases() {
    // We force a refresh if the cache is not available.
    if (!cache_get('update_available_releases', 'cache_update')) {
      $this->refresh();
    }

    $available = update_get_available(TRUE);

    // Force to invalidate some update_status caches that are only cleared
    // when visiting update status report page.
    if (function_exists('_update_cache_clear')) {
      _update_cache_clear('update_project_data');
      _update_cache_clear('update_project_projects');
    }

    return $available;
  }
}

<?php

/**
 * @file
 * Implementation of 'drupal' update_status engine for Drupal 7.
 */

namespace Drush\UpdateService;

class StatusInfoDrupal7 extends StatusInfoDrupal8 {

  /**
   * {@inheritdoc}
   */
  function lastCheck() {
    return variable_get('update_last_check', 0);
  }

  /**
   * {@inheritdoc}
   */
  function beforeGetStatus(&$projects, $check_disabled) {
    // If check-disabled option was provided, alter Drupal settings temporarily.
    // There's no other way to hook into this.
    if (!is_null($check_disabled)) {
      global $conf;
      $this->update_check_disabled = $conf['update_check_disabled'];
      $conf['update_check_disabled'] = $check_disabled;
    }
  }

  /**
   * {@inheritdoc}
   */
  function afterGetStatus(&$update_info, $projects, $check_disabled) {
    // Restore Drupal settings.
    if (!is_null($check_disabled)) {
      global $conf;
      $conf['update_check_disabled'] = $this->update_check_disabled;
      unset($this->update_check_disabled);
    }

    // update.module sets a different project type
    // for disabled projects. Here we normalize it.
    if ($check_disabled) {
      foreach ($update_info as $key => $project) {
        if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) {
          $update_info[$key]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-'));
        }
      }
    }
  }

  /**
   * Obtains release info for all installed projects via update.module.
   *
   * @see update_get_available().
   * @see update_manual_status().
   */
  protected function getAvailableReleases() {
    // Force to invalidate some caches that are only cleared
    // when visiting update status report page. This allow to detect changes in
    // .info files.
    _update_cache_clear('update_project_data');
    _update_cache_clear('update_project_projects');

    // From update_get_available(): Iterate all projects and create a fetch task
    // for those we have no information or is obsolete.
    $available = _update_get_cached_available_releases();

    module_load_include('inc', 'update', 'update.compare');
    $update_projects = update_get_projects();

    foreach ($update_projects as $key => $project) {
      if (empty($available[$key])) {
        update_create_fetch_task($project);
        continue;
      }
      if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) {
        $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
      }
      if (empty($available[$key]['releases'])) {
        $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
      }
      if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) {
        update_create_fetch_task($project);
      }
    }

    // Set a batch to process all pending tasks.
    $batch = array(
      'operations' => array(
        array('update_fetch_data_batch', array()),
      ),
      'finished' => 'update_fetch_data_finished',
      'file' => drupal_get_path('module', 'update') . '/update.fetch.inc',
    );
    batch_set($batch);
    drush_backend_batch_process();

    // Clear any error set by a failed update fetch task. This avoid rollbacks.
    drush_clear_error();

    // Calculate update status data.
    $available = _update_get_cached_available_releases();
    return $available;
  }
}

<?php

/**
 * @file
 * Implementation of 'drupal' update_status engine for Drupal 8.
 */

namespace Drush\UpdateService;

class StatusInfoDrupal8 implements StatusInfoInterface {

  /**
   * {@inheritdoc}
   */
  public function __construct($type, $engine, $config) {
    $this->engine_type = $type;
    $this->engine = $engine;
    $this->engine_config = $config;
  }

  /**
   * {@inheritdoc}
   */
  function lastCheck() {
    $last_check = \Drupal::state()->get('update.last_check') ?: 0;
    return $last_check;
  }

  /**
   * {@inheritdoc}
   */
  function refresh() {
    update_refresh();
  }

  /**
   * Perform adjustments before running get status.
   *
   *  - Enforce check-disabled option on update module.
   */
  function beforeGetStatus(&$projects, $check_disabled) {
    // If check-disabled option was provided, alter Drupal settings temporarily.
    // There's no other way to hook into this.
    if (!is_null($check_disabled)) {
      $config = \Drupal::config('update.settings');
      $this->update_check_disabled = $config->get('check.disabled_extensions');
      $config->set('check.disabled_extensions', (bool)$check_disabled);
    }
  }

  /**
   * Get update information for all installed projects.
   *
   * @return
   *   Array of update status information.
   */
  function getStatus($projects, $check_disabled) {
    $this->beforeGetStatus($projects, $check_disabled);
    $available = $this->getAvailableReleases();
    $update_info = $this->calculateUpdateStatus($available, $projects);
    $this->afterGetStatus($update_info, $projects, $check_disabled);
    return $update_info;
  }

  /**
   * Perform adjustments after running get status.
   *
   *  - Restore check-disabled setting in update module.
   *  - Adjust project type for disabled projects.
   */
  function afterGetStatus(&$update_info, $projects, $check_disabled) {
    // Restore Drupal settings.
    if (!is_null($check_disabled)) {
      \Drupal::config('update.settings')->set('check.disabled_extensions', $this->update_check_disabled);
      unset($this->update_check_disabled);
    }

    // update.module sets a different project type
    // for disabled projects. Here we normalize it.
    if ($check_disabled) {
      foreach ($update_info as $key => $project) {
        if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) {
          $update_info[$key]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-'));
        }
      }
    }
  }

  /**
   * Obtains release info for all installed projects via update.module.
   *
   * @see update_get_available().
   * @see \Drupal\update\Controller\UpdateController::updateStatusManually()
   */
  protected function getAvailableReleases() {
    // Force to invalidate some caches that are only cleared
    // when visiting update status report page. This allow to detect changes in
    // .info.yml files.
    \Drupal::keyValueExpirable('update')->deleteMultiple(array('update_project_projects', 'update_project_data'));

    // From update_get_available(): Iterate all projects and create a fetch task
    // for those we have no information or is obsolete.
    $available = \Drupal::keyValueExpirable('update_available_releases')->getAll();
    $update_projects = \Drupal::service('update.manager')->getProjects();
    foreach ($update_projects as $key => $project) {
      if (empty($available[$key])) {
        \Drupal::service('update.processor')->createFetchTask($project);
        continue;
      }
      if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) {
        $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
      }
      if (empty($available[$key]['releases'])) {
        $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
      }
      if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) {
        \Drupal::service('update.processor')->createFetchTask($project);
      }
    }

    // Set a batch to process all pending tasks.
    $batch = array(
      'operations' => array(
        array(array(\Drupal::service('update.manager'), 'fetchDataBatch'), array()),
      ),
      'finished' => 'update_fetch_data_finished',
      'file' => drupal_get_path('module', 'update') . '/update.fetch.inc',
    );
    batch_set($batch);
    drush_backend_batch_process();

    // Clear any error set by a failed update fetch task. This avoid rollbacks.
    drush_clear_error();

    return \Drupal::keyValueExpirable('update_available_releases')->getAll();
  }

  /**
   * Calculates update status for all projects via update.module.
   */
  protected function calculateUpdateStatus($available, $projects) {
    module_load_include('inc', 'update', 'update.compare');
    $data = update_calculate_project_data($available);

    foreach ($data as $project_name => $project) {
      // Discard custom projects.
      if ($project['status'] == UPDATE_UNKNOWN) {
        unset($data[$project_name]);
        continue;
      }
      // Discard projects with unknown installation path.
      if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) {
        unset($data[$project_name]);
        continue;
      }

      // Add some info from the project to $data.
      $data[$project_name] += array(
        'path'  => isset($projects[$project_name]['path']) ? $projects[$project_name]['path'] : '',
        'label' => $projects[$project_name]['label'],
      );
      // Store all releases, not just the ones selected by update.module.
      // We use it to allow the user to update to a specific version.
      if (isset($available[$project_name]['releases'])) {
        $data[$project_name]['releases'] = $available[$project_name]['releases'];
      }
    }

    return $data;
  }
}

<?php

/**
 * @file
 * Implementation of 'drush' update_status engine for any Drupal version.
 */

namespace Drush\UpdateService;

use Drush\Log\LogLevel;

class StatusInfoDrush implements StatusInfoInterface {

  /**
   * {@inheritdoc}
   */
  public function __construct($type, $engine, $config) {
    $this->engine_type = $type;
    $this->engine = $engine;
    $this->engine_config = $config;
  }

  /**
   * {@inheritdoc}
   */
  function lastCheck() {
    $older = 0;

    // Iterate all projects and get the time of the older release info.
    $projects = drush_get_projects();
    foreach ($projects as $project_name => $project) {
      $request = pm_parse_request($project_name, NULL, $projects);
      $url = Project::buildFetchUrl($request);
      $cache_file = drush_download_file_name($url);
      if (file_exists($cache_file)) {
        $ctime = filectime($cache_file);
        $older = (!$older) ? $ctime : min($ctime, $older);
      }
    }

    return $older;
  }

  /**
   * {@inheritdoc}
   */
  function refresh() {
    $release_info = drush_include_engine('release_info', 'updatexml');

    // Clear all caches for the available projects.
    $projects = drush_get_projects();
    foreach ($projects as $project_name => $project) {
      $request = pm_parse_request($project_name, NULL, $projects);
      $release_info->clearCached($request);
    }
  }

  /**
   * Get update information for all installed projects.
   *
   * @return
   *   Array of update status information.
   */
  function getStatus($projects, $check_disabled) {
    // Exclude disabled projects.
    if (!$check_disabled) {
      foreach ($projects as $project_name => $project) {
        if (!$project['status']) {
          unset($projects[$project_name]);
        }
      }
    }
    $available = $this->getAvailableReleases($projects);
    $update_info = $this->calculateUpdateStatus($available, $projects);
    return $update_info;
  }

  /**
   * Obtains release info for projects.
   */
  private function getAvailableReleases($projects) {
    drush_log(dt('Checking available update data ...'), LogLevel::OK);

    $release_info = drush_include_engine('release_info', 'updatexml');

    $available = array();
    foreach ($projects as $project_name => $project) {
      // Discard projects with unknown installation path.
      if ($project_name != 'drupal' && !isset($project['path'])) {
        continue;
      }
      drush_log(dt('Checking available update data for !project.', array('!project' => $project['label'])), LogLevel::OK);
      $request = $project_name . (isset($project['core']) ? '-' . $project['core'] : '');
      $request = pm_parse_request($request, NULL, $projects);
      $project_release_info = $release_info->get($request);
      if ($project_release_info) {
        $available[$project_name] = $project_release_info;
      }
    }

    // Clear any error set by a failed project. This avoid rollbacks.
    drush_clear_error();

    return $available;
  }

  /**
   * Calculates update status for given projects.
   */
  private function calculateUpdateStatus($available, $projects) {
    $update_info = array();
    foreach ($available as $project_name => $project_release_info) {
      // Obtain project 'global' status. NULL status is ok (project published),
      // otherwise it signals something is bad with the project (revoked, etc).
      $project_status = $this->calculateProjectStatus($project_release_info);
      // Discard custom projects.
      if ($project_status == DRUSH_UPDATESTATUS_UNKNOWN) {
        continue;
      }

      // Prepare update info.
      $project = $projects[$project_name];
      $is_core = ($project['type'] == 'core');
      $version = pm_parse_version($project['version'], $is_core);
      // If project version ends with 'dev', this is a dev snapshot.
      $install_type = (substr($project['version'], -3, 3) == 'dev') ? 'dev' : 'official';
      $project_update_info = array(
        'name'             => $project_name,
        'label'            => $project['label'],
        'path'             => isset($project['path']) ? $project['path'] : '',
        'install_type'     => $install_type,
        'existing_version' => $project['version'],
        'existing_major'   => $version['version_major'],
        'status'           => $project_status,
        'datestamp'        => empty($project['datestamp']) ? NULL : $project['datestamp'],
      );

      // If we don't have a project status yet, it means this is
      // a published project and we need to obtain its update status
      // and recommended release.
      if (is_null($project_status)) {
        $this->calculateProjectUpdateStatus($project_release_info, $project_update_info);
      }

      // We want to ship all release info data including all releases,
      // not just the ones selected by calculateProjectUpdateStatus().
      // We use it to allow the user to update to a specific version.
      unset($project_update_info['releases']);
      $update_info[$project_name] = $project_update_info + $project_release_info->getInfo();
    }

    return $update_info;
  }

  /**
   * Obtain the project status in the update service.
   *
   * This is not the update status of the installed version
   * but the project 'global' status (unpublished, revoked, etc).
   *
   * @see update_calculate_project_status().
   */
  private function calculateProjectStatus($project_release_info) {
    $project_status = NULL;

    // If connection to the update service went wrong, or the received xml
    // is malformed, we don't have a UpdateService::Project object.
    if (!$project_release_info) {
      $project_status = DRUSH_UPDATESTATUS_NOT_FETCHED;
    }
    else {
      switch ($project_release_info->getStatus()) {
        case 'insecure':
          $project_status = DRUSH_UPDATESTATUS_NOT_SECURE;
          break;
        case 'unpublished':
        case 'revoked':
          $project_status = DRUSH_UPDATESTATUS_REVOKED;
          break;
        case 'unsupported':
          $project_status = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
          break;
        case 'unknown':
          $project_status = DRUSH_UPDATESTATUS_UNKNOWN;
          break;
      }
    }
    return $project_status;
  }

  /**
   * Obtain the update status of a project and the recommended release.
   *
   * This is a stripped down version of update_calculate_project_status().
   * That function has the same logic in Drupal 6,7,8.
   * Note: in Drupal 6 this is part of update_calculate_project_data().
   *
   * @see update_calculate_project_status().
   */
  private function calculateProjectUpdateStatus($project_release_info, &$project_data) {
    $available = $project_release_info->getInfo();

    /**
     * Here starts the code adapted from update_calculate_project_status().
     * Line 492 in Drupal 7.
     *
     * Changes are:
     *   - Use DRUSH_UPDATESTATUS_* constants instead of DRUSH_UPDATESTATUS_*
     *   - Remove error conditions we already handle
     *   - Remove presentation code ('extra' and 'reason' keys in $project_data)
     *   - Remove "also available" information.
     */

    // Figure out the target major version.
    $existing_major = $project_data['existing_major'];
    $supported_majors = array();
    if (isset($available['supported_majors'])) {
      $supported_majors = explode(',', $available['supported_majors']);
    }
    elseif (isset($available['default_major'])) {
      // Older release history XML file without supported or recommended.
      $supported_majors[] = $available['default_major'];
    }

    if (in_array($existing_major, $supported_majors)) {
      // Still supported, stay at the current major version.
      $target_major = $existing_major;
    }
    elseif (isset($available['recommended_major'])) {
      // Since 'recommended_major' is defined, we know this is the new XML
      // format. Therefore, we know the current release is unsupported since
      // its major version was not in the 'supported_majors' list. We should
      // find the best release from the recommended major version.
      $target_major = $available['recommended_major'];
      $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
    }
    elseif (isset($available['default_major'])) {
      // Older release history XML file without recommended, so recommend
      // the currently defined "default_major" version.
      $target_major = $available['default_major'];
    }
    else {
      // Malformed XML file? Stick with the current version.
      $target_major = $existing_major;
    }

    // Make sure we never tell the admin to downgrade. If we recommended an
    // earlier version than the one they're running, they'd face an
    // impossible data migration problem, since Drupal never supports a DB
    // downgrade path. In the unfortunate case that what they're running is
    // unsupported, and there's nothing newer for them to upgrade to, we
    // can't print out a "Recommended version", but just have to tell them
    // what they have is unsupported and let them figure it out.
    $target_major = max($existing_major, $target_major);

    $release_patch_changed = '';
    $patch = '';

    foreach ($available['releases'] as $version => $release) {
      // First, if this is the existing release, check a few conditions.
      if ($project_data['existing_version'] === $version) {
        if (isset($release['terms']['Release type']) &&
            in_array('Insecure', $release['terms']['Release type'])) {
          $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE;
        }
        elseif ($release['status'] == 'unpublished') {
          $project_data['status'] = DRUSH_UPDATESTATUS_REVOKED;
        }
        elseif (isset($release['terms']['Release type']) &&
                in_array('Unsupported', $release['terms']['Release type'])) {
          $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SUPPORTED;
        }
      }

      // Otherwise, ignore unpublished, insecure, or unsupported releases.
      if ($release['status'] == 'unpublished' ||
          (isset($release['terms']['Release type']) &&
           (in_array('Insecure', $release['terms']['Release type']) ||
            in_array('Unsupported', $release['terms']['Release type'])))) {
        continue;
      }

      // See if this is a higher major version than our target and discard it.
      // Note: at this point Drupal record it as an "Also available" release.
      if (isset($release['version_major']) && $release['version_major'] > $target_major) {
        continue;
      }

      // Look for the 'latest version' if we haven't found it yet. Latest is
      // defined as the most recent version for the target major version.
      if (!isset($project_data['latest_version'])
          && $release['version_major'] == $target_major) {
        $project_data['latest_version'] = $version;
        $project_data['releases'][$version] = $release;
      }

      // Look for the development snapshot release for this branch.
      if (!isset($project_data['dev_version'])
          && $release['version_major'] == $target_major
          && isset($release['version_extra'])
          && $release['version_extra'] == 'dev') {
        $project_data['dev_version'] = $version;
        $project_data['releases'][$version] = $release;
      }

      // Look for the 'recommended' version if we haven't found it yet (see
      // phpdoc at the top of this function for the definition).
      if (!isset($project_data['recommended'])
          && $release['version_major'] == $target_major
          && isset($release['version_patch'])) {
        if ($patch != $release['version_patch']) {
          $patch = $release['version_patch'];
          $release_patch_changed = $release;
        }
        if (empty($release['version_extra']) && $patch == $release['version_patch']) {
          $project_data['recommended'] = $release_patch_changed['version'];
          $project_data['releases'][$release_patch_changed['version']] = $release_patch_changed;
        }
      }

      // Stop searching once we hit the currently installed version.
      if ($project_data['existing_version'] === $version) {
        break;
      }

      // If we're running a dev snapshot and have a timestamp, stop
      // searching for security updates once we hit an official release
      // older than what we've got. Allow 100 seconds of leeway to handle
      // differences between the datestamp in the .info file and the
      // timestamp of the tarball itself (which are usually off by 1 or 2
      // seconds) so that we don't flag that as a new release.
      if ($project_data['install_type'] == 'dev') {
        if (empty($project_data['datestamp'])) {
          // We don't have current timestamp info, so we can't know.
          continue;
        }
        elseif (isset($release['date']) && ($project_data['datestamp'] + 100 > $release['date'])) {
          // We're newer than this, so we can skip it.
          continue;
        }
      }

      // See if this release is a security update.
      if (isset($release['terms']['Release type'])
          && in_array('Security update', $release['terms']['Release type'])) {
        $project_data['security updates'][] = $release;
      }
    }

    // If we were unable to find a recommended version, then make the latest
    // version the recommended version if possible.
    if (!isset($project_data['recommended']) && isset($project_data['latest_version'])) {
      $project_data['recommended'] = $project_data['latest_version'];
    }

    //
    // Check to see if we need an update or not.
    //

    if (!empty($project_data['security updates'])) {
      // If we found security updates, that always trumps any other status.
      $project_data['status'] = DRUSH_UPDATESTATUS_NOT_SECURE;
    }

    if (isset($project_data['status'])) {
      // If we already know the status, we're done.
      return;
    }

    // If we don't know what to recommend, there's nothing we can report.
    // Bail out early.
    if (!isset($project_data['recommended'])) {
      $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN;
      $project_data['reason'] = t('No available releases found');
      return;
    }

    // If we're running a dev snapshot, compare the date of the dev snapshot
    // with the latest official version, and record the absolute latest in
    // 'latest_dev' so we can correctly decide if there's a newer release
    // than our current snapshot.
    if ($project_data['install_type'] == 'dev') {
      if (isset($project_data['dev_version']) && $available['releases'][$project_data['dev_version']]['date'] > $available['releases'][$project_data['latest_version']]['date']) {
        $project_data['latest_dev'] = $project_data['dev_version'];
      }
      else {
        $project_data['latest_dev'] = $project_data['latest_version'];
      }
    }

    // Figure out the status, based on what we've seen and the install type.
    switch ($project_data['install_type']) {
      case 'official':
        if ($project_data['existing_version'] === $project_data['recommended'] || $project_data['existing_version'] === $project_data['latest_version']) {
          $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT;
        }
        else {
          $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT;
        }
        break;

      case 'dev':
        $latest = $available['releases'][$project_data['latest_dev']];
        if (empty($project_data['datestamp'])) {
          $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CHECKED;
        }
        elseif (($project_data['datestamp'] + 100 > $latest['date'])) {
          $project_data['status'] = DRUSH_UPDATESTATUS_CURRENT;
        }
        else {
          $project_data['status'] = DRUSH_UPDATESTATUS_NOT_CURRENT;
        }
        break;

      default:
        $project_data['status'] = DRUSH_UPDATESTATUS_UNKNOWN;
    }
  }
}

<?php

/**
 * @file
 * Interface for update_status engine implementations.
 */

namespace Drush\UpdateService;

interface StatusInfoInterface {

  /**
   * Constructor.
   * @todo this pertains to a yet to be defined EngineInterface.
   */
  public function __construct($type, $engine, $config);

  /**
   * Returns time of last check of available updates.
   */
  function lastCheck();

  /**
   * Refresh update status information.
   */
  function refresh();

  /**
   * Get update information for all installed projects.
   *
   * @return Array containing remote and local versions
   * for all installed projects.
   */
  function getStatus($projects, $check_disabled);
}
<?php

namespace Drush\User;

class User6 extends User7 {

  /**
   * {inheritdoc}
   */
  public function create($properties) {
    $account = user_save(NULL, $properties, NULL);
    return new UserSingle6($account);
  }

  /**
   * {@inheritdoc}
   */
  public function load_by_name($name) {
    return user_load(array('name' => $name));
  }

  /**
   * {@inheritdoc}
   */
  public function load_by_mail($mail) {
    return user_load(array('mail' => $mail));
  }

}
<?php

namespace Drush\User;

class User7 extends UserVersion {

  /**
   * {inheritdoc}
   */
  public function create($properties) {
    $account = user_save(NULL, $properties, NULL);
    return new UserSingle7($account);
  }
}
<?php


namespace Drush\User;

use Drupal\user\Entity\User;

class User8 extends UserVersion {

  /**
   * {inheritdoc}
   */
  public function create($properties) {
    $account = entity_create('user', $properties);
    $account->save();
    return new UserSingle8($account);
  }

  /**
   * Attempt to load a user account.
   *
   * @param int $uid
   * @return \Drupal\user\Entity\User;
   */
  public function load_by_uid($uid) {
    return User::load($uid);
  }

  /**
   * {inheritdoc}
   */
  public function getCurrentUserAsAccount() {
    return \Drupal::currentUser()->getAccount();
  }

  /**
   * Set the current user in Drupal.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   */
  public function setCurrentUser($account) {
    // Some parts of Drupal still rely on a global user object.
    // @todo remove once https://www.drupal.org/node/2163205 is in.
    global $user;
    $user = $account;
    \Drupal::currentUser()->setAccount($account);
  }
}
<?php

namespace Drush\User;

class UserList {

  /** @var \Drush\User\UserSingleBase[] */
  public $accounts;

  /**
   * Finds a list of user objects based on Drush arguments,
   * or options.
   */
  public function __construct($inputs) {
    if ($this->accounts = $this->getFromOptions() + $this->getFromParameters($inputs)) {
      return $this;
    }
    else {
      throw new UserListException('Unable to find a matching user.');
    }
  }

  /**
   * Iterate over each account and call the specified method.
   *
   * @param $method
   *   A method on a UserSingleBase object.
   * @param array $params
   *   An array of params to pass to the method.
   * @return array
   *   An associate array of values keyed by account ID.
   */
  public function each($method, array $params = array()) {
    foreach ($this->accounts as $account) {
      $return[$account->id()] = call_user_func_array(array($account, $method), $params);
    }
    return $return;
  }

  /*
   * Check common options for specifying users. If valid, return the accounts.
   *
   * @return \Drush\User\UserSingleBase[]
   */
  function getFromOptions() {
    $accounts = array();
    $userversion = drush_user_get_class();
    if ($mails = _convert_csv_to_array(drush_get_option('mail'))) {
      foreach ($mails as $mail) {
        if ($account = $userversion->load_by_mail($mail)) {
          $single = drush_usersingle_get_class($account);
          $accounts[$single->id()] = $single;
        }
        else {
          throw new UserListException('Unable to find a matching user for ' . $mail . '.');
        }
      }
    }
    if ($names = _convert_csv_to_array(drush_get_option('name'))) {
      foreach ($names as $name) {
        if ($account = $userversion->load_by_name($name)) {
          $single = drush_usersingle_get_class($account);
          $accounts[$single->id()] = $single;
        }
        else {
          throw new UserListException('Unable to find a matching user for ' . $name . '.');
        }
      }
    }
    if ($userids = _convert_csv_to_array(drush_get_option('uid'))) {
      foreach ($userids as $userid) {
        if (is_numeric($userid) && $account = $userversion->load_by_uid($userid)) {
          $single = drush_usersingle_get_class($account);
          $accounts[$single->id()] = $single;
        }
        else {
          throw new UserListException('Unable to find a matching user for ' . $userid . '.');
        }
      }
    }
    return $accounts;
  }

  /**
   * Given a comma-separated list of inputs, return accounts
   * for users that match by uid,name or email address.
   *
   * @param string $inputs
   *   A comma delimited string (or array) of arguments, specifying user account(s).
   *
   * @throws UserListException
   *   If any input is unmatched, an exception is thrown.
   *
   * @return \Drush\User\UserSingleBase[]
   *   An associative array of UserSingleBase objects, keyed by user id.
   */
  public static function getFromParameters($inputs) {
    $accounts = array();
    $userversion = drush_user_get_class();
    if ($inputs && $userversion) {
      $inputs = _convert_csv_to_array($inputs);
      foreach($inputs as $input) {
        if (is_numeric($input) && $account = $userversion->load_by_uid($input)) {

        }
        elseif ($account = $userversion->load_by_name($input)) {

        }
        elseif ($account = $userversion->load_by_mail($input)) {

        }
        else {
          // Unable to load an account for the input.
          throw new UserListException('Unable to find a matching user for ' . $input . '.');
        }
        // Populate $accounts with a UserSingle object. Will go into $this->accounts.
        $single = drush_usersingle_get_class($account);
        $accounts[$single->id()] = $single;
      }
    }
    return $accounts;
  }

  /*
   * A comma delimited list of names built from $this->accounts.
   */
  public function names() {
    $names = array();
    foreach ($this->accounts as $account) {
      $names[] = $account->getUsername();
    }
    return implode(', ', $names);
  }
}
<?php

namespace Drush\User;

class UserListException extends \Exception {}
<?php

namespace Drush\User;

class UserSingle6 extends UserSingle7 {

  public function cancel() {
    user_delete(array(), $this->account->uid);
  }
}
<?php

namespace Drush\User;

class UserSingle7 extends UserSingleBase {

  public function block() {
    user_user_operations_block(array($this->account->uid));
  }

  public function unblock() {
    user_user_operations_unblock(array($this->account->uid));
  }

  public function addRole($rid) {
    user_multiple_role_edit(array($this->account->uid), 'add_role', $rid);
  }

  public function removeRole($rid) {
    user_multiple_role_edit(array($this->account->uid), 'remove_role', $rid);
  }

  function info() {
    $userinfo = (array)$this->account;
    unset($userinfo['data']);
    unset($userinfo['block']);
    unset($userinfo['form_build_id']);
    foreach (array('created', 'access', 'login') as $key) {
      $userinfo['user_' . $key] = format_date($userinfo[$key]);
    }
    $userinfo['user_status'] = $userinfo['status'] ? 'active' : 'blocked';
    return $userinfo;
  }

  function password($pass) {
    user_save($this->account, array('pass' => $pass));
  }

  public function getUsername() {
    return $this->account->name;
  }

  public function id() {
    return $this->account->uid;
  }
}
<?php

namespace Drush\User;

class UserSingle8 extends UserSingleBase {

}
<?php

namespace Drush\User;

abstract class UserSingleBase {

  // A Drupal user entity.
  public $account;

  public function __construct($account) {
    $this->account = $account;
  }

  /**
   * A flatter and simpler array presentation of a Drupal $user object.
   *
   * @return array
   */
  public function info() {
    return array(
      'uid' => $this->account->id(),
      'name' => $this->account->getUsername(),
      'password' => $this->account->getPassword(),
      'mail' => $this->account->getEmail(),
      'user_created' => $this->account->getCreatedTime(),
      'created' => format_date($this->account->getCreatedTime()),
      'user_access' => $this->account->getLastAccessedTime(),
      'access' => format_date($this->account->getLastAccessedTime()),
      'user_login' => $this->account->getLastLoginTime(),
      'login' => format_date($this->account->getLastLoginTime()),
      'user_status' => $this->account->get('status')->value,
      'status' => $this->account->isActive() ? 'active' : 'blocked',
      'timezone' => $this->account->getTimeZone(),
      'roles' => $this->account->getRoles(),
      'langcode' => $this->account->getPreferredLangcode(),
      'uuid' => $this->account->uuid->value,
    );
  }

  /**
   * Block a user from login.
   */
  public function block() {
    $this->account->block();
    $this->account->save();
  }

  /**
   * Unblock a user from login.
   */
  public function unblock() {
    $this->account->get('status')->value = 1;
    $this->account->save();
  }

  /**
   * Add a role to the current user.
   *
   * @param $rid
   *   A role ID.
   */
  public function addRole($rid) {
    $this->account->addRole($rid);
    $this->account->save();
  }

  /**
   * Remove a role from the current user.
   *
   * @param $rid
   *   A role ID.
   */
  public function removeRole($rid) {
    $this->account->removeRole($rid);
    $this->account->save();
  }

  /**
   * Block a user and remove or reassign their content.
   */
  public function cancel() {
      if (drush_get_option('delete-content')) {
        user_cancel(array(), $this->id(), 'user_cancel_delete');
      }
      else {
        user_cancel(array(), $this->id(), 'user_cancel_reassign');
      }
      // I got the following technique here: http://drupal.org/node/638712
      $batch =& batch_get();
      $batch['progressive'] = FALSE;
      batch_process();
  }

  /**
   * Change a user's password.
   *
   * @param $password
   */
  public function password($password) {
    $this->account->setPassword($password);
    $this->account->save();
  }

  /**
   * Build a one time login link.
   *
   * @param string $path
   * @return string
   */
  public function passResetUrl($path = '') {
    $url = user_pass_reset_url($this->account) . '/login';
    if ($path) {
      $url .= '?destination=' . $path;
    }
    return $url;
  }

  /**
   * Get a user's name.
   * @return string
   */
  public function getUsername() {
    return $this->account->getUsername();
  }

  /**
   * Return an id from a Drupal user account.
   * @return int
   */
  public function id() {
    return $this->account->id();
  }
}
<?php

namespace Drush\User;

abstract class UserVersion {

  /**
   * Create a new user account.
   *
   * @param array $properties
   *
   * @return
   *   A user object.
   */
  public function create($properties) {}

  /**
   * Attempt to load a user account.
   *
   * @param int $uid
   * @return mixed
   */
  public function load_by_uid($uid) {
    return user_load($uid);
  }

  /**
   * Attempt to load a user account.
   *
   * @param string $name
   * @return mixed
   */
  public function load_by_name($name) {
    return user_load_by_name($name);
  }

  /**
   * Attempt to load a user account.
   *
   * @param string $mail
   * @return mixed
   */
  public function load_by_mail($mail) {
    return user_load_by_mail($mail);
  }

  /**
   * Load the current user account.
   *
   * @return mixed
   *   A user object.
   */
  public function getCurrentUserAsAccount() {
    global $user;
    return $user;
  }

  /**
   * Load the current user account and return a UserSingle instance.
   *
   * @return \Drush\User\UserSingleBase
   *   A Drush UserSingle instance.
   */
  public function getCurrentUserAsSingle() {
    return drush_usersingle_get_class($this->getCurrentUserAsAccount());
  }

  /**
   * Set the current "global" user account in Drupal.

   * @param
   *   A user object.
   */
  public function setCurrentUser($account) {
    global $user;
    $user = $account;
  }
}
                                       .,.                                              
                                      .cd:..                                            
                                      .xXd,,'..                                         
                                     .lXWx;;;,,..                                       
                                   .,dXWXo;;;;;,,..                                     
                                 .;dKWWKx:;;;;;;;,,'..                                  
                              .;oOXNXKOo;;;;;;;;;;;;,,,'..                              
                           .:dOXWMMN0Okl;;;;;;;;;;;;;;;;,,,'..                          
                       .,lk0NMMMMMMNKOxc;;;;;;;;;;;;;;;;;;;;,,,'..                      
                   .'cx0XWMMMMMMMWX0kd:;;;;;;;;;;;;;;;;;;;;;;;;;,,,..                   
                .'cx0NMMMMMMMMMWX0Oxl;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'..                
              .;d0NMMMMMMMMMMWX0Oxl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,...             
            .:kXWMMMMMMMMMWNK0kdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..           
          .cONMMMMMMMMMWNX0Okoc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.          
        .;kNMMMMMMMMWNX0Okdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..        
       .oXMMMMMMWWXK0Oxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       
      ,oKWWWWNXKK0kxoc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..      
     'lOO0000OOxdlc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''.     
    .,lxkxxdolc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,''''..   
   .,;;;::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''..  
  .,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''''''.  
 .';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''.. 
.',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''..
.,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''..
',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''.
,,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''.
,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''.
,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''''''
,;;;;;;;;;;;;;;;;;;;;;;;;;;;cldxkkOkkxxdlc;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''',''''''''
,;;;;;;;;;;;;;;;;;;;;;;;:ox0XWMMMMMMMMMWNX0kxdc;;;;;;;;;;;;;;;;,,,'''''''';cdk00Okl,''''
,;;;;;;;;;;;;;;;;;;;;;cxKWMMMMMMMMMMMMMMMMMMMWN0xl;;;;;;;;;;,,,'''''''';lkKWMMMMMMW0c'''
',;;;;;;;;;;;;;;;;;;:dKWMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;;;;;,,'''''''';okXWMMMMMMMMMMM0:'.
.,;;;;;;;;;;;;;;;;;:kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;''''''',:oOXWMMMMMMMMMMMMMMNd'.
.',;;;;;;;;;;;;;;;;xWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xolccoxONMMMMMMMMMMMMMMMMMMWd'.
 .,;;;;;;;;;;;;;;;oXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNWMMMMMMMMMMMMMMMMMMMMMMXl..
 .',;;;;;;;;;;;;;;xNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0OOOKNMMMMMMMMMMMMMMMMMMMM0;. 
  .',;;;;;;;;;;;;;dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWN0xl:,''',cxXWMMMMMMMMMMMMMMMMWd.  
   .',;;;;;;;;;;;;lKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:,'''''''''':dKWMMMMMMMMMMMMMWk,   
    .',;;;;;;;;;;;;dXMMMMMMMMMMMMMMMMMMMMMMMMWX0xl;'''',;:::;,''''';oKWMMMMMMMMMMWO,    
     ..',,;;;;;;;;;;o0NMMMMMMMMMMMMMMMMMMN0xdo:,''',cdO0XXXXK0kl,'''';o0WMMMMMMMNd,     
       .''',,,,,,,,,,;lk0XWMMMMMMMMWNX0ko:,'''''';oONN0xdoodx0NNx,''''';lOXWWWXkc.      
        ..'''''''''''''',:lloodddoolc;'''''''''',xN0dc,'''''',oK0:''''''',:clc;..       
         ...''''''''''''''''''''''''''''''''''''':c,''''''''''';;''''''''''''..         
           ..''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''...          
             ...'''''''''''''''''''''',lxdc,'''''''''''''''''',:oxkl''''''..            
                ...''''''''''''''''''':kNMNK0OxdollcccclllodxO0XX0d;'''..               
                   ..'''''''''''''''''',cxOKXXNWMMWWWWWWWWNXKOxo:'''..                  
                      ...'.'''''''''''''''',;:clooodddoollc:,'''...                     
                           ....'''''''''''''''''''''''''''.....                         
                               ....'..''''''''''''........                              
                                       .,.                                              
                                      .cd:..                                            
                                      .xXd,,'..                                         
                                     .lXWx;;;,,..                                       
                                   .,dXWXo;;;;;,,..                                     
                                 .;dKWWKx:;;;;;;;,,'..                                  
                              .;oOXNXKOo;;;;;;;;;;;;,,,'..                              
                           .:dOXWMMN0Okl;;;;;;;;;;;;;;;;,,,'..                          
                       .,lk0NMMMMMMNKOxc;;;;;;;;;;;;;;;;;;;;,,,'..                      
                   .'cx0XWMMMMMMMWX0kd:;;;;;;;;;;;;;;;;;;;;;;;;;,,,..                   
                .'cx0NMMMMMMMMMWX0Oxl;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'..                
              .;d0NMMMMMMMMMMWX0Oxl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,...             
            .:kXWMMMMMMMMMWNK0kdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..           
          .cONMMMMMMMMMWNX0Okoc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'.          
        .;kNMMMMMMMMWNX0Okdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..        
       .oXMMMMMMWWXK0Oxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..       
      ,oKWWWWNXKK0kxoc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'..      
     'lOO0000OOxdlc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''.     
    .,lxkxxdolc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,''''..   
   .,;;;::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''..  
  .,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,'''''''.  
 .';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''.. 
.',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''..
.,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''..
',;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''.
,,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''.
,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,'''''''''''''''.
,;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''''''''''
,;;;;;;;;;;;;;;;;;;;;;;;;;;;cldxkkOkkxxdlc;;;;;;;;;;;;;;;;;;;;;;;;,,''''''''''',''''''''
,;;;;;;;;;;;;;;;;;;;;;;;:ox0XWMMMMMMMMMWNX0kxdc;;;;;;;;;;;;;;;;,,,'''''''';cdk00Okl,''''
,;;;;;;;;;;;;;;;;;;;;;cxKWMMMMMMMMMMMMMMMMMMMWN0xl;;;;;;;;;;,,,'''''''';lkKWMMMMMMW0c'''
',;;;;;;;;;;;;;;;;;;:dKWMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;;;;;,,'''''''';okXWMMMMMMMMMMM0:'.
.,;;;;;;;;;;;;;;;;;:kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xl;''''''',:oOXWMMMMMMMMMMMMMMNd'.
.',;;;;;;;;;;;;;;;;xWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xolccoxONMMMMMMMMMMMMMMMMMMWd'.
 .,;;;;;;;;;;;;;;;oXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNWMMMMMMMMMMMMMMMMMMMMMMXl..
 .',;;;;;;;;;;;;;;xNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0OOOKNMMMMMMMMMMMMMMMMMMMM0;. 
  .',;;;;;;;;;;;;;dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWN0xl:,''',cxXWMMMMMMMMMMMMMMMMWd.  
   .',;;;;;;;;;;;;lKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWKko:,'''''''''':dKWMMMMMMMMMMMMMWk,   
    .',;;;;;;;;;;;;dXMMMMMMMMMMMMMMMMMMMMMMMMWX0xl;'''',;:::;,''''';oKWMMMMMMMMMMWO,    
     ..',,;;;;;;;;;;o0NMMMMMMMMMMMMMMMMMMN0xdo:,''',cdO0XXXXK0kl,'''';o0WMMMMMMMNd,     
       .''',,,,,,,,,,;lk0XWMMMMMMMMWNX0ko:,'''''';oONN0xdoodx0NNx,''''';lOXWWWXkc.      
        ..'''''''''''''',:lloodddoolc;'''''''''',xN0dc,'''''',oK0:''''''',:clc;..       
         ...''''''''''''''''''''''''''''''''''''':c,''''''''''';;''''''''''''..         
           ..''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''...          
             ...'''''''''''''''''''''',lxdc,'''''''''''''''''',:oxkl''''''..            
                ...''''''''''''''''''':kNMNK0OxdollcccclllodxO0XX0d;'''..               
                   ..'''''''''''''''''',cxOKXXNWMMWWWWWWWWNXKOxo:'''..                  
                      ...'.'''''''''''''''',;:clooodddoollc:,'''...                     
                           ....'''''''''''''''''''''''''''.....                         
                               ....'..''''''''''''........                              
This directory holds the build script for Drush's Windows distribution. This script is only useful to Drush administrators who are generating a new build. 

To use this script:

- Edit the metadata at the top
- Run the script
- Attach the .zip file to the corresponding Release on Github.
- Update the links at bottom of docs/install.md
@echo off

SET SCRIPT_HOME=%~dp0
SET PATH=%SCRIPT_HOME%php;%PATH%

@php.exe %SCRIPT_HOME%composer.phar %*
@echo off

SET SCRIPT_HOME=%~dp0
SET PHP_PATH=%SCRIPT_HOME%php
SET PATH=%SCRIPT_HOME%tools\bin;%PHP_PATH%;%PATH%

@php.exe "%SCRIPT_HOME%vendor\drush\drush\drush.php" --php="php.exe" %*
MZ����@���	�!�L�!This program cannot be run in DOS mode.

$���@��@��@�ͱ&V�S�ͱ&T�J�ͱ&W���@���ͼ� �E���W�A���U�A��Rich@��PEL�z(R�b��@ @�T�<D0�@�.text�`b `.rdata�E�Ff@@.data,��@�.reloc��@BU��Qj��@�E�jj�EP��@P�MQ�U�R��@��]�U����E��E��E�Ph�jh�@jjh����@�E�}�u�E�����h�@������E���]�jh��@�j�Y�MZf9@t3��3�<@��@PEu�f9�@u�3ۃ�t@v	9��@�É]���
��uj��Y�.��uj��Y�r�e���
��yj�Y��@��@����@�N
��yj�Y�j��yj	�Yj��Y��tP�Y��@���@P�5�@�5�@�������u܅�uV���.�M���E�QP�YYËe�u�u܃}�uV��R�E���������U��=`�@t��u�h���YY]����{���U��E��8csm�u%�xu�@= �t=!�t="�t
=@�t3�]���h@�.Y3��U��V����E�V\W�}��99t
�����;�r�;�s99t3Ʌ���Q�����u�a3�@���u�����ES�^`�F`�y��j$_�F\���d����|�9���~du�Fd���9��u	�Fd��u�9��u	�Fd��d�9��u	�Fd��S�9��u	�Fd��B�9��u	�Fd��1�9��u	�Fd�� �9��u	�Fd���9��u�Fd��vdj��Y�~d�	�q�a��Y�^`��[�3�_^]�jh��@��u����~$t	�v$�\Y�~,t	�v,�MY�~4t	�v4�>Y�~<t	�v<�/Y�~@t	�v@� Y�~Dt	�vD�Y�~Ht	�vH�Y�~\P�@t	�v\��Yj
�oY�e��~h��tW�$�@��u����@tW��Y�E������Wj�6Y�E��~l��t#W�Y;=�@t���@t�?uW�%Y�E������V�kY����uj
�CYËuj�7Y�V���uj�Y��^�VW��@�54�@�����Y��uGh�j�B��YY��t3V�54�@�YY��tjV�%YY�(�@�N���	V��Y3�W��@_��^�jh�@��
�u�F\P�@�f3�G�~�~pjCXf���f����Fh��@���j
�Y�e��vh� �@�E������>j��Y�}��E�Fl��u��@�Fl�vl�RY�E�������
�3�G�uj
�Y�j�Y�����u�c3��h�@�CY�4�@���t�Vh�j���YY��t-V�54�@�sYY��tjV��YY�(�@�N��3�@^��3�^á4�@���tP��
4�@�Y�8U��Q�E�Ph�@j�8�@��th�@�u��<�@��t�u����U��u����Y�u�4�@�U����u�Yh���jjj�@���jjj�1���U��=�@th�@�N!Y��t
�u��@Y�e#h0�@h�@�YY��uPVWh�"@�/#Y��@��@����t�Ѓ�;�r�=��@_^th��@�� Y��tjjj���@3�]�U��jj�u���]�Vj�,�@��V�#V�PV�#V�&#V�:#V�i
��^�-U��V�u����t�Ѓ�;ur�^]�U��V�u3����u���t�у�;ur�^]�j�`Y�j�Y�jh�@��
j�BY�e��=��@�����@�E���@�}���5�@�50�@�֋؉]ԅ�tt�5�@�֋��]�}�}܃��}�;�rWj�,�@9t�;�rG�7�֋�j�,�@���5�@�50�@�։E�5�@�֋M�9M�u9E�t��M�ى]ԉE��hD�@h4�@����YYhL�@hH�@����YY�E������ �}u)���@j�Y�u�i����}tj�Y��	�U��jj�u������]�j��Y��tj��Y��u�= �@uh��1h��'YY�U��M3�;Š�@t
@��r�3�]ËŤ�@]�U������@3ʼnE�V�uWV�����Y���ySj�hY���j�WY��u
�= �@������Ah��@hh(�@��#��3ۅ��/hhZ�@Sf�b�@�H�@����uh�@VhZ�@�#������hZ�@��#@Y��<v5hZ�@��#�E��@��-Z�@j�h�@+�VQ��#������h �@h�(�@V��"������WhV�"����u{h h(�@V�<$���Wj��@��tI���tD3ۋˊO��
���f9Ot	A���r�S�����P�����P�]���!YP�����PV�D�@[�M�_3�^�%��SSSSS���L�@3Ʌ����P�@���jdh0�@�kj��Y3ۉ]�j@j _W�rYY�ȉM܅�uj��E�Ph��@�h%�����U�X�@�=��@;�s1f�A
�	��Y�a$��A$$�A$f�A%

�Y8�Y4��@�MܡX�@�ƍE�P�\�@f�}��)�E������M��E���E�;�|�ȉM�3�F�u�9
��@} j@W�
YY�ȉM܅����
��@�M��}ԋE؋U�;����2���tX���tS��tM�uV�P�@�U�t8���������4�X�@�u܋��E؊�Fh��FP�T�@�F�U�M�G�}ԋE�@�E؃��U�놉�X�@=��@��X�@;�s$f�A
�	��Y�a$�f�A%

�Y8�Y4��@�M���F�uЋM������]ԃ������5X�@�u܃>�t�>�t�F��F��F���uj�X�
�C�������P��@�����tE��tAW�P�@��t6�>%���u�F@���u	�F�Fh��FP�T�@�F�"�F@�F������\�@��t
���@����C�<����E������3���j�Y�U��QQ�=�@u�SVWh�X�@3�WS�\�@�`�@�5�@�=�@��t8u���E�P�E�PSSV�[�]��������?sE�M����s=��;�r6R���Y��t)�E�P�E�P��PWV��E���H��@�=�@3����_^[��U��ES�]V�#�u��EW�}��t�8���E3ɉM�>"u3�����F�ȉM�"�5���t��G��E��PF�%Y��t���t��GF�E��t�M��u�< t<	u���t�G��N�e�>���< t<	uF��>���U��t�:���U�E�3�B3��FA�>\t��>"u3��u�}t�F�8"u���
3�3�9E���E���I��t�\G���u���tA9Mu< t8<	t4��t*��P�.$Y��t��t��GF���G���tF��F�o�����t�G��-����U_^[��t�"�E�]Ã=�@u��
V�5��@W3���u���<=tGV�}FY���u�GjP�w	��YY�=�@��tʋ5��@S�>t>V�H�>=Y�Xt"jS�F	YY���t@VSP�u#����uH���>uȋ5��@V���%��@�'��@3�Y[_^�5�@��%�@����3�PPPPP��U��E� �@]�U��E��x!��~
��u�
`�@��
`�@�`�@��]������]�U������@�e��e�VW�N�@����;�t
��t	���@�f�E�P�l�@�E�3E�E��(�@1E��h�@1E��E�P�d�@�M�3M�E�3M�3�;�u�O�@����u��
G��ȉ
��@�щ
��@_^��VW���@���@����t�Ѓ�;�r�_^�VW���@���@����t�Ѓ�;�r�_^�U��QW�p�@��3���tuV��f9t��f9u���f9u�SPPP+�P�FVWPP�x�@�E���t7P���Y��t*3�PP�u�SVWPP�x�@��u	S�Y3�W�t�@���	W�t�@3�[^_�����������h�#@d�5�D$�l$�l$+�SVW���@1E�3�P�e�u��E��E������E��E�d�ËM�d�
Y__^[��]Q��������U���S�]VW�{3=��@�E��E���s���t
�O�30��O�G�30�u�E�@f���E�E�E�E�C��C�E�������@�@�L������E��t{���2��M����~~h�E�8csm�u(�=�@th�@�3����tj�u��@���U�M��E�U�9Pth��@V����E�X����tu�f�M��É]�����]�����tG�!�E���{�t6h��@V�˺����������t
�O�30�l�O�W�32�\�E�_^[��]ËO�30�E�O�G�30�5�M�֋I�E�jhP�@������@x��t�e����3�@Ëe��E�������hE%@�,�@�d�@�U�졀�@3��@t�u��]�]�%��@U�졄�@3��@�ut��]���@]�U�졈�@3��@�ut��]���@]�U�졌�@3��@�u�ut��]���@]�U��QV�5��@��y%��@3�3��@�u�t
V�M�Q�Ѓ�zuF�5��@3�����^��VWht�@��@�5<�@��h��@W��3��@h��@W���@��3��@h��@W���@��3��@h��@W���@��3��@h��@W���@��3��@h،@W���@��3��@h�@W���@��3��@h�@W���@��3��@h�@W���@��3��@h0�@W���@��3��@hP�@W���@��3��@hh�@W���@��3��@h��@W���@��3��@h��@W���@��3��@h��@W���@��3��@���@hč@W��3��@h�@W���@��3��@h�@W���@��3��@h �@W��@��3��@h4�@W��@��3��@hP�@W��@��3��@hd�@W��@��3��@ht�@W��@��3��@h��@W��@��3��@h��@W��@��3��@h��@W��@��3��@h��@W��@��3��@hԎ@W��@��3��@h�@W��@��3��@_��@^�U��u���@]�U��u���@P���@]�U��j���@�u�|�@]�U��V�u�<���@uV�qY��uj�!�Y�4���@���@^]�VW���@��S���t�tS�X�@S�"�'Y������@|�[�>t�~u�6�X�@������@|�_^�jhp�@����=P�@u�L�j��h��{�YY�}�<���@u[j�2Y��u�\�3��Aj
����Y�e��<���@uh�V�T�@�4���@�V�iY�E������	3�@����j
�7Y�VW���@�h�@�~u�>h��6���T�@������@|�3�_@^�U��E�4ň�@���@]�U��}t-�uj�5P�@���@��uV�
���@P�
Y�^]�U��VW3�j�u�u�������u'9��@vV���@�����;
��@v�����uË�_^]�U��SVW�=��@3��u�w��Y��u%��t!V���@�=��@�����;�v�����u�_^��[]�U��VW3��u�u����YY��u,9Et'9��@vV���@�����;��@v�����u���_^]�U��SV�5 �@W�}W�փxt�wx�֋����tP�փ|t�w|�֋����tP��jX�_�E�{���@t�;t�3�֋E�{�t�{�t�s��֋E��H�Eu΋���P��_^[]�U��SV�u3ۋ��W��tf=0�@t_�Fx��tX9uT�����t9uP���������YY�F|��t9uP��������YY�vx���������YY�����tD9u@���-�P��������+�P������+�P��������������=��@t9��uP�����j���YYjX����~�E����@t���t�8uP�?����3�8���YY�E��t�G���t�8uP����Y�E����H�Eu�V����Y_^[]�U��V�u����SW�=$�@V�׃~xt�vx�׋����tP�׃~|t�v|�׋����tP��jX�^�E�{���@t�;t�3�׋E�{�t�{�t�s��׋E��H�Eu΋�����Q��_[��^]�jh��@�q����	���
$�@�Npt"�~lt����pl��uj ��Y������j���Y�e��5�@�FlP�!YY��u��E������뼋u�j����Y�U��W�}��t;�E��t4V�0;�t(W�8����Y��tV����>Yu���@tV�O���Y��^�3�_]Ã=�@uj���Y��@3��U��V��M�F��uf���ЉV�Jl��Jh�N�;
�@t�$�@�Bpu������F;��@t�N�$�@�Apu���F�N�Ap�u���Ap�F�
���A�F��^]�U��E-�t&��t��
tHt3�]á`�@]á\�@]áX�@]áT�@]�U����M�j�.����E�%��@���u���@���@�,���u���@���@����u�E����@�@�}�t�M��ap���U��S�]VWh3��sWV�`3��ȉ{�{��������{������@��+���7�FIu�����9�AJu�_^[]�U��� ���@3ʼnE�SV�uW����P�v���@3ۿ�����È�����@;�r���ƅ���� ������Q���
;�s
Ƅ���� @;�v����u�S�v������PW������PjS�/S�v������WPW������PW��S���@������S�vWPW������Ph��S���$����M�����t�L��
������t�L ��
���������A;�r��Wj���X+‹ˉ����‰����� ��w
�L�A �����w�L �A�������A��;�r��M�_^3�[���jh��@�g������
$�@�Opt�lt�wh��uj ���Y���~��j
���Y�e��wh�u�;5��@t6��tV�$�@��u����@tV���Y���@�Gh�5��@�u�V� �@�E������뎋u�j
���Y�jhн@�����W��؉]��=����sh�u����Y�E;F�nh ���Y�؅��[���E�ph���3��3S�u�GYY���}���
�E�ph�$�@���E�u�Hh����@t
Q����Y�E�XhS� �@�E�@p���$�@��j
�s���Y�u��C���@�C���@�����@�ΉM�}f�DKf�M��@A��ΉM�}
�D����@A��u�}������@F��5��@�$�@��u���@=��@tP�\���Y���@S� �@�E�������1�}j
�!���Y��#���u����@tS����Y����3����f��U��� ���@3ʼnE�SV�u�u�-�����Y�]��uV���Y3��W3��ωM��9���@��A��0�M�=�r�����������P���@�����E�PS���@����h�FWP��^3�C����9]�vO�}��E�t!�P��t�����LA;�v����8uߍF���@Iu��v���������^��~3��ȋ�����~����9=��@tV�������h�FWP���U��k�0����@�E�8��t5�A��t+������s����@D�AC;�v��9u΋E�G���E�r��]�S�^�F�W�������j�N����@_f�f��R�IOu�V�<���Y3�_�M�^3�[�I��U���(���@3ʼnE��}�Wt	�u�Y����jL����jP������������0���������������������������������������f������f����f�����f�����f������f�����������E�����E���Dž0����@������E�����E�����E�������@�������P��Y��u��u�}�t	�u�Y�M�3�_�&
��U��E���@]�U��5��@�0�@��t]��u�u�u�u�u��3�PPPPP�������j�9��tjY�)Vj��Vj�u���V����^��E����u�d�@��U��V����MQ�� Y����0^]������u�`�@��U��M3�;��@t'@��-r�A�wj
X]�D���jY;��#���]��@]��������������U��ES�H<�V�A�Y��3��W��t�}�p;�r	�H�;�r
B��(;�r�3�_^[]��������������U��j�h�@h�#@d�P��SVW���@1E�3�P�E�d��e��E�h@�|����tT�E-@Ph@�R�������t:�@$���Ѓ��E������M�d�
Y_^[��]ËE�3Ɂ8�����Ëe��E�����3��M�d�
Y_^[��]�������U��E�MZf9t3�]ËH<�3��9PEu�f9Q��]�Vjj ���YY��V�,�@��@��@��ujX^Ã&3�^�jh�@������e��u�#Y��u��E����������Ëu��a���U��QSV�50�@W�5�@��5�@�E��֋؋E�;�����+��O��rvP���GY;�sG�;�s�Ƌ]��;�r
PS��YY��u�F;�r>PS��YY��t1��P���,�@��@�u�,�@�KQ��,�@��@�E�3�_^[��U��u��������YH]�V3���h�@�,�@��h�@����(r�^�U��5��@�0�@��t�u��Y��t3�@]�3�]�U��E���@]�U��E���@]�U��E���@]�5��@�0�@�U��E���@���@���@���@]�j$h0�@���3ۉ]�3��}؋u��Pt��jY+�t"+�t+�t^+�uH�k�����}؅�u���d�E���@���@�^�w\V�SYY���E��V�ƃ�t6��t#Ht�
���������E���@���@��E���@���@��E���@���@3�C�]�P�0�@�E܃�����uj�����tj�x�Y�e���t
��t��u�G`�EЃg`��uA�Gd�E��Gd���u/�
�@�щUԡ�@�;�}&��k��G\�dB�Uԋ
�@��j�,�@�M��E��������u �wdV�U�Y��u�]�}؅�tj�<�Y�V�U�Y��t
��t��u�EЉG`��u�ẺGd3����U��M��@V�u9qt��k�E��;�r�k�U;�s	9qu���3�^]���������������̋L$��t$�����tN��u���$��$�����~Ѓ�3ƒ���t�A���t2��t$��t��t�͍A��L$+�ÍA��L$+�ÍA��L$+�ÍA��L$+��U��VW�}��t�M��t�U��u3�f����j^�0�g�����_^]Ë�f�>t��Iu��t�+��f��Rf��tIu�3���u�f����j"�U��V�u��t�U��t�M��u3�f��u���j^�0�����^]�W��+��f��If��tJu�3�_��u�f��@���j"��U��Ef���f��u�+E�H]�U��U�MV��u
��u
9Mu&3��3��t�E��t��u3�f���u��u3�f����j^�0�h�����^]�SW�ً����u+��f�3�vf��t%Ou�� +��f��[f��tOtJu��u3�f���_[�{������u�E3�jPf�TA�X�3�f��i���j"�U���$���@3ʼnE��ES�,�@VW�E�E3�W�E�Ӌ�u��J��E�9=�@��hWh��@�Ā@��u$��@��W�hh��@�̀@���Sh��@V�<�@���?P��h��@V��@�<�@P��hȒ@V��@�<�@P��hܒ@V��@�<�@P�ӣ�@��th��@V�<�@P�ӣ�@�u���@��t�E�tP�Ȁ@9}�tjX�9}�t�5�@�0�@j���@�0�@;�tO95�@tGP��5�@�E�ӋM�E��t/��t+�х�t�M�Qj�M�QjP�U�t�E�u�u�� �0��@;�t$P�Ӆ�t�Ћ���t��@;�tP�Ӆ�tW�Ћ��u�5�@�Ӆ�tV�u�u�W���3��M�_^3�[���;
��@u��������SVW�T$�D$�L$URPQQhB@d�5���@3ĉD$d�%�D$0�X�L$,3�p���t;�T$4���t;�v.�4v�\���H�{u�h�C�"��C�4�d���_^[ËL$�A�t3�D$�H3��<���U�h�p�p�p�>�����]�D$�T$���U�L$�)�q�q�q(������]�UVWS��3�3�3�3�3���[_^]Ë��j�3�3�3�3�3���U��SVWjRh�B@Q�._^[]�U�l$RQ�t$�����]��`�@Vj^��u��;�}�ƣ`�@jP�v�YY�\�@��ujV�5`�@�]�YY�\�@��ujX^�3ҹ��@��� �R�� �@}�\�@��3�^���=��@t���5\�@����%\�@Y�U��V�u���@;�r"���@w��+�����P�*��N�Y�
�F P���@^]�U��E��}��P���EY�H�]ËE�� P���@]�U��E���@;�r=�@w�`���+�����P��Y]Ã� P���@]�U��M�E��}�`����AP���Y]Ã� P���@]�U���V�u�M��d��u�E�M�L0u3�9Ut�E��p#E��…�t3�B�}�^t�M��ap�����U��jj�uj�����]�U��V�u��t�U��t	�M��u��!�j^�0���^]�W��+���A��tJu�_��u����j"��3����/�����tj�M���Y� �@t!j�+��tjY�)jh@j����j����U��V�u���woSW�P�@��u�J��j���h��y���P�@YY��t���3�AQjP�Ԁ@����u&j[9H�@t
V�D���Y��u���=��6���_[�V�#���Y�"��3�^]�U��}u�u�]���Y]�V�u��u
�u�;�Y3��MS�0��uFV�uj�5P�@�؀@�؅�u^9H�@t@V��Y��t���v�V��Y���3�[^]�����@P��Y�������@P��Y�����U��V�u��tj�3�X��;Es�P��3��Q�u��uF3Ƀ��wVj�5P�@�Ԁ@�ȅ�u*�=H�@tV�
�Y��uЋE��t�봋E��t���^]�U��V�u�����F;<�@tP��Y�F;@�@tP��Y�F;D�@tP��Y�F;H�@tP���Y�F;L�@tP���Y�F ;P�@tP��Y�F$;T�@tP��Y�F8;h�@tP��Y�F<;l�@tP��Y�F@;p�@tP�x�Y�FD;t�@tP�f�Y�FH;x�@tP�T�Y�FL;|�@tP�B�Y^]�U��V�u��tY�;0�@tP�#�Y�F;4�@tP��Y�F;8�@tP��Y�F0;`�@tP���Y�F4;d�@tP���Y^]�U��V�u���n�v���v���v���v���v���v���6���v ���v$���v(�y��v,�q��v0�i��v4�a��v�Y��v8�Q��v<�I���@�v@�>��vD�6��vH�.��vL�&��vP���vT���vX���v\���v`���vd���vh����vl����vp����vt����vx����v|�����@����������������������������������v�����k�����`�����U�����J�����?�����4�����)�������������@��������������������������������������������������������������������������������������v�����k�����`����@���R�����G�����<�����1�����&��������� �����$�����(�����,������0������4������8������<������@�����D�����@��H�����L�����P�����T�~����X�s����\�h����`�]����^]�U��QQ���@3ʼnE�SV�uW��~!�E��I�8t@��u�����+�H;ƍp|��M$3���u
�E��@�E$��3�9E(j��jV�u��PQ�@�@�ȉM���u3��X~Kj�3�X��r?�M��w����܅�t����Q�����Y��t	������M��3ۅ�t�QSV�uj�u$�@�@�����u�jjVS�u�u������������Mt,�M ����;���Q�uVS�u�u������~Bj�3�X����r6�};�w����tf����P����Y��tQ������3���t@WV�u�S�u�u�+����t!3�PP9E uPP��u �uWVP�u$�x�@��V�`YS�YY�Ǎe�_^[�M�3�����U����u�M�����u(�E�u$�u �u�u�u�u�uP�����$�}�t�M��ap���U��E��t���8��uP���Y]�U��Q���@3ʼnE��MSVW3���u
�E��@�E��3�9E WW�u���u��PQ�@�@�؅�u3��~A���w9�]=w���t�����P�����Y��t�����������t��PWV���SV�u�uj�u�@�@��t�uPV�u�܀@��V����Y�Ǎe�_^[�M�3��W���U����u�M�����u �E�u�u�u�u�uP������}�t�M��ap�������������̋T$�L$��t�D$�%T�@s
�L$W�|$��]�T$���|�%��@��
W����r1�ك�t+ш����u����������ʃ���t��t
�����u��D$_ËD$Ã%X�@�U��}u�����o���]�uj�5P�@��@]�j����Y�U��SVW3����;�+‹��jU�4�8�@�u�����ty�^���~;�~Ѓ����<�@_^[]�U��}t�u���Y��x=�s	���@]�3�]�U���@3��@t3�QQQ�u�u�u�u�u�u��]�u�u�u�u�u�u���YP��@]�U��V�u3���t^�MSW�}jA[jZZ+��U�jZZ�f;�r
f;�w�� ������f;�rf;Ew�� ����Nt
f��tf;�t�����_+�[^]�������������WV�t$�L$�|$�����;�v;��h�%T�@s���������3Ʃu�%��@���%T�@������������s
����v����s�~���vf����tc����foN�v�fo^��0foF fon0�v0��0fo�f:�ffo�f:�fGfo�f:�fo �0}��v�foN��v��Ifo^��0foF fon0�v0��0fo�f:�ffo�f:�fGfo�f:�fo �0}��v�VfoN��v���fo^��0foF fon0�v0��0fo�f:�ffo�f:�fGfo�f:�fo �0}��v��|�o���vf�����s
����v����s�~���vf�����T@���u������r*�$��T@��Ǻ��r���$��S@�$��T@��$�<T@��S@�S@T@#ъ��F�G�F���G������r��$��T@�I#ъ��F���G������r��$��T@�#ъ���������r��$��T@�I�T@�T@�T@|T@tT@lT@dT@\T@�D��D��D��D��D��D��D��D��D��D��D���D���D���D�������$��T@���T@�T@�T@�T@�D$^_Ð���D$^_Ð���F�G�D$^_ÍI���F�G�F�G�D$^_Ð�t1��|9���u$������r
��$�DV@����$��U@�I�Ǻ��r��+�$�HU@�$�DV@�XU@|U@�U@�F#шG��������r���$�DV@�I�F#шG�F���G������r���$�DV@��F#шG�F�G�F���G�������V�����$�DV@�I�U@V@V@V@V@ V@(V@;V@�D��D��D��D��D��D��D��D��D��D��D��D��D��D������$�DV@��TV@\V@lV@�V@�D$^_Ð�F�G�D$^_ÍI�F�G�F�G�D$^_Ð�F�G�F�G�F�G�D$^_Í�$W�ƃ������у���te��$�fofoNfoV fo^0ffOfW f_0fof@fonPfov`fo~pfg@foPfw`fp������Ju���tO�����t��fof�v�Ju��t*����t
���v�Iu�ȃ�t��FGIu���X^_Í�$���̺+�+�Q�‹ȃ�t	��FGIu���t
���v�Hu�Y���U��E��u�,�������]Ë@]�U��M���u
���	�8��x$;
��@s��������X�@���D��@]������	�X��3�]�U��V�u��u	V�Y�/V�,Y��t����F@tV�U���P�Y��Y��3�^]�U��SV�u3ۋF$<uB�Ft9W�>+~��~.W�vV����YP���;�u�F��y��F��N ��_�N�f�^��[]�j�Y�jhP�@�e��3��}�!}�j����Y!}�3��]�u�;5`�@���\�@����t]�@�tWPV��YY�E��\�@���@�t0��uP����Y���tG�}����u�@tP����Y���u	E܃e��F녋]�}�u�\�@�4�V��YY��E����������t�E�����Ë]�}�j�a��Y�U����@j�D�@�����u�����=D�@YYuj���Yh	����Y]�U���$j���tjY�)�(�@�
$�@� �@��@�5�@�=�@f�@�@f�
4�@f��@f��@f�%�@f�-�@��8�@�E�,�@�E�0�@�E�<�@������x�@�0�@�4�@�(�@	��,�@�8�@jXk�ǀ<�@jXk��
��@�L�jX���
��@�L�h��@�������������U��SVWUjjh[@�u�]_^[��]ËL$�A�t2�D$�H�3��,�U�h�P(R�P$R���]�D$�T$���SVW�D$UPj�h [@d�5���@3�P�D$d��D$(�X�p���t:�|$,�t;t$,v-�4v���L$�H�|�uh�D��I�D��_뷋L$d�
��_^[�3�d�
�y [@u�Q�R9Qu��SQ���@�SQ���@�L$�K�C�kUQPXY]Y[����jhx�@�
��3��}�j�o��Y!}�j^�u�;5`�@}S�\�@����tD�@�tP�B
Y���tG�}�|)�\�@���� P�X�@�\�@�4����Y�\�@�$�F��E������������Ë}�j�P��Y�jh��@�p����@95�@t*j����Y�e�Vh�@�K��YY��@�E�������y���j���Y�����������Q�L$+ȃ����Y�Q�L$+ȃ����Y����uf��fn�f`�fa�fp�SQ�ك���ux�ڃ���t0ffAfA fA0fA@fAPfA`fAp���KuЅ�t7���t��If�IKu���t���t
f~�IJu���t�AKu�X[�ۃ�+�R�Ӄ�t�AJu���t
f~�IKu�Z�^���U��%P�@��S3�C	��@j
�r���3ɋÉP�@�V�5��@W�}����_�O�W�E��5��@t���P�@�5��@�E�t���P�@�5��@j3�X��u��^�N�V�E��5T�@t	���5T�@3�3���}��_�O�W�}�Genuu_�}�ineIuV�}�nteluM3�@3����_�O�W�E�%�?�=�t#=`t=pt=Pt=`t=pu	���5T�@_^3�[��jh��@�����}���u�y���	�����;=��@������E�߃�����X�@�D��ttW�
Y3��u��E��X�@�Dt(W�YP��@��u��@��u�t�����0�����	���u��E������
���!�}�u�W�Y�����	�F�����S���jhؾ@����u���u�_��� ����	�����;5��@�������������X�@�D8��tcV��	Y�e���X�@�D8t�u�uV�_���������	����� ���}��E������
���)�u�}�V��
Y����� �����	�d�����q���U����	���@3ʼnE��E�M3�W����@���D���<���,�9Uu3�����u�G��!8�t���������SV����������0���X�@����\$����t��u+�E�Шu����!8��������L��@��D t
jRRP�S
����@�����Y�����0���X�@�D��軲���@l3�9�����P��0�����X�@�4��@���@����9�@�t������@��D�!�$��ʉ����4�9}�~3���8�Dž�
�����	3���
����@���0���X�@�|8t�D4�E�j�E�M��d8P�Z��P�>	Y��tD��D���4�+�E����jR��<�P�
���������4�@��8��&j��4���<�P�V
���������4�3�QQ@��8�j��4��E�Pj��<�PQ����x�@������kj��$�QP�E�P��0���X�@�4�D�@������8�����,�9�$��!��@���j��$�Pj�E�P��0��E�
��X�@�4�D�@������$�����,�G���t��u3�3�f;����<���8��ƒ�����4���8���@���t��uU��<��.	Yf;�<������@�t$j
XP��<��	Yf;�<���G��,���8���4�;E�����#��0����X�@G�D4��X�@�D8��@����@����0���X�@�D��U��D�3���8������‰�<�9u��3�+‹�<���H���@�;EsD�
B@��#���
��@���<�u��,��
CA��#����<�CA��@����r������H�+�j��(�PS��H�P��0���X�@�4�D�@�����(���D�9�(�����<�+�;E��<��5�����ʀ�����@�9u��Dž�
�����,���+‹����H�;Es>�1������@�f;��uj
Yf���@�������f�3�������r������H�+�j��(�PS��H�P��0���,���X�@�4�D�@��8���<������(���D���<�9�(�����@���+�;E� �����]��$�����Dž�
�����$�+ʋ����H���;�s;�>������$�f;��uj
^f�0��$�����f�8�������r�3�VVhU
����Q��H���+��+��P��PVh��x�@��8���<���4�����3ɉ�@�j+���(�RP��������P��0���X�@�4�D�@��t��@��(���4���@�;�����@��@���4���8�;�Q��$���D���+���<�;������7j��(�Q�u��D��4�D�@��t
��(�3����@��D���uc��t$j[;�u�
���	������?V����Y�6��0������X�@�D@t	�:u3�� ��������� ���+�,���^[�M�3�_�����U��V�uW����u���������E�F�t9V���V����V�(�P�|����y����~t
�v����fY�f��_^]�jh��@艹�����}�3��u������u���������裹���F@t�f��V�g��Y�e�V�?���Y���}��E�������Nju�}�V���Y���������������Q�L$+����#ȋ�%�;�r
��Y���$�-���jh�@�ٸ���}���������4�X�@�~u0j
�#���Y�e��~uh��FP�T�@�F�E������*���������X�@���P���@3�@諸��Ë}j
�-���Y�U��EVW��x`;��@sX�������X�@�����Dt=�<�t7�= �@u3�+�tHtHuQj��Qj��Qj���@��X�@��3������	�H��� ��_^]�U��M���u�.��� �Z���	�B��x&;
��@s��������X�@���Dt�]������ ����	�����]�U��M����������X�@���P���@]�U����u�M�����E��M����H%��}�t�M��ap���U��j�u���YY]�U��QQV�uWV������Y;�u����	�Nj��D�u�M�Q�u�uP���@��u��@P�0��Y�Ӌ������X�@���d0��E��U�_^��U���SV�u��t�]��t�>u�E��t3�f�3�^[��W�u�M�����E�u�M��t�f�3�G��E�P�P���YY��t@�}�t~';_t|%3�9E��P�u�wtVj	�w�@�@�}�u;_tr.�~t(�t�13�9E��3�GP�u�E�WVj	�p�@�@��u�X�����*�}�t�M��ap���_�6���U��j�u�u�u�����]�U��Q���@���u
�����@���u�����j�M�Qj�MQP���@��t�f�E��jh8�@�6����u���u���� ����	���xy;5��@sq�����������X�@�D8��tSV����Y�e���X�@�D8tV�UY����b���	���}��E������
���)�u�}�V�/���Y����� �+���	�����辴���U��VW�}W���Y���tP�X�@��u	���u��u�@Dtj�l���j���c���YY;�tW�W���YP��@��u
��@���3�W���Y�������X�@���D9��tV�h��Y���3�_^]�U��V�u�F�t �Ft�v誺���f����3�Y��F�F^]á��@���t���tP��@�3�PPjPjh@h �@��@���@���������V�D$�u(�L$�D$3��؋D$����d$�ȋ�d$��G�ȋ\$�T$�D$���������u���d$�ȋD$���r;T$wr;D$v	N+D$T$3�+D$T$��؃��ʋӋًȋ�^�����������̋D$�L$ȋL$u	�D$���S��؋D$�d$؋D$���[��%��@�%Ѐ@��d������� �8�P�f�v��������������<�T�f�|������������,�J�^�r�~�����������������$�0�D�`�r�������������������"�2�F�V����.@�9@�B@$^@M@�\@�o@EC@�����������������������	mscoree.dllCorExitProcessR6008

- not enough space for arguments

R6009

- not enough space for environment

R6010

- abort() has been called

R6016

- not enough space for thread data

R6017

- unexpected multithread lock error

R6018

- unexpected heap error

R6019

- unable to open console device

R6024

- not enough space for _onexit/atexit table

R6025

- pure virtual function call

R6026

- not enough space for stdio initialization

R6027

- not enough space for lowio initialization

R6028

- unable to initialize heap

R6030

- CRT not initialized

R6031

- Attempt to initialize the CRT more than once.
This indicates a bug in your application.

R6032

- not enough space for locale information

R6033

- Attempt to use MSIL code from this assembly during native code initialization
This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain.

R6034

- inconsistent onexit begin-end variables

DOMAIN error

SING error

TLOSS error



runtime error X�@�@	p�@
Ȃ@�@h�@ȃ@�@h�@؄@(�@��@�@T�@��@ `�@!ȇ@"��@x �@y@�@z\�@�x�@���@R6002

- floating point support not loaded

Runtime Error!

Program: <program name unknown>...

Microsoft Visual C++ Runtime Librarykernel32.dllFlsAllocFlsFreeFlsGetValueFlsSetValueInitializeCriticalSectionExCreateSemaphoreExWSetThreadStackGuaranteeCreateThreadpoolTimerSetThreadpoolTimerWaitForThreadpoolTimerCallbacksCloseThreadpoolTimerCreateThreadpoolWaitSetThreadpoolWaitCloseThreadpoolWaitFlushProcessWriteBuffersFreeLibraryWhenCallbackReturnsGetCurrentProcessorNumberGetLogicalProcessorInformationCreateSymbolicLinkWSetDefaultDllDirectoriesEnumSystemLocalesExCompareStringExGetDateFormatExGetLocaleInfoExGetTimeFormatExGetUserDefaultLocaleNameIsValidLocaleNameLCMapStringExGetCurrentPackageIdSunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssSunMonTueWedThuFriSatSundayMondayTuesdayWednesdayThursdayFridaySaturdayJanFebMarAprMayJunJulAugSepOctNovDecJanuaryFebruaryMarchAprilJuneJulyAugustSeptemberOctoberNovemberDecemberAMPMMM/dd/yydddd, MMMM dd, yyyyHH:mm:ssen-USd�@p�@|�@��@ja-JPzh-CNko-KRzh-TWUSER32.DLLMessageBoxWGetActiveWindowGetLastActivePopupGetUserObjectInformationWGetProcessWindowStation         (((((                  H����������������������          h((((                  H����������������������                                 H�����������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~�����������������������������������������������������������������������������������������������������������������������������X�@`�@h�@p�@��@��@��@��@	��@
��@��@��@
��@ȩ@Щ@ة@�@�@�@��@�@�@�@�@ �@(�@0�@8�@@�@H�@ P�@!X�@"`�@#h�@$p�@%x�@&��@'��@)��@*��@+��@,��@-��@/��@6��@7Ȫ@8Ъ@9ت@>�@?�@@�@A��@C�@D�@F�@G�@I �@J(�@K0�@N8�@O@�@PH�@VP�@WX�@Z`�@eh�@p�@t�@��@��@��@��@��@��@��@	H�@ȫ@ԫ@
�@�@��@�@d�@|�@�@�@(�@4�@@�@L�@X�@d�@p�@|�@��@��@ ��@!��@"��@#Ĭ@$Ь@%ܬ@&�@'�@)�@*�@+�@,$�@-<�@/H�@2T�@4`�@5l�@6x�@7��@8��@9��@:��@;��@>��@?̭@@ح@A�@C�@D�@E�@F �@G,�@I8�@JD�@KP�@L\�@Nh�@Ot�@P��@R��@V��@W��@Z��@eĮ@kԮ@l�@��@��@p�@�@	�@
 �@,�@8�@D�@P�@\�@h�@��@,��@;��@>��@C��@kԯ@�@�@��@	�@
�@ �@,�@;D�@kP�@`�@l�@x�@	��@
��@��@��@;��@İ@а@ܰ@	�@
�@�@�@;$�@4�@	@�@
L�@X�@d�@;|�@��@	��@
��@��@;ȱ@ ر@	 �@
 �@; ��@$�@	$�@
$$�@;$0�@(@�@	(L�@
(X�@,d�@	,p�@
,|�@0��@	0��@
0��@4��@	4��@
4IJ@8в@
8ܲ@<�@
<�@@�@
@�@
D�@
H$�@
L0�@
P<�@|H�@|X�@p�@B��@,`�@qX�@l�@�x�@���@���@���@���@���@���@�̳@�س@��@��@���@C�@��@� �@���@),�@�D�@kh�@!\�@c`�@h�@Dt�@}��@�h�@��@E��@��@G��@���@��@H��@ȴ@�Դ@��@I�@���@�h�@A�@���@�@J��@ �@�,�@�8�@�D�@�P�@�\�@�h�@�t�@���@���@���@K��@���@���@	��@�ȵ@�Ե@��@��@���@��@��@��@�(�@�4�@�@�@�L�@�X�@�d�@�p�@�|�@���@���@�x�@#��@e��@*��@l��@&��@h��@
Ķ@LЪ@.ж@s��@ܶ@��@��@��@M�@��@�P�@>$�@��@70�@��@<�@Nت@/H�@t �@T�@�`�@Zȩ@
l�@O��@(x�@jX�@��@aЩ@��@Pة@��@���@Q�@��@RȪ@-��@r�@1̷@x0�@:ط@��@X�@?�@��@S�@2�@y��@%�@g��@$�@f$�@���@+0�@m<�@�H�@=H�@�8�@;T�@��@0`�@�l�@wx�@u��@U�@��@���@T��@���@��@��@6��@~�@̸@V�@ظ@W�@��@��@��@��@ �@X�@,�@Y@�@<8�@�D�@�P�@v\�@�(�@h�@[p�@"t�@d��@���@���@���@���@�й@�0�@�@\X�@��@��@��@�4�@�8�@L�@�X�@]��@3d�@z`�@@p�@� �@8��@�(�@9��@�@�@��@^��@nH�@��@_�@5��@|`�@ Ⱥ@bP�@Ժ@`�@4�@���@{��@'�@i�@o(�@8�@�H�@�T�@�`�@�l�@�x�@F��@parbgcazh-CHScsdadeelenesfifrhehuisitjakonlnoplptroruhrsksqsvthtruridukbesletlvltfavihyazeumkafkafohimskkkyswuzttpagutateknmrsamnglkoksyrdivar-SAbg-BGca-EScs-CZda-DKde-DEel-GRfi-FIfr-FRhe-ILhu-HUis-ISit-ITnl-NLnb-NOpl-PLpt-BRro-ROru-RUhr-HRsk-SKsq-ALsv-SEth-THtr-TRur-PKid-IDuk-UAbe-BYsl-SIet-EElv-LVlt-LTfa-IRvi-VNhy-AMaz-AZ-Latneu-ESmk-MKtn-ZAxh-ZAzu-ZAaf-ZAka-GEfo-FOhi-INmt-MTse-NOms-MYkk-KZky-KGsw-KEuz-UZ-Latntt-RUbn-INpa-INgu-INta-INte-INkn-INml-INmr-INsa-INmn-MNcy-GBgl-ESkok-INsyr-SYdiv-MVquz-BOns-ZAmi-NZar-IQde-CHen-GBes-MXfr-BEit-CHnl-BEnn-NOpt-PTsr-SP-Latnsv-FIaz-AZ-Cyrlse-SEms-BNuz-UZ-Cyrlquz-ECar-EGzh-HKde-ATen-AUes-ESfr-CAsr-SP-Cyrlse-FIquz-PEar-LYzh-SGde-LUen-CAes-GTfr-CHhr-BAsmj-NOar-DZzh-MOde-LIen-NZes-CRfr-LUbs-BA-Latnsmj-SEar-MAen-IEes-PAfr-MCsr-BA-Latnsma-NOar-TNen-ZAes-DOsr-BA-Cyrlsma-SEar-OMen-JMes-VEsms-FIar-YEen-CBes-COsmn-FIar-SYen-BZes-PEar-JOen-TTes-ARar-LBen-ZWes-ECar-KWen-PHes-CLar-AEes-UYar-BHes-PYar-QAes-BOes-SVes-HNes-NIes-PRzh-CHTsraf-zaar-aear-bhar-dzar-egar-iqar-joar-kwar-lbar-lyar-maar-omar-qaar-saar-syar-tnar-yeaz-az-cyrlaz-az-latnbe-bybg-bgbn-inbs-ba-latnca-escs-czcy-gbda-dkde-atde-chde-dede-lide-ludiv-mvel-gren-auen-bzen-caen-cben-gben-ieen-jmen-nzen-phen-tten-usen-zaen-zwes-ares-boes-cles-coes-cres-does-eces-eses-gtes-hnes-mxes-nies-paes-pees-pres-pyes-sves-uyes-veet-eeeu-esfa-irfi-fifo-fofr-befr-cafr-chfr-frfr-lufr-mcgl-esgu-inhe-ilhi-inhr-bahr-hrhu-huhy-amid-idis-isit-chit-itja-jpka-gekk-kzkn-inkok-inko-krky-kglt-ltlv-lvmi-nzmk-mkml-inmn-mnmr-inms-bnms-mymt-mtnb-nonl-benl-nlnn-nons-zapa-inpl-plpt-brpt-ptquz-boquz-ecquz-pero-roru-rusa-inse-fise-nose-sesk-sksl-sisma-nosma-sesmj-nosmj-sesmn-fisms-fisq-alsr-ba-cyrlsr-ba-latnsr-sp-cyrlsr-sp-latnsv-fisv-sesw-kesyr-syta-inte-inth-thtn-zatr-trtt-ruuk-uaur-pkuz-uz-cyrluz-uz-latnvi-vnxh-zazh-chszh-chtzh-cnzh-hkzh-mozh-sgzh-twzu-za(�@x�@	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ACONOUT$H��@��@�#B [������������@�@������������@�����@�����������@����@������������@����|�������~@�����������e%@i%@������������)@�����������C.@�����������v2@������������3@�����������)9@<9@������������9@�������������<@������������Y@~Y@������������\@�����������]@�����������`@������������`@�����������$j@������������j@������������n@�������r����d������� �8�P�f�v��������������<�T�f�|������������,�J�^�r�~�����������������$�0�D�`�r�������������������"�2�F�V����SendMessageTimeoutAUSER32.dll�GetStdHandlelstrlenA�WriteConsoleA�GetCommandLineAjGetLastErrorSetLastErrorqInterlockedIncrementmInterlockedDecrement(GetCurrentThreadId<EncodePointerDecodePointermExitProcess�GetModuleHandleExW�GetProcAddress�MultiByteToWideChar�WriteFile}GetModuleFileNameW�GetProcessHeapWGetFileTypefInitializeCriticalSectionAndSpinCountDeleteCriticalSection�GetStartupInfoW|GetModuleFileNameA<QueryPerformanceCounter$GetCurrentProcessId�GetSystemTimeAsFileTime@GetEnvironmentStringsW�FreeEnvironmentStringsW�WideCharToMultiByte�UnhandledExceptionFilterPSetUnhandledExceptionFilter#GetCurrentProcessoTerminateProcess�TlsAlloc�TlsGetValue�TlsSetValue�TlsFree�GetModuleHandleW@EnterCriticalSection�LeaveCriticalSectionQHeapFree_Sleep�IsValidCodePage�GetACP�GetOEMCP�GetCPInfo�IsDebuggerPresent�IsProcessorFeaturePresent�LoadLibraryExWOutputDebugStringW�LoadLibraryW�RtlUnwindMHeapAllocTHeapReAlloc�GetStringTypeWVHeapSize�LCMapStringW�FlushFileBuffers�GetConsoleCPGetConsoleMode/SetStdHandle	SetFilePointerEx�WriteConsoleW�CloseHandle�CreateFileWKERNEL32.dllEnvironmentCould not broadcast WM_SETTINGCHANGE���������
����C��@��@�@�@�@�@�@�@�@$�@,�@8�@D�@L�@X�@\�@`�@d�@h�@l�@p�@t�@x�@|�@��@��@��@��@��@��@h�@��@��@��@ď@Џ@؏@�@�@�@��@�@�@$�@,�@4�@<�@D�@L�@T�@\�@l�@|�@��@��@��@Đ@ؐ@�@�@�@��@�@�@�@�@ �@(�@0�@8�@H�@\�@h�@��@t�@��@��@��@��@��@ԑ@�@�@��@�@4�@H�@�@��@��@��@��@��@0�@�@��@�@��@��@�`�y�!�������@~�����ڣ ��@����ڣ ��A��Ϣ���[��@~��QQ�^� _�j�2�����1~��                          abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ                          abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
		


!
5A
CPR
S
WYl
m pr	�
�
�	��
�)�
���
���)P@)P@)P@)P@)P@)P@)P@)P@)P@)P@N�@���D��@��@����0�@.,�@�@�@�@�@�@�@�@�@�@��@ �@ �@ �@ �@ �@ �@ �@.�@�@ ��@����00(0P0_0t0�0�0�0�0�0�0�0!1&101j1o1v1|1�1N2�394^4h4�4�4	5585S5k5w5�5�5�5�5>6I6j6�6�6�6�6�6�6�6�6A7I7\7g7l7~7�7�7�7�7�7�7l8�8�8�8�8�8�8�8�899969;9G9L9k9�9�9::R:j:t:�:�:�:�:�:�:�:�:�:�:;;D;W;�;�;�;�;<$<*<b<n<�<�<==9=\=b=i=�=�=>9>R>�>�>�>�>�>�>"?(? ��0�0�01L1X1b1s1~1�1�1�1�1�122'202=2l2t22�2�2�2�2�2313<3Q3n3�3\4d4{4�4�4H5z5�5�5�5�5�5�5�5�5�5�5�5�5�5666 686I6O6U6\6e6j6p6x6}6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�677777#7(7.767;7A7I7N7T7\7a7g7o7t7y7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�788888%8-82888@8E8K8S8X8^8f8k8q8y88�8�8�8�8�8�8�8�8�89949=9I9T9y9�9�9�9�9�9
::+:1:G:M:_:�:�:�:�:�:�:#;,;:;U;�;�;�<�<K=�=�=�=!>�>�>�>�>�>�>?_?f?m?t?�?�?�?�?�?�?0�0Y0t0�1�1,292C2Q2Z2d2�233&393S3[3f3}3�3�3�3�3�3�3�3�3474q4�4�4T5�5�5�5/67O7Z7`7�7�7
8=8�8�8�8�8�8�8�9�9�9�9:::�:�:�:�:�:�:�:�:�:�:;$;1;9;?;K;P;U;Z;c;�;�;�;�;<<<< <}<�<�<�<=�?�?�?�?�?�?�?�?@�	00 0&0,040:0@0H0N0T0\0e0l0t0}0�0�0�0�0�0�0�01,1E1n1�1�1�2�2�2�2	33%343;3L3Z3e3t3~3�3�3�3�34<45U5t5�5�566#6Z6r6�6�6�67#757G7Y7k7}7�7�7�7�7�7�788,8>8P8�;K<�<�=0>m>�>�>t?�?�?P0#0N0w0�0�0�0�0�1�1�1u3�3�3�3�3�3�3�3�3454<4@4D4H4L4P4T4X4�4�4�4�4�45%5@5G5L5P5T5u5�5�5�5�5�5�5�5�5�5�5>6D6H6L6P6�78�89959�9�9�9�9: :&:,:2:8:?:F:M:T:[:b:i:q:y:�:�:�:�:�:�:�:�:�:�:�:;q;};�;<<:<[<b<�<�<�<�<�<�<�<�<=)>7>P>Y>x>�>�>�>�>�>�>R?`?�?�?�?�?�?`�F0y0�0�031�12P2_2}2�2�3�3�34(4�4565�5�5�6�6�7-868^8�8�89�9n:�:�:�:�:;;.;P;W;�;�;�;<�<�<�<T=�=�=�=>>@>V>r>�>*?4?P?�?�?�?�?�?p�0�0�H1 1$1(1,181<1@1�:�:�:�:�:�:�:�:�:�:�:�:;;;;$;,;4;<;D;L;T;�LT2X2\2`2;$;,;4;<;D;L;T;\;d;l;t;|;�;�;�;�;�;�;�;�;�;�;�;�;�;�;�;�;<<<<$<,<4<<<D<L<T<\<d<l<t<|<�<�<�<�<�<�<�<�<�<�<�<�<�<�<�<�<====$=,=4=<=D=L=T=\=d=l=t=|=�=�=�=�=�=�=�=�=�=�=�=�=�=�=�=�=>>>>$>,>4><>D>L>T>\>d>l>t>|>�>�>�>�>�>�>�>�>�>�>�>�>�>�>�>�>????$?,?4?<?D?L?T?\?d?l?t?|?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�`0000$0,040<0D0L0T0\0d0l0t0|0�0�0�0�0�0�0�0�0�0�0�0�0�0�0�0�01111$1,141<1D1L1T1\1d1l1t1|1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�12222$2,24282@2H2P2X2`2h2p2x2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�23333 3(30383@3H3P3X3`3h3p3x3�3�3�3�3�3�3�3�3�3�3�3�3�3�3�3�34444 4(40484@4H4P4X4`4h4p4x4�4�4�4�4�4�4�4�4�4�4�4�4�4�4�4�45555 5(50585@5H5P5X5`5h5p5x5�5�5�5�5�5�5�5�5�5�5�5�5�5�5�5�56666 6(60686@6H6P6X6`6h6p6x6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�6�67777 7(70787@7H7P7X7`7h7p7x7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�7�78888 8(80888@8H8P8X8`8h8p8x8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�8�89999 9(90989@9H9P9�H�;�;l<p<�<�<�<�<==(=H=d=h=�=�=�=�=>>(>H>h>t>�>�>�>�>?0?P?��1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�1�122222222 2$2(2,2024282<2@2D2H2L2P2T2X2d2h2l2p2t2x2|2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2�2333333<3L3\3l3|3�3�3�3�3�3�3h:l:p:t:x:|:�:�:�:�:�:�:(=0=4=8=<=@=D=H=L=P=T=`=d=h=l=p=t=x=|=�=�=�=@cscript //NoLogo %~dp0setenv.js
@pausevar shell = WScript.CreateObject("WScript.Shell");
var fs = new ActiveXObject("Scripting.FileSystemObject");

var PATH_KEY = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\Path";
var path = shell.RegRead(PATH_KEY);
var windrush = fs.GetParentFolderName(WScript.ScriptFullName)

var inPath = path.toLowerCase().indexOf(windrush.toLowerCase()) != -1;

WScript.Echo("Adding '" + windrush + "' to your PATH variable...");
if (inPath) {
    WScript.Echo("'" + windrush + "' is already in your PATH variable");
} else {
    try {
        shell.RegWrite(PATH_KEY, windrush + ";" + path, "REG_EXPAND_SZ");
        var oExec = shell.Exec(windrush + "\\tools\\bin\\notify_env_change.exe");
        while (oExec.Status == 0)
             WScript.Sleep(100);
        if (oExec.ExitCode != 0)
            WScript.Echo("Failed to notify the system about PATH change. Reboot required");
        WScript.Echo("Done.");
    } catch (err) {
        WScript.Echo("Could not write PATH variable to the registry.\nYou may have insufficient permissions to that. Try running this script as administrator");
    }

}

#!/bin/bash

########################################
# Configurations

BINTOOL_GIT=https://github.com/acquia/DevDesktopCommon.git
PHP_VER=5.4.41-nts-Win32-VC9-x86
# NOTE there in also "5.5" string in the download url. Change it if upgrading to 5.6
MYSQL_VER=5.5.41-win32
# goes to composer require command
DRUSH_VER=7.0.0

TOPDIR=windrush

########################################
# Utils

fail() {
  echo "$1">&2
  kill -s TERM $$
}

replace_in_file() {
  sed "s/$1/$2/" "$3" > aa.tmp
  mv aa.tmp "$3"
}

enable_php_extension() {
  for a in $1 ; do
    replace_in_file ";extension=php_$a.dll" "extension=php_$a.dll" "$2"
  done
}


########################################
# Prepare $TOPDIR dir

[ -e $TOPDIR ] && ( rm -r -f $TOPDIR || fail "Could not remove $TOPDIR dir" )
mkdir $TOPDIR


########################################
# MSYS & other binary tools
#

# msys & stuff from the DD repo
#svn export $BINTOOL_SVN $TOPDIR/tools || fail "Svn failed to export from $BINTOOL_SVN"
[ -e DevDesktopCommon ] && rm -r -f DevDesktopCommon
git clone $BINTOOL_GIT  || fail "Git failed to get $BINTOOL_GIT"
mv DevDesktopCommon/bintools-win/msys $TOPDIR/tools
rm -r -f DevDesktopCommon

# mysql
MYSQL_ZIP=mysql-$MYSQL_VER.zip
MYSQL_URL=http://dev.mysql.com/get/Downloads/MySQL-5.5/$MYSQL_ZIP
if [ ! -e $MYSQL_ZIP ]; then
  wget $MYSQL_URL || fail "Could not download MySQL from $MYSQL_URL"
fi

unzip -o -j $MYSQL_ZIP mysql-$MYSQL_VER/bin/mysql.exe -d $TOPDIR/tools/bin
unzip -o -j $MYSQL_ZIP mysql-$MYSQL_VER/bin/mysqldump.exe -d $TOPDIR/tools/bin

########################################
# PHP

PHP_ZIP=php-$PHP_VER.zip
PHP_URL=http://windows.php.net/downloads/releases/$PHP_ZIP
if [ ! -e $PHP_ZIP ]; then
  wget $PHP_URL || fail "Could not download PHP from $PHP_URL"
fi

unzip $PHP_ZIP -d $TOPDIR/php

PHP_INI=$TOPDIR/php/php.ini

cp "$PHP_INI-development" "$PHP_INI"
replace_in_file '; extension_dir = "ext"' 'extension_dir = "ext"' "$PHP_INI"
enable_php_extension "bz2 curl fileinfo gd2 gettext intl mbstring mysql mysqli openssl pdo_mysql soap sockets tidy xmrpc xsl" "$PHP_INI"

########################################
# Drush, composer and setenv stuff

cd $TOPDIR
php -r "readfile('https://getcomposer.org/installer');" | php
./composer.phar require drush/drush:$DRUSH_VER || fail "Composer failed"
cd ..

cp assets/drush.bat $TOPDIR
cp assets/composer.bat $TOPDIR
cp assets/setenv.bat $TOPDIR
cp assets/setenv.js $TOPDIR
cp assets/notify_env_change.exe $TOPDIR/tools/bin


########################################
# Zip everything up

[ -e $TOPDIR.zip ] && rm $TOPDIR.zip
zip -r $TOPDIR.zip $TOPDIR
cd ..


echo "Done."
<?php

/**
 * @file
 * Documentation of the Drush API.
 */

/**
 * Declare a new command.
 */
function hook_drush_command() {
  // To learn more, run `drush topic docs-commands` and
  // `drush topic docs-examplecommand`.
}

/**
 * All Drush commands are invoked in a specific order, using
 * drush-made hooks, very similar to the Drupal hook system. See drush_invoke()
 * for the actual implementation.
 *
 * For any commandfile named "hook", the following hooks are called, in
 * order, for the command "COMMAND":
 *
 * 0. drush_COMMAND_init()
 * 1. drush_hook_COMMAND_pre_validate()
 * 2. drush_hook_COMMAND_validate()
 * 3. drush_hook_pre_COMMAND()
 * 4. drush_hook_COMMAND()
 * 5. drush_hook_post_COMMAND()
 *
 * For example, here are the hook opportunities for a mysite.drush.inc file
 * that wants to hook into the `pm-download` command.
 *
 * 1. drush_mysite_pm_download_pre_validate()
 * 2. drush_mysite_pm_download_validate()
 * 3. drush_mysite_pre_pm_download()
 * 4. drush_mysite_pm_download()
 * 5. drush_mysite_post_pm_download()
 *
 * Note that the drush_COMMAND_init() hook is only for use by the
 * commandfile that defines the command.
 *
 * If any of hook function fails, either by calling drush_set_error
 * or by returning FALSE as its function result, then the rollback
 * mechanism is called.  To fail with an error, call drush_set_error:
 *
 *   return drush_set_error('MY_ERROR_CODE', dt('Error message.'));
 *
 * To allow the user to confirm or cancel a command, use drush_confirm
 * and drush_user_abort:
 *
 *   if (!drush_confirm(dt('Are you sure?'))) {
 *     return drush_user_abort();
 *   }
 *
 * The rollback mechanism will call, in reverse, all _rollback hooks.
 * The mysite command file can implement the following rollback hooks:
 *
 * 1. drush_mysite_post_pm_download_rollback()
 * 2. drush_mysite_pm_download_rollback()
 * 3. drush_mysite_pre_pm_download_rollback()
 * 4. drush_mysite_pm_download_validate_rollback()
 * 5. drush_mysite_pm_download_pre_validate_rollback()
 *
 * Before any command is called, hook_drush_init() is also called.
 * hook_drush_exit() is called at the very end of command invocation.
 *
 * @see includes/command.inc
 *
 * @see hook_drush_init()
 * @see drush_COMMAND_init()
 * @see drush_hook_COMMAND_pre_validate()
 * @see drush_hook_COMMAND_validate()
 * @see drush_hook_pre_COMMAND()
 * @see drush_hook_COMMAND()
 * @see drush_hook_post_COMMAND()
 * @see drush_hook_post_COMMAND_rollback()
 * @see drush_hook_COMMAND_rollback()
 * @see drush_hook_pre_COMMAND_rollback()
 * @see drush_hook_COMMAND_validate_rollback()
 * @see drush_hook_COMMAND_pre_validate_rollback()
 * @see hook_drush_exit()
 */

/**
 * @addtogroup hooks
 * @{
 */

/**
 * Take action before any command is run.
 *
 * Logging an error stops command execution.
 */
function hook_drush_init() {

}

/**
 * Initialize a command prior to validation.
 *
 * If a command needs to bootstrap to a higher level, this is best done in the
 * command init hook.  It is permisible to bootstrap in any hook, but note that
 * if bootstrapping adds more commandfiles (*.drush.inc) to the commandfile
 * list, the newly-added commandfiles will not have any hooks called until the
 * next phase.  For example, a command that calls drush_bootstrap_max() in
 * drush_hook_COMMAND() would only permit commandfiles from modules enabled in
 * the site to participate in drush_hook_post_COMMAND() hooks.
 */
function drush_COMMAND_init() {
  drush_bootstrap_max();
}

/**
 * Run before a specific command validates.
 *
 * Logging an error stops command execution, and the rollback function (if any)
 * for each hook implementation is invoked.
 *
 * @see drush_hook_COMMAND_pre_validate_rollback()
 */
function drush_hook_COMMAND_pre_validate() {

}

/**
 * Run before a specific command executes.
 *
 * Logging an error stops command execution, and the rollback function (if any)
 * for each hook implementation is invoked.
 *
 * @see drush_hook_COMMAND_validate_rollback()
 */
function drush_hook_COMMAND_validate() {

}

/**
 * Run before a specific command executes.
 *
 * Logging an error stops command execution, and the rollback function (if any)
 * for each hook implementation is invoked, in addition to the validate
 * rollback.
 *
 * @see drush_hook_pre_COMMAND_rollback()
 * @see drush_hook_COMMAND_validate_rollback()
 */
function drush_hook_pre_COMMAND() {

}

/**
 * Implementation of the actual drush command.
 *
 * This is where most of the stuff should happen.
 *
 * Logging an error stops command execution, and the rollback function (if any)
 * for each hook implementation is invoked, in addition to pre and
 * validate rollbacks.
 *
 * @return mixed|false
 *   The return value will be passed along to the caller if --backend option is
 *   present. A boolean FALSE indicates failure and rollback will be inititated.
 *
 * @see drush_hook_COMMAND_rollback()
 * @see drush_hook_pre_COMMAND_rollback()
 * @see drush_hook_COMMAND_validate_rollback()
 */
function drush_hook_COMMAND() {

}

/**
 * Run after a specific command executes.
 *
 * Logging an error stops command execution, and the rollback function (if any)
 * for each hook implementation is invoked, in addition to pre, normal
 * and validate rollbacks.
 *
 * @see drush_hook_post_COMMAND_rollback()
 * @see drush_hook_COMMAND_rollback()
 * @see drush_hook_pre_COMMAND_rollback()
 * @see drush_hook_COMMAND_validate_rollback()
 */
function drush_hook_post_COMMAND() {

}

/**
 * Take action after any command is run.
 */
function hook_drush_exit() {

}

/**
 * Adjust the contents of any command structure prior to dispatch.
 *
 * @see core_drush_command_alter()
 */
function hook_drush_command_alter(&$command) {

}

/**
 * Adjust the contents of a site alias.
 */
function hook_drush_sitealias_alter(&$alias_record) {
  // If the alias is "remote", but the remote site is
  // the system this command is running on, convert the
  // alias record to a local alias.
  if (isset($alias_record['remote-host'])) {
    $uname = php_uname('n');
    if ($alias_record['remote-host'] == $uname) {
      unset($alias_record['remote-host']);
      unset($alias_record['remote-user']);
    }
  }
}

/**
 * Take action after a project has been downloaded.
 */
function hook_drush_pm_post_download($project, $release) {

}

/**
 * Take action after a project has been updated.
 */
function hook_pm_post_update($project_name, $installed_release, $project) {
}

/**
 * Adjust the location a project should be copied to after being downloaded.
 *
 * See @pm_drush_pm_download_destination_alter().
 */
function hook_drush_pm_download_destination_alter(&$project, $release) {
  if ($some_condition) {
    $project['project_install_location'] = '/path/to/install/to/' . $project['project_dir'];
  }
}

/**
 * Automatically download project dependencies at pm-enable time.
 *
 * Use a pre-pm_enable hook to download before your module is enabled,
 * or a post-pm_enable hook (drush_hook_post_pm_enable) to run after
 * your module is enabled.
 *
 * Your hook will be called every time pm-enable is executed; you should
 * only download dependencies when your module is being enabled.  Respect
 * the --skip flag, and take no action if it is present.
 */
function drush_hook_pre_pm_enable() {
  // Get the list of modules being enabled; only download dependencies if our
  // module name appears in the list.
  $modules = drush_get_context('PM_ENABLE_MODULES');
  if (in_array('hook', $modules) && !drush_get_option('skip')) {
    $url = 'http://server.com/path/MyLibraryName.tgz';
    $path = drush_get_context('DRUSH_DRUPAL_ROOT');
    drush_include_engine('drupal', 'environment');
    if (drush_module_exists('libraries')) {
      $path .= '/' . libraries_get_path('MyLibraryName') . '/MyLibraryName.tgz';
    }
    else {
      $path .= '/' . drupal_get_path('module', 'hook') . '/MyLibraryName.tgz';
    }
    drush_download_file($url, $path) && drush_tarball_extract($path);
  }
}

/**
 * Sql-sync sanitization example.
 *
 * This is equivalent to the built-in --sanitize option of sql-sync, but
 * simplified to only work with default values on Drupal 6 + mysql.
 *
 * @see sql_drush_sql_sync_sanitize()
 */
function hook_drush_sql_sync_sanitize($source) {
  $table = drush_get_option('db-prefix') ? '{users}' : 'users';
  drush_sql_register_post_sync_op('my-sanitize-id',
    dt('Reset passwords and email addresses in user table.'),
    "UPDATE $table SET pass = MD5('password'), mail = concat('user+', uid, '@localhost') WHERE uid > 0;");
}

/**
 * Add help components to a command.
 */
function hook_drush_help_alter(&$command) {
  if ($command['command'] == 'sql-sync') {
    $command['options']['myoption'] = "Description of modification of sql-sync done by hook";
    $command['sub-options']['sanitize']['my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)";
  }
  if ($command['command'] == 'global-options') {
    // Recommended: don't show global hook options in brief global options help.
    if ($command['#brief'] === FALSE) {
      $command['options']['myglobaloption'] = 'Description of option used globally in all commands (e.g. in a commandfile init hook)';
    }
  }
}

/**
 * Add/edit options to cache-clear command.
 *
 * @param array $types
 *   Adjust types as needed. Is passed by reference.
 *
 * @param bool $include_bootstrapped_types
 *   If FALSE, omit types which require a FULL bootstrap.
 */
function hook_drush_cache_clear(&$types, $include_bootstrapped_types) {
  $types['views'] = 'views_invalidate_cache';
}

/**
 * Inform drush about one or more engine types.
 *
 * This hook allow to declare available engine types, the cli option to select
 * between engine implementatins, which one to use by default, global options
 * and other parameters. Commands may override this info when declaring the
 * engines they use.
 *
 * @return array
 *   An array whose keys are engine type names and whose values describe
 *   the characteristics of the engine type in relation to command definitions:
 *
 *   - description: The engine type description.
 *   - topic: If specified, the name of the topic command that will
 *     display the automatically generated topic for this engine.
 *   - topic-file: If specified, the path to the file that will be
 *     displayed at the head of the automatically generated topic for
 *     this engine.  This path is relative to the Drush root directory;
 *     non-core commandfiles should therefore use:
 *       'topic-file' => dirname(__FILE__) . '/mytopic.html';
 *   - topics: If set, contains a list of topics that should be added to
 *     the "Topics" section of any command that uses this engine.  Note
 *     that if 'topic' is set, it will automatically be added to the topics
 *     list, and therefore does not need to also be listed here.
 *   - option: The command line option to choose an implementation for
 *     this engine type.
 *     FALSE means there's no option. That is, the engine type is for internal
 *     usage of the command and thus an implementation is not selectable.
 *   - default: The default implementation to use by the engine type.
 *   - options: Engine options common to all implementations.
 *   - add-options-to-command: If there's a single implementation for this
 *     engine type, add its options as command level options.
 *   - combine-help: If there are multiple implementations for this engine
 *     type, then instead of adding multiple help items in the form of
 *     --engine-option=engine-type [description], instead combine all help
 *     options into a single --engine-option that lists the different possible
 *     values that can be used.
 *
 * @see drush_get_engine_types_info()
 * @see pm_drush_engine_type_info()
 */
function hook_drush_engine_type_info() {
  return array(
    'dessert' => array(
      'description' => 'Choose a dessert while the sandwich is baked.',
      'option' => 'dessert',
      'default' => 'ice-cream',
      'options' => 'sweetness',
      'add-options-to-command' => FALSE,
    ),
  );
}

/**
 * Inform drush about one or more engines implementing a given engine type.
 *
 *   - description: The engine implementation's description.
 *   - implemented-by: The engine that actually implements this engine.
 *       This is useful to allow the implementation of similar engines
 *       in the reference one.
 *       Defaults to the engine type key (e.g. 'ice-cream').
 *   - verbose-only: The engine implementation will only appear in help
 *       output in --verbose mode.
 *
 * This hook allow to declare implementations for an engine type.
 *
 * @see pm_drush_engine_package_handler()
 * @see pm_drush_engine_version_control()
 */
function hook_drush_engine_ENGINE_TYPE() {
  return array(
    'ice-cream' => array(
      'description' => 'Feature rich ice-cream with all kind of additives.',
      'options' => array(
        'flavour' => 'Choose your favorite flavour',
      ),
    ),
    'frozen-yogurt' => array(
      'description' => 'Frozen dairy dessert made with yogurt instead of milk and cream.',
      'implemented-by' => 'ice-cream',
    ),
  );
}

/**
 * Alter the order that hooks are invoked.
 *
 * When implementing a given hook we may need to ensure it is invoked before
 * or after another implementation of the same hook. For example, let's say
 * you want to implement a hook that would be called after drush_make. You'd
 * write a drush_MY_MODULE_post_make() function. But if you need your hook to
 * be called before drush_make_post_make(), you can ensure this by implemen-
 * ting MY_MODULE_drush_invoke_alter().
 *
 * @see drush_command_invoke_all_ref()
 */
function hook_drush_invoke_alter($modules, $hook) {
  if ($hook == 'some_hook') {
    // Take the module who's hooks would normally be called last.
    $module = array_pop($modules);
    // Ensure it'll be called first for 'some_hook'.
    array_unshift($modules, $module);
  }
}

/**
 * @} End of "addtogroup hooks".
 */
# BASH completion script for Drush.
#
# Place this in your /etc/bash_completion.d/ directory or source it from your
# ~/.bash_completion or ~/.bash_profile files.  Alternatively, source
# examples/example.bashrc instead, as it will automatically find and source
# this file.
#
# If you're using ZSH instead of BASH, add the following to your ~/.zshrc file
# and source it.
#
#   autoload bashcompinit
#   bashcompinit
#   source /path/to/your/drush.complete.sh

# Ensure drush is available.
command -v drush >/dev/null || alias drush &> /dev/null || return

__drush_ps1() {
  f="${TMPDIR:-/tmp/}/drush-env-${USER}/drush-drupal-site-$$"
  if [ -f $f ]
  then
    __DRUPAL_SITE=$(cat "$f")
  else
    __DRUPAL_SITE="$DRUPAL_SITE"
  fi

  # Set DRUSH_PS1_SHOWCOLORHINTS to a non-empty value and define a
  # __drush_ps1_colorize_alias() function for color hints in your Drush PS1
  # prompt. See example.prompt.sh for an example implementation.
  if [ -n "${__DRUPAL_SITE-}" ] && [ -n "${DRUSH_PS1_SHOWCOLORHINTS-}" ]; then
    __drush_ps1_colorize_alias
  fi

  [[ -n "$__DRUPAL_SITE" ]] && printf "${1:- (%s)}" "$__DRUPAL_SITE"
}

# Completion function, uses the "drush complete" command to retrieve
# completions for a specific command line COMP_WORDS.
_drush_completion() {
  # Set IFS to newline (locally), since we only use newline separators, and
  # need to retain spaces (or not) after completions.
  local IFS=$'\n'
  # The '< /dev/null' is a work around for a bug in php libedit stdin handling.
  # Note that libedit in place of libreadline in some distributions. See:
  # https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214
  COMPREPLY=( $(drush --early=includes/complete.inc "${COMP_WORDS[@]}" < /dev/null 2> /dev/null) )
}

# Register our completion function. We include common short aliases for Drush.
complete -o bashdefault -o default -o nospace -F _drush_completion d dr drush drush5 drush6 drush7 drush8 drush.php
drush_version=8.1.16
#!/usr/bin/env sh
#
# This script is a simple launcher that will run Drush with the most appropriate
# php executable it can find.  In most cases, the 'drush' script should be
# called first; it will in turn launch this script.
#
# Solaris users: Add /usr/xpg4/bin to the head of your PATH
#

# Get the absolute path of this executable
SELF_DIRNAME="`dirname -- "$0"`"
SELF_PATH="`cd -P -- "$SELF_DIRNAME" && pwd -P`/`basename -- "$0"`"

# Decide if we are running a Unix shell on Windows
if `which uname > /dev/null 2>&1`; then
  case "`uname -a`" in
    CYGWIN*)
      CYGWIN=1 ;;
    MINGW*)
      MINGW=1 ;;
  esac
fi

# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink.
while [ -h "$SELF_PATH" ]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) Get the pwd
    # 4) Append the basename
    DIR="`dirname -- "$SELF_PATH"`"
    SYM="`readlink "$SELF_PATH"`"
    SYM_DIRNAME="`dirname -- "$SYM"`"
    SELF_PATH="`cd "$DIR" && cd "$SYM_DIRNAME" && pwd`/`basename -- "$SYM"`"
done

# If not exported, try to determine and export the number of columns.
# We do not want to run `tput cols` if $TERM is empty, "unknown", or "dumb", because
# if we do, tput will output an undesirable error message to stderr.  If
# we redirect stderr in any way, e.g. `tput cols 2>/dev/null`, then the
# error message is suppressed, but tput cols becomes confused about the
# terminal and prints out the default value (80).
if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] && [ "$TERM" != unknown ] && [ -n "`which tput`" ] ; then
  # Note to cygwin/mingw/msys users: install the ncurses package to get tput command.
  # Note to mingw/msys users: there is no precompiled ncurses package.
  if COLUMNS="`tput cols`"; then
    export COLUMNS
  fi
fi

if [ -n "$DRUSH_PHP" ] ; then
  # Use the DRUSH_PHP environment variable if it is available.
  php="$DRUSH_PHP"
else
  # On MSYSGIT, we need to use "php", not the full path to php
  if [ -n "$MINGW" ] ; then
    php="php"
  else
    # Default to using the php that we find on the PATH.
    # We check for a command line (cli) version of php, and if found use that.
    # Note that we need the full path to php here for Dreamhost, which behaves oddly.  See http://drupal.org/node/662926
    php="`which php-cli 2>/dev/null`"

    if [ ! -x "$php" ]; then
      php="`which php 2>/dev/null`"
    fi

    if [ ! -x "$php" ]; then
      echo "ERROR: can't find php."; exit 1
    fi
  fi
fi

# Build the path to drush.php.
SCRIPT_PATH="`dirname "$SELF_PATH"`/drush.php"
if [ -n "$CYGWIN" ] ; then
  # try to determine if we are running cygwin port php or Windows native php:
  if [ -n "`"$php" -i | grep -E '^System => Windows'`" ]; then
    SCRIPT_PATH="`cygpath -w -a -- "$SCRIPT_PATH"`"
  else
    SCRIPT_PATH="`cygpath -u -a -- "$SCRIPT_PATH"`"
  fi
fi

# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir
# Last found wins, so search in reverse priority order
for conf_dir in "`dirname "$SELF_PATH"`" /etc/drush "$HOME/.drush" ; do
  if [ ! -d "$conf_dir" ] ; then
    continue
  fi
  # Handle paths that don't start with a drive letter on MinGW shell. Equivalent to cygpath on Cygwin.
  if [ -n "$MINGW" ] ; then
    conf_dir=`sh -c "cd \"$conf_dir\"; pwd -W"`
  fi
  if [ -f "$conf_dir/php.ini" ] ; then
    drush_php_ini="$conf_dir/php.ini"
  fi
  if [ -f "$conf_dir/drush.ini" ] ; then
    drush_php_override="$conf_dir/drush.ini"
  fi
done
# If the PHP_INI environment variable is specified, then tell
# php to use the php.ini file that it specifies.
if [ -n "$PHP_INI" ] ; then
  drush_php_ini="$PHP_INI"
fi
# If the DRUSH_INI environment variable is specified, then
# extract all ini variable assignments from it and convert
# them into php '-d' options. These will override similarly-named
# options in the php.ini file
if [ -n "$DRUSH_INI" ] ; then
  drush_php_override="$DRUSH_INI"
fi

# Add in the php file location and/or the php override variables as appropriate
if [ -n "$drush_php_ini" ] ; then
  php_options="--php-ini $drush_php_ini"
fi
if [ -n "$drush_php_override" ] ; then
  php_options=`grep '^[a-z_A-Z0-9.]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' '  '`
fi
# If the PHP_OPTIONS environment variable is specified, then
# its contents will be passed to php on the command line as
# additional options to use.
if [ -n "$PHP_OPTIONS" ] ; then
  php_options="$php_options $PHP_OPTIONS"
fi

# Pass in the path to php so that drush knows which one to use if it
# re-launches itself to run subcommands.  We will also pass in the php options.
# Important note: Any options added here must be removed  when Drush processes
# a #! (shebang) script.  @see drush_adjust_args_if_shebang_script()
exec "$php" $php_options "$SCRIPT_PATH" --php="$php" --php-options="$php_options" "$@"
#!/usr/bin/env php
<?php

/**
 * @file
 * drush is a PHP script implementing a command line shell for Drupal.
 *
 * @requires PHP CLI 5.4.5, or newer.
 */

require __DIR__ . '/includes/preflight.inc';
exit(drush_main());
�PNG


IHDR��|�O	pHYs��tEXtSoftwareAdobe ImageReadyq�e<Z}IDATx��}�\Wy�w���l/ZiWҪ�*�^w�XS��`�!t^��C��!�$$����.`l�`c0�%l�JV�%�Ҫ��J��{�9wn9��;������jfs����+�ǜ�_��Ivw�{�ׇ�g�t�V>%�|Ӂ�vs#��zIg��o�����-��2��U�]�o�L�M���`�T����v֏�~���Ǻ{�w�v�

w6|f]@���!��`�q����*�����_��<�}j�������N�N�#�q�a�e��G�����&�w�3�+r[K?����C'����f�жbF����m.�k�	*`�������4�����Ax��^�㯂�H�~��` w?h�ם"d� @��M*����L�?̎�/U�\׍k1fYK"�1��6?7�4��Qq�L��y������D�|$�*�!D��ݏ���͂h��_���奀2��
���o6�&�*XK�iX��~F2��E0��i���0�i(�K��`À�`9��-�Yl����@+mN��d���
�Ս5����ǎg(5�1�l���p6��m$��fd��3�����2���0��/\���7'�����Ej�Z&�Y`�MKk�Jr�[( Z�O��(��)��Fa�B��3����p,&��G3�J�����̦9 ��Sa>���2���y�`�����d'�����cl��4�?��pP�J���@�;@#~Y�C�H�“F1��n5l�8d&��!)�T���QZI����1{%��LF��0p�����G����Xڜe���h2i?���ev*�2��Ĉ�����X��`4kڿ��.�GC���1�昋�2��V�f��Rd3��h6�Q6�u����
IM���YZɛ���0��J�7AD1�'�4���`SE��P���YZ���B�3oi�4-5
lR`����%��`�Xڜc�|GzȀ
�i���r�L����i�0�!XEZ�kh^�~$䘌�J�bB�Hc��'�dό{4�"�zZ*3�C�����}/�zghg�(h
�C$�~�U%U`�e�>�[�5"��G�9 �[�m�<_�T����#3B˦;�z^�AW�O�@+|"���B��%��#Cuԑ�M6�\��#0l6���m,V��%�vR�Oء����d9���=�SV�a=>vr�蜩�/m�ٌ$t�f�����G|4=�0lZ.��2!�[��jT�Y�i[�D�z���l�Uǡ"��|�)��]/��=^�G7\g)��shd�jA$�N����
��N3�[5e!D%�'b3aap�`C����`)���6�������L�ݨz��)o1�*1�������{��쨟?K,��ldl)�pSè���H�aqⓓ���[g
h��d4/i?�LK�f���qɀ�X���8"7!s�Af�H:[|?�4��,&����`'H��T�,m�����NA�]���ď��fl?.͟��	N
��O�@���J�m�'y��n�iBtN��d@�g�uǼ` �v�'�r=(����˙�A��c��Y�؏u,��6�~?}�y����GS��DvOe&{��3tѝ{@�?�'M����+#����БW��?$?�if7��^����͡H8`&tZΙ�>/r��9�)��)�n7� �%�h}�r�_U�>'���������ߠ'AK�⯑��`���7U�RP$%2�c��l/��w,�������z)�"��D?>M�EЖ�&� ��&Q4�Yf��l�al�#c���M��s�.*��L@FL�[߹�7Ȉ��1pG�+�4�b��q�2���OLǷi�[�"y�l��`	����_}�*�rQ=,kH��^����b}��&�N��^D��/����1�+[����=���"w�DEAR%��vv5�e�t,4Ef����<_����؅��C�y|e[=|���,
�<� ��=�oV�cH�@�o��'������i�����3kI�A��+�ؓf��ݮ ���������� o2}�uEXk)Hq�L�p2
�'��X�|��f�
���!�HaB2�-�pN��`?mB�V��4�m�d����@3Tl̏���3}��S9��"a�;�k�VdT�l0[f䬗��?���[��D�@��\�v��
w����Ɛ��D~��Qz�R��]��)�l�…����nk�?^��yn"���08�6�ubt&��GAl�4DGN������1�bl:�Ս���X����ϓ�ts�!h�c��8woh"�y]k	�6�Yrd��M�c�T�TEC�V��>�l��h�w�n���͋٢�@W+E���6� ��S]�,�dd������̫
�~Q��-���O%at"�gҬ/��0ЈͿ~�Qu8f����+l�@|�'������_�4d��?FN	clj���!É��|t:6ϒUMoi��}�U�r�`*��c�.x������!}h�<~�	4�9�6c�ՔشR���8��y>TDr?ر�G��L����Km&��F�:"$����L��b�u~�Meͪ�_+w��FÊʁ#LO�uNښ�x����A&2�F�'�!3���ot2������S65��]]p�oS��;v�v5��q˂
I��|_�9r~��<��6��l��/��>y��� ��?�A�S��L�AV���`K��;6
㓞�F̠�2vS��>�����Xe,���p`�.8|`_���������K�x6�9��L簚��䬖cG�d&���t@Vd�sD�Q���K�
��b1�T��눩������^5�	#2�\<�d@3�XKI�mƁ��|�o�����y�l~�f0�f�j�`��x�~.�6���x�:�n��o3���5� ��_k5�c�0>dt{�)xqǮ��Em��&5�!��M��6K!�DtLI]��b=WVS
��1���ĀKb�%�A�
\E<f����"a4!4��b�)����l3���|���6�t=��Ϡ�\����`^]�2sѼ��@��#����0�M��Jcu�G�o����]���v1WMܮL�:
XS]����6f�d8,���E��h6�AƁ�|����	蘏���22��u��#1���CCx�?��0஺�2��K�ME�@����%�:$"���s��Šc*�a��gh�v�oo�����dd�&[T�S�"
��ە ��/[P�)�6-`�a��{���a8�;�z��hZ.�
޺f��hN��t&	�<�<�S��{Ue��5��lU����6���6ք�}^6O����5�x�g3i��ʆ�2/�0�A6����8�
"���`�B�v�4����:�[��h�#{�d �4���� �c���,��iL-y{�I�j�]����7�_������{�&Sr�-������	�^����Wv����
��!KZk��"˜|�Gb�"`u��JX!Uv�]S.*$��ytd>v�F�K���$^����a�2Ɩ�N��D:�,��3"0a�`�G�t
�_ODYȎ�`U;��
�P���������<Ȃ��~��_����Zj5�i�}"��PaI|���~�����p�ql��T��<����}�\������8t��1������X|fT1`�>��|{n�1��2�/86ج�
�q-����k�L(��K:�V�%!�n<�mV��k���R�FI�U,� [�V|g�.l�g T�`���4������zߨa}���m��ȱcp���_�BZ4;RݶsyV�&	�B�ݜ����v�}&�jI�4V�K����b�������`�5²�
F�&p�f��h�~�4��N��"��̾g�w�
Q���D��|���
���&�8b�D�I��F�YIt1SPrLE�1~
*�d4�O0Z��3mcZc_����Ã�W�����.�Wy���R��]�Ɉ�������8_,�����>~=͔�
52`W���I�"��'쇍N�`l"��w���rI3\y�b��P�Pٛ�A¥�Q��������CW�ۧ����`n�0k����h`�u'�Nb�ȎL�a��B0���4bd�t���ho��;��yb���5K���h$�l}^)���J��'�.��IhwU�^g�Etn��9LG�����T�(���	~��w\��k���z�|�틱`���a3'�%g
"��}$��|�_����	2��}6�}�/M`���d6
i=A-!-��4 T������Z 
�X�jYg�4I�0�zdGƍ"+G�aߙ���Y�IC�Uz��<�ht�y�~!#�X��Q��y��m~S54�W��]G�̀Z�l��u��P[�U<�J}d1Ow~
 ���dE��Ws�&k��"T]�&��/�Lm�c��[40A)�p���%��-����DA��t��H|�V�R� ܵ�(Lpy�,��I,L�P�s�ƙ.i$4���b6بn��Z�1۾����>x��S��L[b&^p~+��d��i�IGmV����4�2bAFgJ; ��`4�-���(�c1�"�:/�PɋM����~s�)d��3Uc&Td�YY���O�!n`?�-QM�(�UV@d^�vP��TF�3#��5��huQ���Jc�� 4NX�̿q^{�a�x��H$[꠲2�,5�b���~z��X,f�6K�A|f4�A<P��-v2X� �{�*���=ҿ���Rd�y"2Z�N%~�����]ܥ*2e��)�]Q�5�/�={�>dh\�
<���2I��"�52ְ�P!e6j��m^:�� �[:�dr�
s<�L��s�C��N�k����ҥ��3�Dg�0ѐTͦ߮��|3��F�L�2�)�b-xWp�LFp���M�L���
�[8V�Ո 3�k���&��ƩAKOP�N�Tk`��S)���.w���7f!���bm����Z:�x�O�I��2���߸B�;����4���\�4K��Nm��C=�ńV�2g4ۺ�@�ن!��w�
��Q�@^���ᰘ�LB�z��RO�@l��	�"H<a�J>	��7��r[�ެ�:��9Mn�9��;q�4^Ҕ�S˃��`4mV�>��_i�u�q���m��l�߇{ �uOQ�D��dc�w�_�\��E2x"�VV�P/��As��S@)��h�.��.�<���:O�Rv�e��� Y�r�q�l�
��*X	����G��5��[�J�;3�`<����}�o�)��&�';p��l1��8>�ʇ���1��@�Y�a���5���!��VQ�ieB��M��g�=�sq�[��F^Q��/+ϔFV4�߫��@M=�4Q-�:4י��s_����[�b��E=8<�#�%c2N7���4�
�8qNOx��_����Y�;֗�u.���w�b����7�Rc�H��<h�|�#��1I���w�F3�D�KDHD�} �fe1U�f�g�{Ok�cr���c^O$��'�'��*��ZUU=*�Keru'*cAN���%��tD��}�F��tj����4�$d�I�V���u1���έS�$`"��<9���
�LJ�P�^��z�#�*6#&�_��f&*&��$l;���֥оx��Y�H��!3�w����A%
��j62��m6����Ly$�w�SoqL���e$eKd3�����Ȅ�rlVb&���׌&��C#>��k��ڑ�i�_C�_QK�N�
ttV1�ϛ�4�r6��kl����� \�嗪'�4�c&��M�M�i2Έ�H����	I�vv��a��A���`����	=�d�V�}�ԣ#>@a�
���`�O.����`�
�$�X1�S�b9;"�h:&x>�oyh
@��܈����;0�>\
��5���F�����|}]m��l��?��[�������S p#vP7��*l�m>"�tԙ��	`t�h�1Y[P�R������
H��)#���|��
�@�7��)��������3�/^Ҿ
Z,�>w�L
ƓY{rp�U�		&�Pt�o*�u��96�t���Έ"�1�<3����DN� 
I��r�&_�!�Ҋ�����}.�����Tƒi�;��d�	9�Mȓpl$�:�^M��e�aB��)�7�ԡ�6��ќ0%�
6��'4:�Ѿ��Q�ʫ7�DQ� _�ItL�`�����ȉ&V:�MG�l�X{6u���:�e(�l�X�&���N�v|���ظt�&���y�qPlN|5"�TW�(_�ɮv�/6�5#TNB%5�uNҧ�Q`EK����~=��`�@�
QZ����SQ����,ۯ�z��	!zd~Nm�^-��v����=��`�/��"���;�����9�b���Լi�tq�TE$ԻgMD]`0nmM�l�1��S3�5G�h*�ݿ22�#����ń�B�B*�q'�ؘ�ђ���Q�;��y�y)����Lgy�CH���(���5�u��y_�LGV��@-H�
���0B<�|�h�q��y~�n��M�	Jp�Z�OlB��$�N�^U	������݂�O�?)��XQ��
�r�4�j��C��($Tx�P���DTin�?��G�Td���	!��
L�b�
�WM�GTaL`&އMF��=5���me��`ۆ�֙E���h]�P�~�d}����&<���~��0�e@��Б��9��;א��3	[z�S��+���9⢧�dY��R4��8*|�i�g�"�h	���ٸ��eM!��
�")5��l�K��x��}�u�۵�u��1m�����#�sU�l�z��>�|7nᚍ&I�n!�5.A�҉e�?	�d��>O���9c>�z�x�jI�j��>trbҗ�O��{���G��I]U��f��(���jN	�5�`_F�s��-js@H���X�
#n�r��1�g7�asrQ�L@�YݎY��ZɈ"%��T��M�n�����	hz�iaU<+%��g�댙�Ij����j�L@�Y�i�@E��F�l�,'��b�3��hB�'�������$#��Iz�	�^����//B�>�x��!)l�beq�ݐ�}6T
�;�3�������8Ӌ�Ԡ�L���9����(�"Ct���:"/�Q����?'��l��_���~��X
)"Y<Y�gZ�7M��>�=��t�P	m=��˛���/�D���;����+����SW�D�V�r9f��ӵq�5�籅KJ@��K�54�\F�&nI¹���2�8_�=�Ƈi�z��"*�,�|R_t�Ux�l8��wo*V+	F�f�bS�w�hvQ5c{�W``P<w��U!��;X9#^�Sq�4S�E��eA���PPG֭��ΤS��Q�;�	+�")����I׮
��P!U��/�;��r���j�b:lv~S+��@#췏>)}~ACX�[:D���t�m�24t.eR:f����G� ���s�#@u�9��BV6vA�:��f�m"�ݓ>��d>��3Bޞ>��mT�	��j�y���'7TD6���y|��#>r�t��r6��j�&��.�v;ZDGq8��3�N��Y�3)24�t>A����Υ�������,B�EoJA><<�?�8H�(�S24���3%�j��yb�zR��4ĸh~-�|c#�������m
!gі[��ONzB� ��ƚ|��t�1π�J��XL��XK�$5�[�!e�x��q���+��T
0��g��K����f5�ݸ�\����d���/�,g��J
B!��>��"�����f�n/b#K9�#��9��#*�Q����_&�cH��GR3y,X��r}�_�� �̫6����T���ھ�#��qWk��Fʓ��˻���'4kU���/�H
T�T�LtF@a@JGX�����F��oh�q �+�IE?j#*���g(��2��9IcV����Y�=�}�A:��(E��F��C��`��Ҙk*O�F���Oz�>
.�C�OƱ�ϼ_�VC+�:rQ�:D����I�ϗ��!V�����(���c;6�v�
��7�rr��`�ō�J��5T�H��È�$g��:s_��yv?]�@\�v|<�DE>"GT���(�oF-L�و@�hҬ��Z���Pvo�Ju���/^9��t%"a4�1>>	/��
"��=�<�(�����:���g�F���55�T �N�%�˦�nS���M8��G^Q�A�K�rV�
�	�af���kx��@�-QdV�v�ϟ��7��k�6}в�,���i���{�K?���#.�
���hԹz�S��3��F܈��_Vೱuݟ�(��@�f������p�$�](T>*7bӱ��P�dXmV������r��k�+�w�<����]A�ʨ3�t����V����:'��*p!n�AD��Kߨ���=�f�� ���;��7��g��w�^	��6zn텞��^��*Y�V�"�8%橑ֻ��>N�6]*��.)*l��NE����L\�"4q���(|
L��_S�H�ó�DE�]��f~X�4�+K簴���eu`�q�L�*��覚Ó^�~0�A��&7�bfS��y.(86�i��kN4��Z��5�W�հݺ��Ff{i�ɻR��L"��)4|����?F"��c.�(��)�xI��B�|����^�٨�Ϩ�����h��q[�{V�|ɉL��X�Pe�u��O~��vJD�A�@�#�5�Zɷ�5�vVvȵ;�T`3�M��5�0aUyX�d҅xF�>�
d�52z?�2��l�����U!&*�3��c�1ܓM$�r�Y�	���u�{�����m�D�UAw��P��V��Sl�Ie��G&�4�����F�J�����A��<�_��e<�Ȑ,-��x�S�	�^A���k*�nR�{�Ԡh��������T���(а�x��㡠�y� {����DT7�c���mJ�m�1�r�I�s(�P��<5e�{�	&kR�V�BbqU`��|����Li2"OH)������S�2�Əs��|b>�����j�R ��,@zu�	��]�R�A,���C��JD�b�3
l6#Q��ɠQL���3I�d��E–]��2V	� S�1
}d~�z��-x�[���f2y�2ʤ~�e�͌�pba��p��uJ@H��l[�����Y�%�v.if��E0����h6F�+���9S��-S�9$x`|W[�)d���b����x����i2R]2��f~��/���"7Y_A�dM-=z��&���9�ySt`'�L2�F�Oku�-kF�j�of��f(�݇�H?$ҁ.3��
������s9�S�E��6��#NQ�:�t�DL�S
��5!7sQ0,S@�ìkp~�4i�"�����{�Z^G�͗����&�s�i�TΊ@ �àa��c+� �z���&�Zl��Ns밞���[~�
,oH�#���HO�C
��#���B �}����b7�hSQCf�b�=���LG
��9J!�3�C�ω��7�G~�խ�e3_>�Lҗ��)�8%�d�	�[���MdS��!�!��\0Re�/��P0Tڋf:�"G'�v�5u�0��#pp`����Ԑ��iБmUs,���-�!`���G��U\8��r�ь�8x�vi�-���29�Z��mxC2�H�@�^�e2*A&\L�Kɹ��*a�c�ڽb;j����OA��Ȧ'�����s�̠�p�/�[B�J̆A��k��Zau1��Ʊ��ז�U‚�8,�K[<�>=#po�G���b�u��3̾�h�*��G�7��AM�L<M\��J����z��3��"������ÿ��\¬��ƅ��
hL�)U�F��*Y��1�#ҥۅ6W oa�eN��	뛖В+���O�$0+!Q�
���v�|�Ji:~S��
����g��ב��9��0�΃���`d���Qd�+_ +Z�c0��h֣��h���cեS�e�T��g	7E"����Ӝ���<A��t�L%������,@H�\��F����O�
=��� L��`�@�
j@-�ρ��&��{�N��ŷ��ۣ=���*]�
�N]E�X���n�o��I�LK��k���K�_S���'3�"?|
�������<�6J@�W� f��<B��!�~ۙU��ۙII��:C&c�/��
����nl`�P�_�����昄H��to���z�
#��ws�h~r�� ��A�^&�� .R���M݁�H��\H���PU��g���#��t���]S�ҌC����4U�6�T���f�/nf�cJ��cF=:%��Os��4�Dq楛:��2`@$,e1�����62�YLF���y�;/�Y�c/9�[RP�S]d~4B�>oF慞�3�l��@¤b�Y?��iHD2��\-9v�n̐,��3R�t��Ё<UIwR-�ye\�>ԋ��
2�qr\��7�I�ʺ'2EXb=�b����lD���1�1�s��L���!|��N���4�r}`F@EF$�56"瓍D��E��[��a7?��LFf�@c ��
�����LU���0g��@�&��fH���[�u+�#�㍤�Xˑ���>k�8ˆ�A㭋����[
����^���޻Z@d0�LPr��'���b-ۓ_���f-:ӁUj��@dz]�(C��H��/7y�S�,�e2����MF�wM��#z�Zӟ��b���A�D}����Ii�'`��u[��]��l����^ym�@$��[��tf����;��~�F�!L�"���}��s]���|h��*	�\�CW�C�.�Y���R�R��8"��F8naXHZ"IMJ�i�C.$5)�����M6Ȅ�Ύ���6��,Pe� b��{D��q1�*�1~��	mr��ʜdؐ�
t��i>Z�JMG�qc�|A�X+SMr�D��d�-N星p+�X6���&�i1��I��lT��SQQB��Z
��*c�2�����b���	X�˴��g8���Y���9��on4��rR����5ϕ3�(�r���`�����”�	�b�X���1%l����||e/�; ��Ha��E�dE��e��|�$
���`9�⧢pa(&��
�E������1
L���Ip�Ӝ&Z�H��p�HT�TО�ʏɐ`n��2�\M׵H7S�]Ud�V	�s�Z�� �+X������9�1p�܎7�G��-��9*&_0�"�Ih�E;�A��$tgL�D">�]R{�V�u�y�e�i"���
q�7�
��N	9�i�L��:N���Ʊc*J$�t
�o> s3�|����ة0�n.~U��db�H�:�H8׬���J�
���3V� ���
�q,�#:	�-��wQ��c-Z�巙9kH�nRs�d����B�4�U4O��g=��H��\�FY4���y����LG$����l��V�p�87ÜNbuYǔ��B�Xo��MU�J6�}��&V6�Ip�xsL�4p�?&�
|S[Hs��e>�7��(c��op1��	�H�)���L|-����/A����9�}5�8)�H=?t�Gg-��X����`��-�8&!S�n�O��Ez�\c�844��y��DQo?�ua��c*�S���z�jaZ�2"_��l&\��;�!���(���JA��/���ށw-m1���s��ʝ���4�)�6�*����o�D`�l�	��_W����)b{�!�}�w��0$�Y�TU5I~�tR��q��A�X��fK���4�j�\$}�/��|�+J*x\WC��Œ�~��N�b0�r���U�D���*�1�&�wU,'ʇ�4}U���LGț��LDw�f1��R�WU�R&�#�1�Q�8-�1�/)&3�w�׈\7İB��'4��s��"9�T`�7l*���?�w�����?��h*�Q�8MY���������0A�w	�	2W�̍��@y��ȇ�/�4��X�j-�I��u4w!O��	�cr���Ɍ�i;Q!�.�~�?�iJ�������#��V2�������C ���
���q�E���---�F��_~ٸݱc�8q^��2�=鹶&�>��dRл���/s%{e0�Zr��f��N�o�Q�r���D+� KaW�H\�h[�'�php̈�'@�usR#�f��\�4�!3%-vc�I��s�
|"�ДAe�k�f��k�-�����⋙[÷ƀ{��'��8 2"�T1.�\T.J{$o��y�Zb��$�F�x,a3P�6�w0�+^c���MG�@
�M����/o���F��͎"i�SQ��Ip.��>�����yEdG�����zx���/0V!c�������F�v��O>����*L��A&�(Q�� H
�[[[��|'TWU�/߻w/<��0<<�T���!�.2���Ī!�4�%3	}����ZP4U/Ț�(,����Em`
�������9Y�\�
$f�RZ�d(����`��b�
���|F>�yp��{/���p�ࡪ�A�����ˤ��b��W\������+��~W���~t��ӈ�*ZJߌR��
��\(Ra.�"�
�m�������~��0�����&���>���g�5�4��-�i���U�M�4�iF�G���|P2yjjl���ܢ<��5�3K��X��r\��39FFF�G?�܃�Z�E��}!s��t�J�qehٞ�n�Y	0~��}������nVQӂ��9pE�
pM�@��������ͤ�1��}�.��^xG�<c�q���
l4X|�M	M���Si�5_�����f��җ�U��� ~���ॗ^��^�푢'ȃ���M2b"���.l&�;�>����а`T�p��*jX;(`퐵w
��O��Sc����j|�/��Q�^�WR�N�V|CA5�BS:��T�2�<�=����}�m�f�0�_�9>��e�=@���'>�	�Ū���~kV���ٰ6n�U0E�`8��%�`���t��ո�Z��$yc'9��N�nkT��%Z��
6��j���aJ�r��D�ɀG�J�a/~�K��
7��4�����K.����u�6o���(�I">\��E��u����g���S���O���\�	���&���[�m�t��1��\�=�G;1:a\��'r&�a[I_�g$s��g�YE
�;����c4��?�s~I�����o|��d�2kTUU�w��غu+܊'���������>�b7�s��l��4|m�F��H�:���1�9�hmvCj;O�N�Ť�����fڌtra�Wv��jv�
�MF^�WD�؝a��@���^Q!.��k��y�5o�/��Pꣳ�.��R�x���Ca"�|2�!�:i��?��!z{|�}�kRF��(�k8�H9k��=��G'?4�������,b�1:6{_�#�1�����0򢆗H"���j�D&;j2}1�� ��˿ȗDJp���3��]�vA*��
2�2&���b�8�ۿ�+,]�tZ�O,��~z{{��a-���6���ມ��}xKN�6��>����B.r�ɺ	�����<��i�tG&P�l�n1�s�\C�`x>*�6�*cZZ����\/��D��T�0DG����`&#��\��G�����;�7Ȑ�Y�a�;��a����l�]��ZVp�l�c��q��3�
۾t����XW,��1�����r�Q�@6P�W
�@�M0h5���I���
�x# ���*w�y�Ŀ�x^tۈ_F�F͵��ុ�?��?�"�	��k��g�b�����i1e�I*y������v��Ɵ�‰�.lN
/=k�R*���pR�[7j���L��$�\�J��_��w���0d�a�۷������d2��<�|��-�����?��w�MB�UU�
�T�����e4z�d�n��K�w\��y+a�l��
�u��f
x��	T�F�yxk�q�Ƴ5��+!�c��<΋�\�
���\6�0�- #�|���{/�f7�va2~y�����e1�X�&����%�h��w�����w]�ї����WmRP��GaXrr�
;A
���}��U@�G������l���hDP!f���6�~����}71u>�`�[;dBH]����]��OB�1!���k��R�]^��}*�Ay���l$�w˯~�=�<���6{]~���ށ���5�6��>��t�����/%1���+�Ke�b.V��@L�8Dш�������8��eNY����-p.���c�V�c���L�q��I/[g�Yi���m�;ұ��l(���N�� �N�2(��-�L�+*�zN��ku�ŗ@y���En	�ID��	�|�H��V.��Օ00xR�$�6n��m
����eQ�Op��-�����_C��2�Ji9r�*�[?�D��"��<��1�
�1غ؞�9m����0�淴� 5K����`�a
b�����/Dk����KUU�<�Kl5��-�1�h��l�LW`���F��32��&�INj���y��틖I���-�� �Ev�E|�Y��B�+��O?�4Uk��9�m6���A�I=<w�O`���m�g!,�E"/p�zbE��6^}�U��w��v<����S2Ƕk�n��>22���XI0Zh�a�����5��6f�9�l�
MP������|��>�,�25M,��b��A�t�O~��QX��{׭��o��Z[g�8w����g�%�� ��(�R3-V3�,Oa_�/�e���E�Qnj��QQ�[�8�������0������wb�AFG����p�7B�,�'>�����t���*�̱ɠ��!")(�d�^b�\��VK�HX�n!�t��y�#��|�:x`�f�L����1<2��7�_��)��x𡇬�`�,��sh$c�rL�:~���r��l>&U?-�B�=*\@W����_|��m���l�f��%�X�m6���K�}�c�q�=���0<䔝+%�����`��Lr��t3����"�!-�Յ�.5��]�;��cmǏ?+@F���?�#��ۜ�;�O�d�����I�ӏ|�p�ر;����Y 5��JF�l��Y��jɬϟ�ږ�T4�|��l4x<�!�햼I%��*�k��'ׯ��`S1W�	-���}�9�Ab�ǽ��
�'b�n&�C��H�~e�T�.j
���h6������h�����
Z扊��E�kb�0��XS�z����B�V���h&��7�	7�t<����@�i͛���Ɂ�.�F���G>2�`#lFj:��������i����MX�E6b1Z��-ЭY%��֪�9d.i����@���Y3�'?��h&JTE$��">HBd�C������f5d/�5���f54�Wө-�D�(���\�;>�S�aS�	�9Ґ������W��;f�>e���v�`�/F�2������)m�
��?�!��p��]�͎=bVQBE�h�fK�FI���|t�!|�~m�<����9�PH�?�˰��'ū�>!}P�R��E},H{��$}�{�=%[�����l޼���^zb��W\n�3�{��c�za�`������8O��pQ,��!���b|;h~��j�W�ج}��*�9AX���
�\e˗���wnRkt��ڈQg4MަU,��dxI$Dy��~_��JdD�'���F�T$6���E������ˉ$��]]p�wp�Wł�Dl��?�ۚ5S�n���fd�ш�X����,V�~�|xSc-�\�;^َ��$�6
�l���<K˃ټ��o�n|�{J�# ;�����&S)�M����¦4��o�TU]
����)��������@w��V�C�|)[�h6�lK
h��'7��'aYu�h�K�6�7����0ñkn���?Z���H����(�F*@��x�猨�822,o�̱�;�����,�BaӦ�&��q��w�	}�����$l��+�������[���g>�H&'����8ī��"BV6.8o����e�9@[�o�%u��A�b~E�[82@v�5��T�0�"��_��/���9��`d�����k1�+����QlJ�}u��]��\���� !5H:���[
�j� ��MMBe�T��m3۩���Md9�o����O�|���
�~�|#�?� �N�}tI� �'���,�ъuB���Z�?�gk�ƈv"D<��cF4G��"���5���7���NE?�Gy���
l��팠�z�*x׻��ፀ�ho��
d,��}��q|���WSd���"���m��j���>����
a��
��7ɉ  ��k�f��ПL�K����0Q!�j!l�8"�Z ��+�<�4V3'СC��+v2�2��bV�"2����I��<�%�8�4���;�v�$����?w�}��w���r�O%�x�1xb�p�/�&XM��5�{�ٻ�x�
%��e�$6#7o�b,8_z�%F�|�xꩧ�f�n7�����D���HIO�f�ߞ�����m`~�rXѐ�����ӝG��I����R���t�ؘ��\f�mpʾ��>���"i��c�����`�ʕF��|��Do�2o���0�4��}��R�O ��o|������l���}�����-|�~��		a]��.\����.����!�'@c�P�>
<!Һ|-��²S��ږsh/㛎+���c�E��`/�f�I��W�o�e�$<����${�	�*�WSS�.ʓ�$���D�&�~�z��HIY��BP�9�Ŀ%�0��e��_	�b6�׽��������ʐ���3�>�AT8�:�T ��iV�i���y/�d�~��ī�y�E��d���B%��& #����}�k�6Õ��;���SC�g�@<
� �/F���F����d�"(>MEM�dϏ))c)��%f�EIY� ���b/�Y����Y����b�
pE\kn�s�<�����*�ɅD�8lB`��>�l7��d�WU���>
Cg�@M�"�̑u#�z�9㣙����0��7-�%u��R�`.m����8��;C�4�j�b���Ρ�d`@ˎϘ��dDn���^�If">Y+#"Yh&��G�t9�ٓ��P�1��_��R�h C~�s?9>�D�‚��޸�<��ڌD�k%4b��[ސ��^��������Cǡ�<�N� ��:�-�+���h̄E>|=�$��0���0`��%�e� *��c�ŭs��2Ħ���%�eɥ��k3Q�18� ��`��^��ca��%�vY[,����C���Ը�B�h(���h�zőr�!���嗽D�0J�
�j3Q�b��=h�,'�L(�\�u����L���С�l�A�׺���gpAVc�ɵ7�ׂM��>0�*oY�O?��YIy�4�w��@���E�}}�R�B�%�υ����bB�(\��(N�1��4��b7I$�XD��2�mZU��L��@�d�fֹ�� �Y�"�m�.o����T$���z��_p��6 O�q�{�ܘ|�.bR\9�>J��1��Ŀ���4P��5�T�1[Yd��f#9�9����T����@6���*�Ͽ��7�u@�lv�e2޴�*���8 �mO?-���Sj@���,�ѰЫկ5���F��͗�q��$L�2P"&�8�����aXLR�19R�+���E��(�o��<�tǺ�f,g�i�o�W�����Qo~�!��O
2�3��^d0$O"U1��税�XHHFU�������(��j��b�p�<X�$泟�������l�(g���O��!����9,{��]g�:��f�I��nZ�Z�,6{���0ij �L[�}r�G���
��Q^�C��R�?Uu�6��E�O�_oDy�u���4C���oqʤR�w;���UEk�����5"<�X�|���,�J��em�YT�F}'�C���q!Q�������[紼O
�ou��������;� ���l�QN��7��8gh
/F
��L0�?��O�8C���w�;�X�����B�X98EQ�6v��k��7c���=7�3�O�K�)�d�X"�y]C��-�J%�w�b�L���d�m���
2��(����(�Q'+6Ǽ��
@�� �#�Y���ts�\��|_����۶�����V�/�T�	2?�2��iì���|��#F�xd$V�駟�DyH�(r��kE��!��'���>7�fN��f��zK�6�q�s��ʼ~��Q>O#��-d#�?D�>��;߁���W�w���o�>�,�+�]3���hs����YW�]�c��.PIg����J`��0��f�L�'��gғ12x*�����V��lm�f�( R�Mkڦ�ď�l6b�I��um�p�
*W0J|������
jE����'�ٳ�H�n�z�Ő��`-����Ʒ��m�6�\���"��+����<d��a1�Ɛ�U}�Khd_#r>@6�cu�r>�f�u��R6#&#�ח�l�$�t|�*i��P� �$�����ٟ��� �L��ٔ��rU����d�9��-BB�._+{�D�w����4�3��&
�>uI;��T�m2(��ŝ;�&cf�[r��� �ݚ�c�"%Q~�x�L�x2Й�n�O�3�����*#�r�v|����{���SQ����3�~��$�|N�1�Y&QD�Hn�,TR�`�@�	����
�fF�v�Z�ުX�Ό�`����B�FR��4�|��T2�S��gᚷ\����n��0�21���*:��h%��;�`�*ɦ��k01z�Ƞ( �-�lfZ�մ��fаQ�s��WCCE~`�����0dF��~#��kN����D�̈́�$�=hkm�뮻�ok׮-��!2�C��
���ڽ����X�ݏ���j���*V���i8ӳ3MZq�PQA�b:v���D��ь���7�Hg�zc@�2�YH��f��}����!m�q�|Ѝ����6���s�����Ȥ&G�H���>Ehٻg�n[$!�uo��򻜋L=�/f�'%j��AeM��UT5Aۊ����dDinU*�닕�=݌F�"���=Mj��������à�����*����P��w�����
	o�Fs����tag}�yΕ�x?K4����^8���3P���M�k��NZ�nc1#D�UI�z8��z�=}}FG-/�UMU�yg��x�@_�h2&� =�K�H���u�M]�G�	g��3"Ep	k_,�Y_&��b��+Rfi.�mU�b��p0�Ϭ�ପo�trR�|�)���P?o��)�K���[Ӯ:�6b2�ڍ�*#6'�B.sZ,-?Wi�%s}�-(������UJ1.���Kʴ>K���a*�Λ��`�L	�ԴjkaddPa�!Q�b䓍��I�����-��ln����;�hN-��&�}��o�fE�.5�zf̆��y����g�|�L,�#/�q������cVi�b+�B�n,�f��p��E�V`;y��zV�n�mF'	���_�`�e�:"d��tt��k?`�c2fR��@�ЈVd�,��`��P�H,Ez�`�	~�$'I�m���?�dRK����Ri̙�� W��e�1h��.Y锃����eK/��Wv#>)�S��.�3Z�쩮�ܟQ�y��6_��޾]j2N��ou�4�|�q�&����j�B8��p�
B�\
=3A��>�,��!H��!*m~�函��0���z�a&���|���'M0��b��a���:TG�
�E��n��ZHԴ@rr$/v#�G��v��n���?�5Cd`[�\
u9�t��ɘ9�m$?�CӼ���
E�!Z�A�"��ȵ��qP��������|���r�f"�b�0"���Ũ�M$�3K��r��ʔ��z�&`G���LMM�cQ_�FR\�a���EP״D��j5�zf��/���5+`aM6�}^M�����0�yk�^��������Yכ��i|?-�P���l^ჽT�!�2��#�Ε��Fn���ɔ̇�y�I�eut�j`8��j`�����Ғ�&�H�&��g��A�R�۔�e�^rnV�:�`���~�	���ӯ�NU��$l��+�i*!^������[z��ز�Q����p.U��k��|�L
�<�z�VB�+V���^� ����l��n0Ag��-�N@GC4G�؎?��H���ރF}k��;To)Z�~��[��!2.^���@r�W���*��Y�8�C�n��T�w��$��NcF2.*��M�PfJMM�yX�{ū�&b8���mlKsB�r�uP3�;_z�7��(��Lj�D�ofb��D�lh2�%���!�5��t���P>"��d�Ő�I�a^�}GJg������ ���ڎH
+�8"7
����P�,/�X�1����4\�y!e�e�Q}�І�{
���&Ȉ�����%�M��o?\�G��z��I)�`�?(Q�kf*�#W���hp;L��n���d��o�p9�2i���i���*sP�ZJ�C���&�BӢK���E�_������zޑ�[�q^;��:~��[5i���?$~�<"�e8�+���ݸu�+�y��M����I�e�x�e�LrX��!���_S7�<���%m5M��7f���H �>��e��b���~Ĭt�,)����"�Ǔ�mR
6u ��K;υ�P�A�Bn���Wj��-ݺ���I���FOO.w�C4/�kZ�^�&�s�e��l̓��'�����Ig6�ҵ>�m�Z��|�
_��ɞ*��OC�����j�2��"���EGϴ��@fr�(�e|���t+\;���52�m�w���n�	����7S�w`hC��؆��3���RAm6AV�@��LjO��'G�e)�[mhAH4��p�5��L��󑰉sy�;΃e�,��ƫp�T����y�;���p^�����|�.w��'�k��sQ����%zl����mVAV�@����}�y�髙7!�U�[eD��)�"��7��M�M+��Q����='O���L-cGat�(�A�Oҝ�HKڦ�7ͺ�0Ź���g˼�G��O1��	�T����u.��{1Av�0|��.�@-��w�*MS�������C�HT�r�-[e<��?m��uK@�6��:�Q�wɁ�����|����0�u�T�G�#W��LSN�RXe�.���үd�35�C,���(�+����1}���eP���AF�F�������N�™�;p��D�\ÂՐ��_t�?�G`�\���a�A��T7-7ي-��T@M�/���i01���6�QWaƛ��kV�K����l�Ε��݉E,�
��j�2\�r��`#��eFJGE��Š-`L�HE��8�t���!`��’���
���$u�y�{��5��`.��,��y��5$u�q�e���C�@|��i��s�6\X��h$
��*)�)�S(�%���me��#@�~t�@�_
���z`�S#,s�w"	���hJ�jD��F��a��$��ٱʺ2���ƀ�)��$ N��3��2�
C6�
4d@�>�?p�MCu�̲V�l��D�Uy�,(g;�D?�N���V�JM�B�E1!yW�2��e��`;LC0�b�L���
|��iy*����!��F���[t�R��H�:ɤ �I�,F@+Ty$R$,��@��YxQ�{!j��oUߡ<�@������N��>5�1n�O#Lf����%��GK��o�-h�k�K��uQ�ъ0Bg�2��%vX���^E,�7��k>4+��k��5�0�����PgˌV�D
Ue�H\.�f.وī�BHhszL� BK�H�wy����}�l,m��QC�4ܾ� %�K�!������(�?��N��R�Y0E�D�ܾ��:�Q��k��z	���G�R�Y�j�"��(�:<���<�p����C�e���l��F��7�ǻ�������$�HD�S<�r1���˕�6WG�:-S��MfJz	"�0�Ϻ��C�9�P���
؈@�N���<pTiJ6��Gylb�@����/U���!ڔ<08
w�9�4%e���zZ>�,���v���D(�&Klϟ�u;���D��OSD����2��:�YuQH"x�詜)�u��S!>Q-Is����n��e��_�8#T>�eJ�Z5!�)I�߽t,���cߊ��F�C�Z�9h����{|�2Њ4��)(��
]7��?܅ͨg���&c�����c(���eӱ<�<�mZ��������'=�s~�j7TT���!t��2�Ή��`��W�w���"��$(�O��ğ&��P�ʦ�97��F�5wU����_lPu���V^�.������*y�jIEND�B`�Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654).

[![Latest Stable Version](https://poser.pugx.org/drush/drush/v/stable.png)](https://packagist.org/packages/drush/drush) [![Total Downloads](https://poser.pugx.org/drush/drush/downloads.png)](https://packagist.org/packages/drush/drush) [![Latest Unstable Version](https://poser.pugx.org/drush/drush/v/unstable.png)](https://packagist.org/packages/drush/drush) [![License](https://poser.pugx.org/drush/drush/license.png)](https://packagist.org/packages/drush/drush) [![Documentation Status](https://readthedocs.org/projects/drush/badge/?version=master)](https://readthedocs.org/projects/drush/?badge=master)

Resources
-----------
* [Installing (and Upgrading)](http://docs.drush.org/en/master/install/)
* [General Documentation](http://docs.drush.org)
* [API Documentation](http://api.drush.org)
* [Drush Commands](http://drushcommands.com)
* Subscribe [this atom feed](https://github.com/drush-ops/drush/releases.atom) to receive notification on new releases. Also, [Version eye](https://www.versioneye.com/).
* [Drush packages available via Composer](https://packagist.org/search/?type=drupal-drush)
* [A list of modules that include Drush integration](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654&solrsort=ds_project_latest_release+desc)
* Drush comes with a [full test suite](https://github.com/drush-ops/drush/blob/master/tests/README.md) powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush).

Support
-----------
* Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush).
* Report bugs and request features in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues).
* Use pull requests (PRs) to contribute to Drush.

FAQ
------

>  Q: What does "drush" stand for?<br>
>  A: The Drupal Shell.
>
>  Q: How do I pronounce Drush?<br>
>  A: Some people pronounce the *dru* with a long 'u' like Dr*u*pal. Fidelity points
>     go to them, but they are in the minority. Most pronounce Drush so that it
>     rhymes with hush, rush, flush, etc. This is the preferred pronunciation.
>
>  Q: Does Drush have unit tests?<br>
>  A: Drush has an excellent suite of unit tests. See 
> [tests/README.md](https://github.com/drush-ops/drush/blob/master/tests/README.md) for more information.


Credits
-----------

* Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7.
* Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5.
* Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from
  the folks listed at https://github.com/orgs/drush-ops/people.

![Drush Logo](drush_logo-black.png)
<?php

/**
 * This is the Drush "finder" script, which is one part of the
 * Drush dispatching chain.  This is the script that
 * should appear in your global $PATH, or, if using Composer
 * (as usually the case), will be found in your vendor/bin directory.
 *
 *
 * - Never copy this script to your site root. Copy examples/drush instead.
 *
 * - Never copy this script to a directory other than its install directory.
 * Symlink to it instead.
 *
 *
 * OVERVIEW OF DRUSH FINDER / WRAPPER / LAUNCHER SCRIPTS
 *
 * When the user types "drush", up to three scripts might be
 * involved in the initial launch:
 *
 *   "drush finder" -> "drush wrapper" -> "drush launcher".
 *
 * Brief description of each:
 *
 *  - Drush finder:   Finds the right Drush script and calls it.
 *  - Drush wrapper:  Contains user customizations to options.
 *  - Drush launcher: Excutes drush.php.
 *
 * A full explanation of each script follows.
 *
 *
 * DRUSH FINDER
 *
 * - The "drush" script on the user's global $PATH
 * - It's goal is to find the correct site-local Drush to run.
 *
 * The Drush finder will locate the correct site-local Drush to use
 * by examining:
 *
 *   a) The --root option
 *   b) The site set via `drush site set` in the current terminal
 *   c) The cwd
 *
 * If no site-local Drush is found, then the global Drush will be
 * used.  The Drush finder assumes that the global Drush is the
 * "Drush launcher" found in the same directory as the Drush finder itself.
 *
 * If a site-local Drush is found, then the Drush finder will call
 * either the "Drush wrapper", if it exists, or the "Drush launcher" if
 * there is no wrapper script.
 *
 *
 * DRUSH WRAPPER
 *
 * - The drush.wrapper script that the user optionally copies and edits.
 * - Its goal is to allow the user to add options when --local is in use
 *
 * The Drush "wrapper" is found at examples/drush.wrapper, and may optionally
 * be copied to __ROOT__ by the user.  It may be named either
 * "drush" or "drush.wrapper".  It will call the "Drush launcher"
 * for the same site that it is located in.  It adds the --local flag; the
 * user is encouraged to add other options to the "Drush wrapper", e.g. to set
 * the location where aliases and global commandfiles can be found.
 * The Drush "finder" script always calls the "Drush wrapper" if it exists;
 * however, if the user does not want to customize the early options of
 * the site-local Drush (site-alias locations, etc.), then the wrapper does not
 * need to be used.
 *
 *
 * DRUSH LAUNCHER
 *
 * - The "drush.launcher" script in vendor/bin
 * - The bash script formerly called "drush"
 *
 * The "Drush launcher" is the traditional script that identifies PHP and
 * sets up to call drush.php.  It is called by the "Drush wrapper", or
 * directly by the "Drush launcher" if there is no "Drush wrapper" in use.
 *
 *
 * LOCATIONS FOR THESE SCRIPTS
 *
 *   "Drush finder"   :  __ROOT__/vendor/bin/drush           (composer install)
 *                       __DRUSH__/drush                     (source)
 *
 *   "Drush wrapper"  :  __ROOT__/drush                      (copied by user)
 *                       __DRUSH__/examples/drush.wrapper    (source)
 *
 *   "Drush launcher" :  __ROOT__/vendor/bin/drush.launcher  (composer install)
 *                       __DRUSH__/drush.launcher            (source)
 *
 *
 * BACKEND CALL DISPATCHING
 *
 * Backend calls are typically set up to call the "drush" script in the $PATH,
 * or perhaps some might call __ROOT__/vendor/bin/drush directly, by way
 * of the "drush-script" element in a site alias.  In either event, this is
 * the "drush finder" script.
 *
 * The backend call will always set --root.  The "Drush finder" script
 * always favors the site-local Drush stored with the site indicated by the
 * --root option, if it exists.  If there is no site-local Drush, then the
 * "Drush finder" will behave as usual (i.e., it will end up calling the
 * "Drush launcher" located next to it).
 *
 * This should always get you the correct "Drush" for local and remote calls.
 * Note that it is also okay for aliases to specify a path directly to
 * drush.launcher, in instances where it is known that a recent version of
 * Drush is installed on the remote end.
 */

if (!function_exists("drush_startup")) {
  include __DIR__ . '/includes/startup.inc';
}
drush_startup($argv);
��
�
)
�������]�GBMB