From 24b6c23e62ba98ef29c39eb97f9f8fe8d205d345 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 17 Jul 2017 20:03:28 +0200 Subject: [PATCH] removing posts --- phpcs.phar | Bin 0 -> 1026122 bytes phpdcd.phar | 20310 ++++++++++++++++++++++++++++++++++++++++++++++++++ search.sh | 40 + 3 files changed, 20350 insertions(+) create mode 100644 phpcs.phar create mode 100644 phpdcd.phar create mode 100755 search.sh diff --git a/phpcs.phar b/phpcs.phar new file mode 100644 index 0000000000000000000000000000000000000000..323bada4fbf95bc77f9a96f365585a738fc279d9 GIT binary patch literal 1026122 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

=Cd<< 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 literal 0 HcmV?d00001 diff --git a/phpdcd.phar b/phpdcd.phar new file mode 100644 index 00000000..bfd16fb3 --- /dev/null +++ b/phpdcd.phar @@ -0,0 +1,20310 @@ +#!/usr/bin/env php + '/php-timer/Timer.php', + 'php_token' => '/php-token-stream/Token.php', + 'php_token_abstract' => '/php-token-stream/Token.php', + 'php_token_ampersand' => '/php-token-stream/Token.php', + 'php_token_and_equal' => '/php-token-stream/Token.php', + 'php_token_array' => '/php-token-stream/Token.php', + 'php_token_array_cast' => '/php-token-stream/Token.php', + 'php_token_as' => '/php-token-stream/Token.php', + 'php_token_at' => '/php-token-stream/Token.php', + 'php_token_backtick' => '/php-token-stream/Token.php', + 'php_token_bad_character' => '/php-token-stream/Token.php', + 'php_token_bool_cast' => '/php-token-stream/Token.php', + 'php_token_boolean_and' => '/php-token-stream/Token.php', + 'php_token_boolean_or' => '/php-token-stream/Token.php', + 'php_token_break' => '/php-token-stream/Token.php', + 'php_token_callable' => '/php-token-stream/Token.php', + 'php_token_caret' => '/php-token-stream/Token.php', + 'php_token_case' => '/php-token-stream/Token.php', + 'php_token_catch' => '/php-token-stream/Token.php', + 'php_token_character' => '/php-token-stream/Token.php', + 'php_token_class' => '/php-token-stream/Token.php', + 'php_token_class_c' => '/php-token-stream/Token.php', + 'php_token_class_name_constant' => '/php-token-stream/Token.php', + 'php_token_clone' => '/php-token-stream/Token.php', + 'php_token_close_bracket' => '/php-token-stream/Token.php', + 'php_token_close_curly' => '/php-token-stream/Token.php', + 'php_token_close_square' => '/php-token-stream/Token.php', + 'php_token_close_tag' => '/php-token-stream/Token.php', + 'php_token_colon' => '/php-token-stream/Token.php', + 'php_token_comma' => '/php-token-stream/Token.php', + 'php_token_comment' => '/php-token-stream/Token.php', + 'php_token_concat_equal' => '/php-token-stream/Token.php', + 'php_token_const' => '/php-token-stream/Token.php', + 'php_token_constant_encapsed_string' => '/php-token-stream/Token.php', + 'php_token_continue' => '/php-token-stream/Token.php', + 'php_token_curly_open' => '/php-token-stream/Token.php', + 'php_token_dec' => '/php-token-stream/Token.php', + 'php_token_declare' => '/php-token-stream/Token.php', + 'php_token_default' => '/php-token-stream/Token.php', + 'php_token_dir' => '/php-token-stream/Token.php', + 'php_token_div' => '/php-token-stream/Token.php', + 'php_token_div_equal' => '/php-token-stream/Token.php', + 'php_token_dnumber' => '/php-token-stream/Token.php', + 'php_token_do' => '/php-token-stream/Token.php', + 'php_token_doc_comment' => '/php-token-stream/Token.php', + 'php_token_dollar' => '/php-token-stream/Token.php', + 'php_token_dollar_open_curly_braces' => '/php-token-stream/Token.php', + 'php_token_dot' => '/php-token-stream/Token.php', + 'php_token_double_arrow' => '/php-token-stream/Token.php', + 'php_token_double_cast' => '/php-token-stream/Token.php', + 'php_token_double_colon' => '/php-token-stream/Token.php', + 'php_token_double_quotes' => '/php-token-stream/Token.php', + 'php_token_echo' => '/php-token-stream/Token.php', + 'php_token_else' => '/php-token-stream/Token.php', + 'php_token_elseif' => '/php-token-stream/Token.php', + 'php_token_empty' => '/php-token-stream/Token.php', + 'php_token_encapsed_and_whitespace' => '/php-token-stream/Token.php', + 'php_token_end_heredoc' => '/php-token-stream/Token.php', + 'php_token_enddeclare' => '/php-token-stream/Token.php', + 'php_token_endfor' => '/php-token-stream/Token.php', + 'php_token_endforeach' => '/php-token-stream/Token.php', + 'php_token_endif' => '/php-token-stream/Token.php', + 'php_token_endswitch' => '/php-token-stream/Token.php', + 'php_token_endwhile' => '/php-token-stream/Token.php', + 'php_token_equal' => '/php-token-stream/Token.php', + 'php_token_eval' => '/php-token-stream/Token.php', + 'php_token_exclamation_mark' => '/php-token-stream/Token.php', + 'php_token_exit' => '/php-token-stream/Token.php', + 'php_token_extends' => '/php-token-stream/Token.php', + 'php_token_file' => '/php-token-stream/Token.php', + 'php_token_final' => '/php-token-stream/Token.php', + 'php_token_finally' => '/php-token-stream/Token.php', + 'php_token_for' => '/php-token-stream/Token.php', + 'php_token_foreach' => '/php-token-stream/Token.php', + 'php_token_func_c' => '/php-token-stream/Token.php', + 'php_token_function' => '/php-token-stream/Token.php', + 'php_token_global' => '/php-token-stream/Token.php', + 'php_token_goto' => '/php-token-stream/Token.php', + 'php_token_gt' => '/php-token-stream/Token.php', + 'php_token_halt_compiler' => '/php-token-stream/Token.php', + 'php_token_if' => '/php-token-stream/Token.php', + 'php_token_implements' => '/php-token-stream/Token.php', + 'php_token_inc' => '/php-token-stream/Token.php', + 'php_token_include' => '/php-token-stream/Token.php', + 'php_token_include_once' => '/php-token-stream/Token.php', + 'php_token_includes' => '/php-token-stream/Token.php', + 'php_token_inline_html' => '/php-token-stream/Token.php', + 'php_token_instanceof' => '/php-token-stream/Token.php', + 'php_token_insteadof' => '/php-token-stream/Token.php', + 'php_token_int_cast' => '/php-token-stream/Token.php', + 'php_token_interface' => '/php-token-stream/Token.php', + 'php_token_is_equal' => '/php-token-stream/Token.php', + 'php_token_is_greater_or_equal' => '/php-token-stream/Token.php', + 'php_token_is_identical' => '/php-token-stream/Token.php', + 'php_token_is_not_equal' => '/php-token-stream/Token.php', + 'php_token_is_not_identical' => '/php-token-stream/Token.php', + 'php_token_is_smaller_or_equal' => '/php-token-stream/Token.php', + 'php_token_isset' => '/php-token-stream/Token.php', + 'php_token_line' => '/php-token-stream/Token.php', + 'php_token_list' => '/php-token-stream/Token.php', + 'php_token_lnumber' => '/php-token-stream/Token.php', + 'php_token_logical_and' => '/php-token-stream/Token.php', + 'php_token_logical_or' => '/php-token-stream/Token.php', + 'php_token_logical_xor' => '/php-token-stream/Token.php', + 'php_token_lt' => '/php-token-stream/Token.php', + 'php_token_method_c' => '/php-token-stream/Token.php', + 'php_token_minus' => '/php-token-stream/Token.php', + 'php_token_minus_equal' => '/php-token-stream/Token.php', + 'php_token_mod_equal' => '/php-token-stream/Token.php', + 'php_token_mul_equal' => '/php-token-stream/Token.php', + 'php_token_mult' => '/php-token-stream/Token.php', + 'php_token_namespace' => '/php-token-stream/Token.php', + 'php_token_new' => '/php-token-stream/Token.php', + 'php_token_ns_c' => '/php-token-stream/Token.php', + 'php_token_ns_separator' => '/php-token-stream/Token.php', + 'php_token_num_string' => '/php-token-stream/Token.php', + 'php_token_object_cast' => '/php-token-stream/Token.php', + 'php_token_object_operator' => '/php-token-stream/Token.php', + 'php_token_open_bracket' => '/php-token-stream/Token.php', + 'php_token_open_curly' => '/php-token-stream/Token.php', + 'php_token_open_square' => '/php-token-stream/Token.php', + 'php_token_open_tag' => '/php-token-stream/Token.php', + 'php_token_open_tag_with_echo' => '/php-token-stream/Token.php', + 'php_token_or_equal' => '/php-token-stream/Token.php', + 'php_token_paamayim_nekudotayim' => '/php-token-stream/Token.php', + 'php_token_percent' => '/php-token-stream/Token.php', + 'php_token_pipe' => '/php-token-stream/Token.php', + 'php_token_plus' => '/php-token-stream/Token.php', + 'php_token_plus_equal' => '/php-token-stream/Token.php', + 'php_token_print' => '/php-token-stream/Token.php', + 'php_token_private' => '/php-token-stream/Token.php', + 'php_token_protected' => '/php-token-stream/Token.php', + 'php_token_public' => '/php-token-stream/Token.php', + 'php_token_question_mark' => '/php-token-stream/Token.php', + 'php_token_require' => '/php-token-stream/Token.php', + 'php_token_require_once' => '/php-token-stream/Token.php', + 'php_token_return' => '/php-token-stream/Token.php', + 'php_token_semicolon' => '/php-token-stream/Token.php', + 'php_token_sl' => '/php-token-stream/Token.php', + 'php_token_sl_equal' => '/php-token-stream/Token.php', + 'php_token_sr' => '/php-token-stream/Token.php', + 'php_token_sr_equal' => '/php-token-stream/Token.php', + 'php_token_start_heredoc' => '/php-token-stream/Token.php', + 'php_token_static' => '/php-token-stream/Token.php', + 'php_token_stream' => '/php-token-stream/Token/Stream.php', + 'php_token_stream_cachingfactory' => '/php-token-stream/Token/Stream/CachingFactory.php', + 'php_token_string' => '/php-token-stream/Token.php', + 'php_token_string_cast' => '/php-token-stream/Token.php', + 'php_token_string_varname' => '/php-token-stream/Token.php', + 'php_token_switch' => '/php-token-stream/Token.php', + 'php_token_throw' => '/php-token-stream/Token.php', + 'php_token_tilde' => '/php-token-stream/Token.php', + 'php_token_trait' => '/php-token-stream/Token.php', + 'php_token_trait_c' => '/php-token-stream/Token.php', + 'php_token_try' => '/php-token-stream/Token.php', + 'php_token_unset' => '/php-token-stream/Token.php', + 'php_token_unset_cast' => '/php-token-stream/Token.php', + 'php_token_use' => '/php-token-stream/Token.php', + 'php_token_var' => '/php-token-stream/Token.php', + 'php_token_variable' => '/php-token-stream/Token.php', + 'php_token_while' => '/php-token-stream/Token.php', + 'php_token_whitespace' => '/php-token-stream/Token.php', + 'php_token_xor_equal' => '/php-token-stream/Token.php', + 'php_token_yield' => '/php-token-stream/Token.php', + 'php_tokenwithscope' => '/php-token-stream/Token.php', + 'php_tokenwithscopeandvisibility' => '/php-token-stream/Token.php', + 'sebastianbergmann\\finderfacade\\configuration' => '/finder-facade/Configuration.php', + 'sebastianbergmann\\finderfacade\\finderfacade' => '/finder-facade/FinderFacade.php', + 'sebastianbergmann\\phpdcd\\analyser' => '/src/Analyser.php', + 'sebastianbergmann\\phpdcd\\cli\\application' => '/src/CLI/Application.php', + 'sebastianbergmann\\phpdcd\\cli\\command' => '/src/CLI/Command.php', + 'sebastianbergmann\\phpdcd\\detector' => '/src/Detector.php', + 'sebastianbergmann\\phpdcd\\log\\text' => '/src/Log/Text.php', + 'sebastianbergmann\\version' => '/version/Version.php', + 'symfony\\component\\console\\application' => '/symfony/console/Symfony/Component/Console/Application.php', + 'symfony\\component\\console\\command\\command' => '/symfony/console/Symfony/Component/Console/Command/Command.php', + 'symfony\\component\\console\\command\\helpcommand' => '/symfony/console/Symfony/Component/Console/Command/HelpCommand.php', + 'symfony\\component\\console\\command\\listcommand' => '/symfony/console/Symfony/Component/Console/Command/ListCommand.php', + 'symfony\\component\\console\\consoleevents' => '/symfony/console/Symfony/Component/Console/ConsoleEvents.php', + 'symfony\\component\\console\\descriptor\\applicationdescription' => '/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php', + 'symfony\\component\\console\\descriptor\\descriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php', + 'symfony\\component\\console\\descriptor\\descriptorinterface' => '/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php', + 'symfony\\component\\console\\descriptor\\jsondescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php', + 'symfony\\component\\console\\descriptor\\markdowndescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php', + 'symfony\\component\\console\\descriptor\\textdescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php', + 'symfony\\component\\console\\descriptor\\xmldescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php', + 'symfony\\component\\console\\event\\consolecommandevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php', + 'symfony\\component\\console\\event\\consoleevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php', + 'symfony\\component\\console\\event\\consoleexceptionevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php', + 'symfony\\component\\console\\event\\consoleterminateevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php', + 'symfony\\component\\console\\formatter\\outputformatter' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php', + 'symfony\\component\\console\\formatter\\outputformatterinterface' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php', + 'symfony\\component\\console\\formatter\\outputformatterstyle' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php', + 'symfony\\component\\console\\formatter\\outputformatterstyleinterface' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php', + 'symfony\\component\\console\\formatter\\outputformatterstylestack' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php', + 'symfony\\component\\console\\helper\\descriptorhelper' => '/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php', + 'symfony\\component\\console\\helper\\dialoghelper' => '/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php', + 'symfony\\component\\console\\helper\\formatterhelper' => '/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php', + 'symfony\\component\\console\\helper\\helper' => '/symfony/console/Symfony/Component/Console/Helper/Helper.php', + 'symfony\\component\\console\\helper\\helperinterface' => '/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php', + 'symfony\\component\\console\\helper\\helperset' => '/symfony/console/Symfony/Component/Console/Helper/HelperSet.php', + 'symfony\\component\\console\\helper\\inputawarehelper' => '/symfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php', + 'symfony\\component\\console\\helper\\progresshelper' => '/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php', + 'symfony\\component\\console\\helper\\tablehelper' => '/symfony/console/Symfony/Component/Console/Helper/TableHelper.php', + 'symfony\\component\\console\\input\\argvinput' => '/symfony/console/Symfony/Component/Console/Input/ArgvInput.php', + 'symfony\\component\\console\\input\\arrayinput' => '/symfony/console/Symfony/Component/Console/Input/ArrayInput.php', + 'symfony\\component\\console\\input\\input' => '/symfony/console/Symfony/Component/Console/Input/Input.php', + 'symfony\\component\\console\\input\\inputargument' => '/symfony/console/Symfony/Component/Console/Input/InputArgument.php', + 'symfony\\component\\console\\input\\inputawareinterface' => '/symfony/console/Symfony/Component/Console/Input/InputAwareInterface.php', + 'symfony\\component\\console\\input\\inputdefinition' => '/symfony/console/Symfony/Component/Console/Input/InputDefinition.php', + 'symfony\\component\\console\\input\\inputinterface' => '/symfony/console/Symfony/Component/Console/Input/InputInterface.php', + 'symfony\\component\\console\\input\\inputoption' => '/symfony/console/Symfony/Component/Console/Input/InputOption.php', + 'symfony\\component\\console\\input\\stringinput' => '/symfony/console/Symfony/Component/Console/Input/StringInput.php', + 'symfony\\component\\console\\output\\bufferedoutput' => '/symfony/console/Symfony/Component/Console/Output/BufferedOutput.php', + 'symfony\\component\\console\\output\\consoleoutput' => '/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php', + 'symfony\\component\\console\\output\\consoleoutputinterface' => '/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php', + 'symfony\\component\\console\\output\\nulloutput' => '/symfony/console/Symfony/Component/Console/Output/NullOutput.php', + 'symfony\\component\\console\\output\\output' => '/symfony/console/Symfony/Component/Console/Output/Output.php', + 'symfony\\component\\console\\output\\outputinterface' => '/symfony/console/Symfony/Component/Console/Output/OutputInterface.php', + 'symfony\\component\\console\\output\\streamoutput' => '/symfony/console/Symfony/Component/Console/Output/StreamOutput.php', + 'symfony\\component\\console\\shell' => '/symfony/console/Symfony/Component/Console/Shell.php', + 'symfony\\component\\console\\tester\\applicationtester' => '/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php', + 'symfony\\component\\console\\tester\\commandtester' => '/symfony/console/Symfony/Component/Console/Tester/CommandTester.php', + 'symfony\\component\\finder\\adapter\\abstractadapter' => '/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php', + 'symfony\\component\\finder\\adapter\\abstractfindadapter' => '/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php', + 'symfony\\component\\finder\\adapter\\adapterinterface' => '/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php', + 'symfony\\component\\finder\\adapter\\bsdfindadapter' => '/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php', + 'symfony\\component\\finder\\adapter\\gnufindadapter' => '/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php', + 'symfony\\component\\finder\\adapter\\phpadapter' => '/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php', + 'symfony\\component\\finder\\comparator\\comparator' => '/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php', + 'symfony\\component\\finder\\comparator\\datecomparator' => '/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php', + 'symfony\\component\\finder\\comparator\\numbercomparator' => '/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php', + 'symfony\\component\\finder\\exception\\accessdeniedexception' => '/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php', + 'symfony\\component\\finder\\exception\\adapterfailureexception' => '/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php', + 'symfony\\component\\finder\\exception\\exceptioninterface' => '/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php', + 'symfony\\component\\finder\\exception\\operationnotpermitedexception' => '/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php', + 'symfony\\component\\finder\\exception\\shellcommandfailureexception' => '/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php', + 'symfony\\component\\finder\\expression\\expression' => '/symfony/finder/Symfony/Component/Finder/Expression/Expression.php', + 'symfony\\component\\finder\\expression\\glob' => '/symfony/finder/Symfony/Component/Finder/Expression/Glob.php', + 'symfony\\component\\finder\\expression\\regex' => '/symfony/finder/Symfony/Component/Finder/Expression/Regex.php', + 'symfony\\component\\finder\\expression\\valueinterface' => '/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php', + 'symfony\\component\\finder\\finder' => '/symfony/finder/Symfony/Component/Finder/Finder.php', + 'symfony\\component\\finder\\glob' => '/symfony/finder/Symfony/Component/Finder/Glob.php', + 'symfony\\component\\finder\\iterator\\customfilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php', + 'symfony\\component\\finder\\iterator\\daterangefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php', + 'symfony\\component\\finder\\iterator\\depthrangefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php', + 'symfony\\component\\finder\\iterator\\excludedirectoryfilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'symfony\\component\\finder\\iterator\\filecontentfilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php', + 'symfony\\component\\finder\\iterator\\filenamefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php', + 'symfony\\component\\finder\\iterator\\filepathsiterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php', + 'symfony\\component\\finder\\iterator\\filetypefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php', + 'symfony\\component\\finder\\iterator\\filteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php', + 'symfony\\component\\finder\\iterator\\multiplepcrefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php', + 'symfony\\component\\finder\\iterator\\pathfilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php', + 'symfony\\component\\finder\\iterator\\recursivedirectoryiterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php', + 'symfony\\component\\finder\\iterator\\sizerangefilteriterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php', + 'symfony\\component\\finder\\iterator\\sortableiterator' => '/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php', + 'symfony\\component\\finder\\shell\\command' => '/symfony/finder/Symfony/Component/Finder/Shell/Command.php', + 'symfony\\component\\finder\\shell\\shell' => '/symfony/finder/Symfony/Component/Finder/Shell/Shell.php', + 'symfony\\component\\finder\\splfileinfo' => '/symfony/finder/Symfony/Component/Finder/SplFileInfo.php', + 'symfony\\component\\yaml\\dumper' => '/symfony/yaml/Symfony/Component/Yaml/Dumper.php', + 'symfony\\component\\yaml\\escaper' => '/symfony/yaml/Symfony/Component/Yaml/Escaper.php', + 'symfony\\component\\yaml\\exception\\dumpexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.php', + 'symfony\\component\\yaml\\exception\\exceptioninterface' => '/symfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.php', + 'symfony\\component\\yaml\\exception\\parseexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.php', + 'symfony\\component\\yaml\\exception\\runtimeexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/RuntimeException.php', + 'symfony\\component\\yaml\\inline' => '/symfony/yaml/Symfony/Component/Yaml/Inline.php', + 'symfony\\component\\yaml\\parser' => '/symfony/yaml/Symfony/Component/Yaml/Parser.php', + 'symfony\\component\\yaml\\unescaper' => '/symfony/yaml/Symfony/Component/Yaml/Unescaper.php', + 'symfony\\component\\yaml\\yaml' => '/symfony/yaml/Symfony/Component/Yaml/Yaml.php', + 'theseer\\fdom\\css\\dollarequalrule' => '/fdomdocument/css/DollarEqualRule.php', + 'theseer\\fdom\\css\\notrule' => '/fdomdocument/css/NotRule.php', + 'theseer\\fdom\\css\\nthchildrule' => '/fdomdocument/css/NthChildRule.php', + 'theseer\\fdom\\css\\regexrule' => '/fdomdocument/css/RegexRule.php', + 'theseer\\fdom\\css\\ruleinterface' => '/fdomdocument/css/RuleInterface.php', + 'theseer\\fdom\\css\\translator' => '/fdomdocument/css/Translator.php', + 'theseer\\fdom\\fdomdocument' => '/fdomdocument/fDOMDocument.php', + 'theseer\\fdom\\fdomdocumentfragment' => '/fdomdocument/fDOMDocumentFragment.php', + 'theseer\\fdom\\fdomelement' => '/fdomdocument/fDOMElement.php', + 'theseer\\fdom\\fdomexception' => '/fdomdocument/fDOMException.php', + 'theseer\\fdom\\fdomnode' => '/fdomdocument/fDOMNode.php', + 'theseer\\fdom\\fdomxpath' => '/fdomdocument/fDOMXPath.php', + 'theseer\\fdom\\xpathquery' => '/fdomdocument/XPathQuery.php', + 'theseer\\fdom\\xpathqueryexception' => '/fdomdocument/XPathQueryException.php' + ); + } + + $class = strtolower($class); + + if (isset($classes[$class])) { + require 'phar://phpdcd-1.0.2.phar' . $classes[$class]; + } + } +); + +Phar::mapPhar('phpdcd-1.0.2.phar'); + +if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') { + print file_get_contents(__PHPDCD_PHAR_ROOT__ . '/manifest.txt'); + exit; +} + +$application = new SebastianBergmann\PHPDCD\CLI\Application; +$application->run(); + +__HALT_COMPILER(); ?> +³)|phpdcd-1.0.2.pharCsymfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php§\S… ¶Bsymfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.phpæ +§\Sæ +â}]¶Bsymfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.phpñ +§\Sñ +èÝkA¶Gsymfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php˜)§\S˜)QJ>symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php{ +§\S{ +9!2¶Dsymfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php± §\S± Г¶0symfony/finder/Symfony/Component/Finder/Glob.phpb §\Sb œg@M¶Fsymfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php’ +§\S’ +¸›É”¶Ksymfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.phpb§\Sbýn ¶Esymfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.phpñ §\Sñ þíjû¶Csymfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.phpŸ§\SŸã½,G¶Nsymfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.phpž§\SžÂʽ¶Lsymfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php¥§\S¥x¹P»¶Ssymfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.phpœ§\Sœ±d”¶Msymfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.phpñ§\Sñils_¶Osymfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.phpî§\Sî + +²¶Osymfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.phpç §\Sç éŸ6s¶Ksymfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php½§\S½{à^ǶIsymfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php§\S¼z"¶Gsymfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php¨§\S¨…±Æ)¶Lsymfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.phpµ§\Sµ ÎàU¶Gsymfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php +§\S +¾PÁ¶Asymfony/finder/Symfony/Component/Finder/Comparator/Comparator.php +§\S +|F z¶Esymfony/finder/Symfony/Component/Finder/Comparator/DateComparator.phpº§\Sº§ÓÑ ¶9symfony/finder/Symfony/Component/Finder/Shell/Command.php˜§\S˜5‹´K¶7symfony/finder/Symfony/Component/Finder/Shell/Shell.php…§\S…á4˵¶Ssymfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php¢§\S¢P¶£™¶Msymfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php§\S>Ã@¶Ksymfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php«§\S«ÊcWÞ¶Hsymfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.phpï§\Sï€7¶Rsymfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php9§\S9²¼¶2symfony/finder/Symfony/Component/Finder/Finder.phpÞV§\SÞV»d¶7symfony/finder/Symfony/Component/Finder/SplFileInfo.php§\Sþ59¶Esymfony/finder/Symfony/Component/Finder/Expression/ValueInterface.phpH§\SHÐø"¶;symfony/finder/Symfony/Component/Finder/Expression/Glob.php§\SŠE±¶Asymfony/finder/Symfony/Component/Finder/Expression/Expression.php& +§\S& + 뇶<symfony/finder/Symfony/Component/Finder/Expression/Regex.php§\S¶É(Ú¶,symfony/yaml/Symfony/Component/Yaml/Yaml.php §\S 7MÓº¶/symfony/yaml/Symfony/Component/Yaml/Escaper.phpm §\Sm )%È,¶.symfony/yaml/Symfony/Component/Yaml/Parser.php–[§\S–[û¿X½¶.symfony/yaml/Symfony/Component/Yaml/Dumper.php” §\S” 8¹n¶1symfony/yaml/Symfony/Component/Yaml/Unescaper.phpŸ§\SŸ6£H ¶?symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.phpÒ§\SÒؙ՚¶Dsymfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.phpƧ\SÆî+­l¶@symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.phpî §\Sî ®¿œ›¶Bsymfony/yaml/Symfony/Component/Yaml/Exception/RuntimeException.phpð§\SðÏ|-¶.symfony/yaml/Symfony/Component/Yaml/Inline.php£?§\S£?q+Î3¶;symfony/console/Symfony/Component/Console/ConsoleEvents.phpø§\Sø\0ÂE¶Gsymfony/console/Symfony/Component/Console/Formatter/OutputFormatter.phpŧ\SůŒlì¶Psymfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.phpã§\SãJpù˶Lsymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php&§\S&oÇŒŒ¶Usymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.phpŸ§\SŸ5¦¶Qsymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.phpä +§\Sä +ùv ª¶Dsymfony/console/Symfony/Component/Console/Helper/HelperInterface.phpó§\Só z…׶Dsymfony/console/Symfony/Component/Console/Helper/FormatterHelper.php¼§\S¼g¶se¶@symfony/console/Symfony/Component/Console/Helper/TableHelper.php2§\S2ë¡¿¶Esymfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php +§\S +"U¥1¶Esymfony/console/Symfony/Component/Console/Helper/InputAwareHelper.phpë§\Sësß H¶Csymfony/console/Symfony/Component/Console/Helper/ProgressHelper.phpo.§\So.|p¶Asymfony/console/Symfony/Component/Console/Helper/DialogHelper.php>@§\S>@&â¶;symfony/console/Symfony/Component/Console/Helper/Helper.php«§\S«Z\§¶>symfony/console/Symfony/Component/Console/Helper/HelperSet.phpÞ §\SÞ •¹¶Bsymfony/console/Symfony/Component/Console/Tester/CommandTester.php§\S~H(R¶Fsymfony/console/Symfony/Component/Console/Tester/ApplicationTester.phpf §\Sf ƒhr¥¶Ksymfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.phpK§\SKìÅ0ž¶;symfony/console/Symfony/Component/Console/Output/Output.php;§\S;séÁQ¶Asymfony/console/Symfony/Component/Console/Output/StreamOutput.phpM §\SM Œ,Q«¶Bsymfony/console/Symfony/Component/Console/Output/ConsoleOutput.php} §\S} Ãϱ¶?symfony/console/Symfony/Component/Console/Output/NullOutput.phpÔ§\SÔF£$_¶Csymfony/console/Symfony/Component/Console/Output/BufferedOutput.phph§\Sht|X4¶Dsymfony/console/Symfony/Component/Console/Output/OutputInterface.phpM §\SM ŽÕzˆ¶Csymfony/console/Symfony/Component/Console/Descriptor/Descriptor.php¼ §\S¼ ‰mª¶Fsymfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.phpð%§\Sð%ﶲͶGsymfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.phpø§\Sø¹ø¶Ksymfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php.§\S.ªlP¾¶Osymfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php¹ §\S¹ ¿s¤X¶Gsymfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.phpu§\SuD„®¶Lsymfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.phpá§\SáJZ0<¶Isymfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php&§\S&´Z–¤¶@symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php¸§\S¸¾õ +ž¶Gsymfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.phpÀ§\SÀ||-&¶Isymfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.phpA§\SAs¤K¶>symfony/console/Symfony/Component/Console/Input/ArrayInput.php%§\S%p¶²—¶?symfony/console/Symfony/Component/Console/Input/StringInput.phpû +§\Sû +«Î‚=¶Csymfony/console/Symfony/Component/Console/Input/InputDefinition.php(0§\S(01áE¶Bsymfony/console/Symfony/Component/Console/Input/InputInterface.php6§\S6ÅAM¶Asymfony/console/Symfony/Component/Console/Input/InputArgument.php× §\S× ‚š'¶Gsymfony/console/Symfony/Component/Console/Input/InputAwareInterface.php^§\S^9Kèh¶?symfony/console/Symfony/Component/Console/Input/InputOption.phpV§\SV¬ÓOö9symfony/console/Symfony/Component/Console/Input/Input.php§\Sgm(¶=symfony/console/Symfony/Component/Console/Input/ArgvInput.php¸)§\S¸)ÑQ¶Asymfony/console/Symfony/Component/Console/Command/HelpCommand.php_ +§\S_ +’m¶=symfony/console/Symfony/Component/Console/Command/Command.php7@§\S7@ðƘª¶Asymfony/console/Symfony/Component/Console/Command/ListCommand.phpÀ +§\SÀ + x­¶9symfony/console/Symfony/Component/Console/Application.phpÒŠ§\SÒŠ­LûU¶3symfony/console/Symfony/Component/Console/Shell.phpû§\Sû2V¶src/Log/Text.php §\S nÙ¶Ú¶src/Analyser.phpE7§\SE7Ë0Qð¶src/Detector.phpá§\SáwHx?¶src/CLI/Command.php&§\S&âUIñ¶src/CLI/Application.php,§\S,<»U¶ manifest.txtÔ§\SԜʵͶversion/Version.php §\S YÓ ¶finder-facade/FinderFacade.phpѧ\SÑÕ­±¶finder-facade/Configuration.php{§\S{r˜¡¾¶fdomdocument/fDOMElement.php@0§\S@0œÛ,G¶fdomdocument/fDOMNode.phpí§\Sí¢ו¶fdomdocument/XPathQuery.phpj§\Sj€÷–¶fdomdocument/css/NotRule.php˜§\S˜ª!8_¶fdomdocument/css/Translator.php‚§\S‚0²L3¶!fdomdocument/css/NthChildRule.phpk§\Skf²]œ¶fdomdocument/css/RegexRule.phpǧ\SÇù®s¶$fdomdocument/css/DollarEqualRule.phpw§\Sw} ض"fdomdocument/css/RuleInterface.phpЧ\SÐÎ,—á¶fdomdocument/fDOMXPath.phpç§\S窮<[¶$fdomdocument/XPathQueryException.phpø§\SøÙÖ¶fdomdocument/fDOMException.php¨§\S¨BÕ%¶fdomdocument/autoload.php¾§\S¾!åH¶fdomdocument/fDOMDocument.php¯R§\S¯Re?¶%fdomdocument/fDOMDocumentFragment.php: §\S: §<še¶php-timer/Timer.php6§\S6#X6ñ¶0php-token-stream/Token/Stream/CachingFactory.phpø §\Sø Ó’Õ¶!php-token-stream/Token/Stream.phpuD§\SuDršSͶphp-token-stream/Token.php\§\S\C2ÿ}¶ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * Interface for finder engine implementations. + * + * @author Jean-François Simon + */ +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 = (Boolean) $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 Boolean Whether the adapter is supported + */ + abstract protected function canBeUsed(); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +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 + */ +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 $this->shell->getType() === Shell::TYPE_UNIX && 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('{}') + ; + } + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +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 + */ +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('{}') + ; + } + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +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\Iterator\SortableIterator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * Shell engine implementation using GNU find command. + * + * @author Jean-François Simon + */ +abstract class AbstractFindAdapter extends AbstractAdapter +{ + /** + * @var Shell + */ + protected $shell; + + /** + * Constructor. + */ + 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) { return; } + : 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 Boolean $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 Boolean $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() ? $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'); + 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 Boolean $not + */ + abstract protected function buildContentFiltering(Command $command, array $contains, $not = false); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Iterator; + +/** + * PHP finder engine implementation. + * + * @author Jean-François Simon + */ +class PhpAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new \RecursiveIteratorIterator( + new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs), + \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->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + 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->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + + return $iterator; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'php'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} + + * + * 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 + */ +interface AdapterInterface +{ + /** + * @param Boolean $followLinks + * + * @return AdapterInterface Current instance + */ + public function setFollowLinks($followLinks); + + /** + * @param integer $mode + * + * @return AdapterInterface Current instance + */ + public function setMode($mode); + + /** + * @param array $exclude + * + * @return AdapterInterface Current instance + */ + public function setExclude(array $exclude); + + /** + * @param array $depths + * + * @return AdapterInterface Current instance + */ + public function setDepths(array $depths); + + /** + * @param array $names + * + * @return AdapterInterface Current instance + */ + public function setNames(array $names); + + /** + * @param array $notNames + * + * @return AdapterInterface Current instance + */ + public function setNotNames(array $notNames); + + /** + * @param array $contains + * + * @return AdapterInterface Current instance + */ + public function setContains(array $contains); + + /** + * @param array $notContains + * + * @return AdapterInterface Current instance + */ + public function setNotContains(array $notContains); + + /** + * @param array $sizes + * + * @return AdapterInterface Current instance + */ + public function setSizes(array $sizes); + + /** + * @param array $dates + * + * @return AdapterInterface Current instance + */ + public function setDates(array $dates); + + /** + * @param array $filters + * + * @return AdapterInterface Current instance + */ + public function setFilters(array $filters); + + /** + * @param \Closure|integer $sort + * + * @return AdapterInterface Current instance + */ + public function setSort($sort); + + /** + * @param array $paths + * + * @return AdapterInterface Current instance + */ + public function setPath(array $paths); + + /** + * @param array $notPaths + * + * @return AdapterInterface Current instance + */ + public function setNotPath(array $notPaths); + + /** + * @param boolean $ignore + * + * @return AdapterInterface Current instance + */ + public function ignoreUnreadableDirs($ignore = true); + + /** + * @param string $dir + * + * @return \Iterator Result iterator + */ + public function searchInDirectory($dir); + + /** + * Tests adapter support for current platform. + * + * @return Boolean + */ + public function isSupported(); + + /** + * Returns adapter name. + * + * @return string + */ + public function getName(); +} + + * + * 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 PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param Boolean $strictLeadingDot + * @param Boolean $strictWildcardSlash + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true) + { + $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 ('.' === $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 '#^'.$regex.'$#'; + } +} + + * + * 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\SplFileInfo; + +/** + * Iterate over shell command result. + * + * @author Jean-François Simon + */ +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); + } +} + + * + * 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 + */ +class FileTypeFilterIterator extends FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param integer $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 Boolean 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; + } +} + + * + * 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 + */ +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; + + /** + * Constructor. + * + * @param \Traversable $iterator The Iterator to filter + * @param integer|callback $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(), $b->getRealpath()); + }; + } 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(), $b->getRealpath()); + }; + } 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 callback 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); + } +} + + * + * 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. + * + * @see https://bugs.php.net/bug.php?id=49104 + * + * @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() + { + $iterator = $this; + while ($iterator instanceof \OuterIterator) { + $innerIterator = $iterator->getInnerIterator(); + + if ($innerIterator instanceof RecursiveDirectoryIterator) { + if ($innerIterator->isRewindable()) { + $innerIterator->next(); + $innerIterator->rewind(); + } + } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) { + $iterator->getInnerIterator()->next(); + $iterator->getInnerIterator()->rewind(); + } + $iterator = $iterator->getInnerIterator(); + } + + parent::rewind(); + } +} + + * + * 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 + * @author WÅ‚odzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return Boolean 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; + } + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $content)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $content)) { + return true; + } + } + } + + return $match; + } + + /** + * 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, '/').'/'; + } +} + + * + * 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 + */ +class SizeRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * Constructor. + * + * @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 Boolean 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; + } +} + + * + * 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 + */ +class ExcludeDirectoryFilterIterator extends FilterIterator +{ + private $patterns = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + foreach ($directories as $directory) { + $this->patterns[] = '#(^|/)'.preg_quote($directory, '#').'(/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = strtr($path, '\\', '/'); + foreach ($this->patterns as $pattern) { + if (preg_match($pattern, $path)) { + return false; + } + } + + return true; + } +} + + * + * 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 + */ +class DepthRangeFilterIterator extends FilterIterator +{ + private $minDepth = 0; + + /** + * Constructor. + * + * @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 Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} + + * + * 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\Expression\Expression; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends FilterIterator +{ + protected $matchRegexps = array(); + protected $noMatchRegexps = array(); + + /** + * Constructor. + * + * @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 a regex. + * + * @param string $str + * + * @return Boolean Whether the given string is a regex + */ + protected function isRegex($str) + { + return Expression::create($str)->isRegex(); + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} + + * + * 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 + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var boolean + */ + private $ignoreUnreadableDirs; + + /** + * @var Boolean + */ + private $rewindable; + + /** + * Constructor. + * + * @param string $path + * @param int $flags + * @param boolean $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; + } + + /** + * Return an instance of SplFileInfo with support for relative paths + * + * @return SplFileInfo File information + */ + public function current() + { + return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname()); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + try { + return parent::getChildren(); + } 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/bug.php?id=49104 + parent::next(); + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return Boolean true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} + + * + * 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\Expression\Expression; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getFilename(); + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return true; + } + } + } + + return $match; + } + + /** + * 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 Expression::create($str)->getRegex()->render(); + } +} + + * + * 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 + */ +class CustomFilterIterator extends FilterIterator +{ + private $filters = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $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 Boolean 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; + } +} + + * + * 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 + * @author WÅ‚odzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $filename = strtr($filename, '\\', '/'); + } + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return true; + } + } + } + + return $match; + } + + /** + * 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, '/').'/'; + } +} + + * + * 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 + */ +class DateRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * Constructor. + * + * @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 Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + if (!$fileinfo->isFile()) { + return true; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} + + * + * 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 PHP port + * @author Richard Clamp Perl version + * + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * Constructor. + * + * @param string $test A comparison string + * + * @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] : '=='); + } +} + + * + * 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 + */ +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 Boolean + */ + 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; + } +} + + * + * 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 + */ +class DateComparator extends Comparator +{ + + /** + * Constructor. + * + * @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); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +/** + * @author Jean-François Simon + */ +class Command +{ + /** + * @var Command|null + */ + private $parent; + + /** + * @var array + */ + private $bits = array(); + + /** + * @var array + */ + private $labels = array(); + + /** + * @var \Closure|null + */ + private $errorHandler; + + /** + * Constructor. + * + * @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 Command New Command instance + */ + 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 Command The current Command instance + */ + public function add($bit) + { + $this->bits[] = $bit; + + return $this; + } + + /** + * Prepends a string or a command instance. + * + * @param string|Command $bit + * + * @return Command The current Command instance + */ + 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 Command The current Command instance + */ + public function arg($arg) + { + $this->bits[] = self::quote($arg); + + return $this; + } + + /** + * Appends escaped special command chars. + * + * @param string $esc + * + * @return Command The current Command instance + */ + 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 Command The current Command instance + * + * @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 Command The labeled command + * + * @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 Command Parent command + * + * @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 Command + */ + 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 === $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])) { + call_user_func($this->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 integer $index + * + * @return Command The current Command instance + */ + public function addAtIndex($bit, $index) + { + array_splice($this->bits, $index, 0, $bit); + + return $this; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +/** + * @author Jean-François Simon + */ +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 (self::TYPE_WINDOWS === $this->type) { + // todo: find a way to test if Windows command exists + return false; + } + + if (!function_exists('exec')) { + return false; + } + + // todo: find a better way (command could not be available) + exec('command -v '.$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; + } +} + + * + * 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 + */ +class OperationNotPermitedException extends AdapterFailureException +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; + +/** + * Base exception for all adapter failures. + * + * @author Jean-François Simon + */ +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; + } +} + + * + * 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 + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} + + * + * 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 + */ +interface ExceptionInterface +{ + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + public function getAdapter(); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Shell\Command; + +/** + * @author Jean-François Simon + */ +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; + } +} + + * + * 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\Exception\ExceptionInterface; + +/** + * 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 + * + * @api + */ +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 = array(); + private $paths = array(); + private $notPaths = array(); + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); + + /** + * Constructor. + */ + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + + $this + ->addAdapter(new GnuFindAdapter()) + ->addAdapter(new BsdFindAdapter()) + ->addAdapter(new PhpAdapter(), -50) + ->setAdapter('php') + ; + } + + /** + * Creates a new Finder. + * + * @return Finder A new Finder instance + * + * @api + */ + public static function create() + { + return new static(); + } + + /** + * Registers a finder engine implementation. + * + * @param AdapterInterface $adapter An adapter instance + * @param integer $priority Highest is selected first + * + * @return Finder The current Finder instance + */ + public function addAdapter(Adapter\AdapterInterface $adapter, $priority = 0) + { + $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 Finder The current Finder instance + */ + public function useBestAdapter() + { + $this->resetAdapterSelection(); + + return $this->sortAdapters(); + } + + /** + * Selects the adapter to use. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return Finder The current Finder instance + */ + public function setAdapter($name) + { + 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 Finder The current Finder instance + */ + public function removeAdapters() + { + $this->adapters = array(); + + return $this; + } + + /** + * Returns registered adapters ordered by priority without extra information. + * + * @return AdapterInterface[] + */ + public function getAdapters() + { + return array_values(array_map(function (array $adapter) { + return $adapter['adapter']; + }, $this->adapters)); + } + + /** + * Restricts the matching to directories only. + * + * @return Finder The current Finder instance + * + * @api + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return Finder The current Finder instance + * + * @api + */ + 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 int $level The depth level expression + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\NumberComparator + * + * @api + */ + 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 rage string + * + * @return Finder The current Finder instance + * + * @see strtotime + * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\DateComparator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\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 $size A size range string + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\NumberComparator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * @param Boolean $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore = $this->ignore | static::IGNORE_DOT_FILES; + } else { + $this->ignore = $this->ignore & ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * @param Boolean $ignoreVCS Whether to exclude VCS files or not + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore = $this->ignore | static::IGNORE_VCS_FILES; + } else { + $this->ignore = $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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + 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 Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\CustomFilterIterator + * + * @api + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return Finder The current Finder instance + * + * @api + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param boolean $ignore + * + * @return Finder The current Finder instance + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (Boolean) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|array $dirs A directory path or an array of directories + * + * @return Finder The current Finder instance + * + * @throws \InvalidArgumentException if one of the directories does not exist + * + * @api + */ + public function in($dirs) + { + $resolvedDirs = array(); + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $dir; + } elseif ($glob = glob($dir, 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 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 Finder The finder + * + * @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 Finder The current Finder instance + */ + 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 + * + * @throws \RuntimeException When none of the adapters are supported + */ + 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[] = '#(^|/)\..+(/|$)#'; + } + + foreach ($this->adapters as $adapter) { + if ($adapter['adapter']->isSupported()) { + try { + return $this + ->buildAdapter($adapter['adapter']) + ->searchInDirectory($dir); + } catch (ExceptionInterface $e) {} + } + } + + throw new \RuntimeException('No supported adapter found.'); + } + + /** + * @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); + } +} + + * + * 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 + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * Constructor + * + * @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 + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path 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; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +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 ValueInterface + */ + public function prepend($expr); + + /** + * @param string $expr + * + * @return ValueInterface + */ + public function append($expr); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +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) + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = strlen($this->pattern); + for ($i = 0; $i < $sizeGlob; $i++) { + $car = $this->pattern[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ('.' === $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 new Regex('^'.$regex.'$'); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Expression implements ValueInterface +{ + const TYPE_REGEX = 1; + const TYPE_GLOB = 2; + + /** + * @var ValueInterface + */ + private $value; + + /** + * @param string $expr + * + * @return Expression + */ + 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(); + } + + /** + * @throws \LogicException + * + * @return Glob + */ + 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(); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +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 Regex + * + * @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 Regex + */ + public function addOption($option) + { + if (!$this->hasOption($option)) { + $this->options.= $option; + } + + return $this; + } + + /** + * @param string $option + * + * @return Regex + */ + public function removeOption($option) + { + $this->options = str_replace($option, '', $this->options); + + return $this; + } + + /** + * @param bool $startFlag + * + * @return Regex + */ + public function setStartFlag($startFlag) + { + $this->startFlag = $startFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasStartFlag() + { + return $this->startFlag; + } + + /** + * @param bool $endFlag + * + * @return Regex + */ + public function setEndFlag($endFlag) + { + $this->endFlag = (bool) $endFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasEndFlag() + { + return $this->endFlag; + } + + /** + * @param bool $startJoker + * + * @return Regex + */ + public function setStartJoker($startJoker) + { + $this->startJoker = $startJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasStartJoker() + { + return $this->startJoker; + } + + /** + * @param bool $endJoker + * + * @return Regex + */ + public function setEndJoker($endJoker) + { + $this->endJoker = (bool) $endJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasEndJoker() + { + return $this->endJoker; + } + + /** + * @param array $replacement + * + * @return Regex + */ + 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; + } +} + + * + * 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 + * + * @api + */ +class Yaml +{ + /** + * Parses YAML into a PHP array. + * + * The parse method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. + * + * Usage: + * + * $array = Yaml::parse('config.yml'); + * print_r($array); + * + * + * 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. + * + * @param string $input Path to a YAML file or a string containing YAML + * @param Boolean $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param Boolean $objectSupport True if object support is enabled, false otherwise + * + * @return array The YAML converted to a PHP array + * + * @throws ParseException If the YAML is not valid + * + * @api + */ + public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false) + { + // if input is a file, process it + $file = ''; + if (strpos($input, "\n") === false && is_file($input)) { + 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); + } catch (ParseException $e) { + if ($file) { + $e->setParsedFile($file); + } + + throw $e; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param array $array PHP array + * @param integer $inline The level where you switch to inline YAML + * @param integer $indent The amount of spaces to use for indentation of nested nodes. + * @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $objectSupport true if object support is enabled, false otherwise + * + * @return string A YAML string representing the original PHP array + * + * @api + */ + public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) + { + $yaml = new Dumper(); + $yaml->setIndentation($indent); + + return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport); + } +} + + * + * 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 + */ +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 Boolean True if the value would require double quotes. + */ + public static function requiresDoubleQuoting($value) + { + return 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 Boolean True if the value would require single quotes. + */ + public static function requiresSingleQuoting($value) + { + return 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)); + } +} + + * + * 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 + */ +class Parser +{ + const FOLDED_SCALAR_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + + private $offset = 0; + private $lines = array(); + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = array(); + + /** + * Constructor + * + * @param integer $offset The offset of YAML document (used for line numbers in error messages) + */ + public function __construct($offset = 0) + { + $this->offset = $offset; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $objectSupport true if object support is enabled, false otherwise + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $this->lines = explode("\n", $this->cleanup($value)); + + if (function_exists('mb_detect_encoding') && false === mb_detect_encoding($value, 'UTF-8', true)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.'); + } + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + $data = array(); + $context = null; + 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 = $isInPlace = $isProcessed = false; + if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping'); + } + $context = 'sequence'; + + if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport); + } else { + if (isset($values['leadspaces']) + && ' ' == $values['leadspaces'] + && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) + ) { + // this is a compact notation element, add to next block and parse + $c = $this->getRealCurrentLineNb(); + $parser = new Parser($c); + $parser->refs =& $this->refs; + + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2); + } + + $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport); + } else { + $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport); + } + } + } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && false === strpos($values['key'],' #')) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence'); + } + $context = 'mapping'; + + // force correct settings + Inline::parse(null, $exceptionOnInvalidType, $objectSupport); + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if ('<<' === $key) { + if (isset($values['value']) && 0 === strpos($values['value'], '*')) { + $isInPlace = substr($values['value'], 1); + if (!array_key_exists($isInPlace, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + if (isset($values['value']) && $values['value'] !== '') { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport); + + $merged = array(); + if (!is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } elseif (isset($parsed[0])) { + // Numeric array, merge individual elements + foreach (array_reverse($parsed) as $parsedItem) { + if (!is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); + } + $merged = array_merge($parsedItem, $merged); + } + } else { + // Associative array, merge + $merged = array_merge($merged, $parsed); + } + + $isProcessed = $merged; + } + } elseif (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($isProcessed) { + // Merge keys + $data = $isProcessed; + // hash + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + $data[$key] = null; + } else { + $c = $this->getRealCurrentLineNb() + 1; + $parser = new Parser($c); + $parser->refs =& $this->refs; + $data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport); + } + } else { + if ($isInPlace) { + $data = $this->refs[$isInPlace]; + } else { + $data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport); + } + } + } else { + // 1-liner optionally followed by newline + $lineCount = count($this->lines); + if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) { + try { + $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (is_array($value)) { + $first = reset($value); + if (is_string($first) && 0 === strpos($first, '*')) { + $data = array(); + foreach ($value as $alias) { + $data[] = $this->refs[substr($alias, 1)]; + } + $value = $data; + } + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return $value; + } + + 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 = 'Unable to parse.'; + } + + throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if ($isRef) { + $this->refs[$isRef] = end($data); + } + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return empty($data) ? null : $data; + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return integer The current line number + */ + private function getRealCurrentLineNb() + { + return $this->currentLineNb + $this->offset; + } + + /** + * Returns the current line indentation. + * + * @return integer The current line indentation + */ + private function getCurrentLineIndentation() + { + return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param integer $indentation The indent level at which the block is to be read, or null for default + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock($indentation = null) + { + $this->moveToNextLine(); + + if (null === $indentation) { + $newIndent = $this->getCurrentLineIndentation(); + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + $newIndent = $indentation; + } + + $data = array(substr($this->currentLine, $newIndent)); + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); + + // Comments must not be removed inside a string block (ie. after a line ending with "|") + $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~'; + $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + + while ($this->moveToNextLine()) { + $indent = $this->getCurrentLineIndentation(); + + if ($indent === $newIndent) { + $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + } + + if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine)) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($removeComments && $this->isCurrentLineComment()) { + 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 Boolean + */ + 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. + */ + private function moveToPreviousLine() + { + $this->currentLine = $this->lines[--$this->currentLineNb]; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param Boolean $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param Boolean $objectSupport True if object support is enabled, false otherwise + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue($value, $exceptionOnInvalidType, $objectSupport) + { + 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->currentLine); + } + + return $this->refs[$value]; + } + + if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); + } + + try { + return Inline::parse($value, $exceptionOnInvalidType, $objectSupport); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a folded scalar. + * + * @param string $separator The separator that was used to begin this folded scalar (| or >) + * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) + * @param integer $indentation The indentation that was used to begin this folded scalar + * + * @return string The text value + */ + private function parseFoldedScalar($separator, $indicator = '', $indentation = 0) + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $text = ''; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $text .= "\n"; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + if (preg_match('/^ +/', $this->currentLine, $matches)) { + $indentation = strlen($matches[0]); + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank) { + $text .= substr($this->currentLine, $indentation); + } else { + $text .= $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $text .= "\n"; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $text .= "\n"; + } + + if ($notEOF) { + $this->moveToPreviousLine(); + } + + // replace all non-trailing single newlines with spaces in folded blocks + if ('>' === $separator) { + preg_match('/(\n*)$/', $text, $matches); + $text = preg_replace('/(?getCurrentLineIndentation(); + $EOF = !$this->moveToNextLine(); + + while (!$EOF && $this->isCurrentLineEmpty()) { + $EOF = !$this->moveToNextLine(); + } + + if ($EOF) { + return false; + } + + $ret = false; + if ($this->getCurrentLineIndentation() > $currentIndentation) { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return Boolean 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 Boolean 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 Boolean 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[0] === '#'; + } + + /** + * 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#su', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if ($count == 1) { + // 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 ($count == 1) { + // 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*$#s', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection + * + * @return Boolean 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 = false; + if ( + $this->getCurrentLineIndentation() == $currentIndentation + && + $this->isStringUnIndentedCollectionItem($this->currentLine) + ) { + $ret = true; + } + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item + * + * @return Boolean Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem() + { + return (0 === strpos($this->currentLine, '- ')); + } + +} + + * + * 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 + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var integer + */ + protected $indentation = 4; + + /** + * Sets the indentation. + * + * @param integer $num The amount of spaces to use for indentation of nested nodes. + */ + public function setIndentation($num) + { + $this->indentation = (int) $num; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param integer $inline The level where you switch to inline YAML + * @param integer $indent The level of indentation (used internally) + * @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $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 = array_keys($input) !== range(0, count($input) - 1); + + 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; + } +} + + * + * 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 + */ +class Unescaper +{ + // Parser and Inline assume UTF-8 encoding, so escaped Unicode characters + // must be converted to that encoding. + const ENCODING = 'UTF-8'; + + // Regex fragment that matches an escaped character in a double quoted + // string. + const REGEX_ESCAPED_CHARACTER = "\\\\([0abt\tnvfre \\\"\\/\\\\N_LP]|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 + */ + 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 "\xd"; + case 'e': + return "\x1b"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return $this->convertEncoding("\x00\x85", self::ENCODING, 'UCS-2BE'); + case '_': + // U+00A0 NO-BREAK SPACE + return $this->convertEncoding("\x00\xA0", self::ENCODING, 'UCS-2BE'); + case 'L': + // U+2028 LINE SEPARATOR + return $this->convertEncoding("\x20\x28", self::ENCODING, 'UCS-2BE'); + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return $this->convertEncoding("\x20\x29", self::ENCODING, 'UCS-2BE'); + case 'x': + $char = pack('n', hexdec(substr($value, 2, 2))); + + return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); + case 'u': + $char = pack('n', hexdec(substr($value, 2, 4))); + + return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); + case 'U': + $char = pack('N', hexdec(substr($value, 2, 8))); + + return $this->convertEncoding($char, self::ENCODING, 'UCS-4BE'); + } + } + + /** + * Convert a string from one encoding to another. + * + * @param string $value The string to convert + * @param string $to The input encoding + * @param string $from The output encoding + * + * @return string The string with the new encoding + * + * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring) + */ + private function convertEncoding($value, $to, $from) + { + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($value, $to, $from); + } elseif (function_exists('iconv')) { + return iconv($from, $to, $value); + } + + throw new \RuntimeException('No suitable convert encoding function (install the iconv or mbstring extension).'); + } +} + + * + * 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 + * + * @api + */ +class DumpException extends RuntimeException +{ +} + + * + * 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 + * + * @api + */ +interface ExceptionInterface +{ +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +if (!defined('JSON_UNESCAPED_UNICODE')) { + define('JSON_UNESCAPED_SLASHES', 64); + define('JSON_UNESCAPED_UNICODE', 256); +} + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + * + * @api + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * Constructor. + * + * @param string $message The error message + * @param integer $parsedLine The line where the error occurred + * @param integer $snippet The snippet of code near the problem + * @param string $parsedFile The file name where the error occurred + * @param \Exception $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 integer The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + * + * @param integer $parsedLine The file line + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} + + * + * 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 + * + * @api + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} + + * + * 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 + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + + /** + * Converts a YAML string to a PHP array. + * + * @param string $value A YAML string + * @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $objectSupport true if object support is enabled, false otherwise + * + * @return array A PHP array representing the YAML string + * + * @throws ParseException + */ + public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false) + { + self::$exceptionOnInvalidType = $exceptionOnInvalidType; + self::$objectSupport = $objectSupport; + + $value = trim($value); + + if (0 == strlen($value)) { + return ''; + } + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $i = 0; + switch ($value[0]) { + case '[': + $result = self::parseSequence($value, $i); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $i); + ++$i; + break; + default: + $result = self::parseScalar($value, null, array('"', "'"), $i); + } + + // 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 Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $objectSupport true if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP array + * + * @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'); + } + $repr = is_string($value) ? "'$value'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : strval($value)); + + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + + return $repr; + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + return Escaper::escapeWithSingleQuotes($value); + case '' == $value: + return "''"; + case preg_match(self::getTimestampRegex(), $value): + case in_array(strtolower($value), array('null', '~', 'true', 'false')): + return "'$value'"; + default: + return $value; + } + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param Boolean $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param Boolean $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 + $keys = array_keys($value); + if ((1 == count($keys) && '0' == $keys[0]) + || (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2) + ) { + $output = array(); + foreach ($value as $val) { + $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // mapping + $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 scalar to a YAML string. + * + * @param scalar $scalar + * @param string $delimiters + * @param array $stringDelimiters + * @param integer &$i + * @param Boolean $evaluate + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) + { + 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 (false !== $strpos = strpos($output, ' #')) { + $output = rtrim(substr($output, 0, $strpos)); + } + } elseif (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)); + } + + if ($evaluate) { + $output = self::evaluateScalar($output); + } + } + + return $output; + } + + /** + * Parses a quoted scalar to YAML. + * + * @param string $scalar + * @param integer &$i + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar($scalar, &$i) + { + if (!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 sequence to a YAML string. + * + * @param string $sequence + * @param integer &$i + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence($sequence, &$i = 0) + { + $output = array(); + $len = strlen($sequence); + $i += 1; + + // [foo, bar, ...] + while ($i < $len) { + switch ($sequence[$i]) { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); + + if (!$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $value = self::parseMapping('{'.$value.'}'); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence)); + } + + /** + * Parses a mapping to a YAML string. + * + * @param string $mapping + * @param integer &$i + * + * @return string A YAML string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping($mapping, &$i = 0) + { + $output = array(); + $len = strlen($mapping); + $i += 1; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + // value + $done = false; + while ($i < $len) { + switch ($mapping[$i]) { + case '[': + // nested sequence + $output[$key] = self::parseSequence($mapping, $i); + $done = true; + break; + case '{': + // nested mapping + $output[$key] = self::parseMapping($mapping, $i); + $done = true; + break; + case ':': + case ' ': + break; + default: + $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); + $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 + * + * @return string A YAML string + */ + private static function evaluateScalar($scalar) + { + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + 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 intval(self::parseScalar(substr($scalar, 2))); + 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 null; + case ctype_digit($scalar): + $raw = $scalar; + $cast = intval($scalar); + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = intval($scalar); + + return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return floatval(str_replace(',', '', $scalar)); + case preg_match(self::getTimestampRegex(), $scalar): + return strtotime($scalar); + } + 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 <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } +} + + * + * 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 + */ +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. + * + * @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. + * + * @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. + * + * @var string + */ + const EXCEPTION = 'console.exception'; +} + + * + * 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 class for console output. + * + * @author Konstantin Kudryashov + * + * @api + */ +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) + { + return preg_replace('/([^\\\\]?) FormatterStyle" instances + * + * @api + */ + public function __construct($decorated = false, array $styles = array()) + { + $this->decorated = (Boolean) $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(); + } + + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated) + { + $this->decorated = (Boolean) $decorated; + } + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + * + * @api + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return Boolean + * + * @api + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + * + * @api + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + * + * @api + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + // 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 ($pos && '\\' == $message[$pos - 1]) { + // escaped tag + $output .= $this->applyCurrentStyle($text); + } 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)); + + 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|Boolean 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 { + $style->setOption($match[1]); + } + } + + 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; + } +} + + * + * 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 + * + * @api + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + * + * @api + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return Boolean + * + * @api + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @api + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + * + * @api + */ + public function format($message); +} + + * + * 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 class for defining styles. + * + * @author Konstantin Kudryashov + * + * @api + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = array( + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'magenta' => 35, + 'cyan' => 36, + 'white' => 37 + ); + private static $availableBackgroundColors = array( + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'magenta' => 45, + 'cyan' => 46, + 'white' => 47 + ); + private static $availableOptions = array( + 'bold' => 1, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'conceal' => 8 + ); + + 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 + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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 (false === array_search(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) + { + $codes = array(); + + if (null !== $this->foreground) { + $codes[] = $this->foreground; + } + if (null !== $this->background) { + $codes[] = $this->background; + } + if (count($this->options)) { + $codes = array_merge($codes, $this->options); + } + + if (0 === count($codes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[0m", implode(';', $codes), $text); + } +} + + * + * 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 + * + * @api + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string $color The color name + * + * @api + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + * + * @api + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @api + */ + 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); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + /** + * @var OutputFormatterStyleInterface + */ + private $emptyStyle; + + /** + * Constructor. + * + * @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 OutputFormatterStyleStack + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} + + * + * 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 + * + * @api + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + * + * @api + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + * + * @api + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + * + * @api + */ + public function getName(); +} + + * + * 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 + */ +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', $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 Boolean $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + $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(); + foreach ($lines as $line) { + $messages[] = $line.str_repeat(' ', $len - $this->strlen($line)); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + foreach ($messages as &$message) { + $message = sprintf('<%s>%s', $style, $message, $style); + } + + return implode("\n", $messages); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'formatter'; + } +} + + * + * 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 InvalidArgumentException; + +/** + * Provides helpers to display table output. + * + * @author Саша Стаменковић + */ +class TableHelper extends Helper +{ + const LAYOUT_DEFAULT = 0; + const LAYOUT_BORDERLESS = 1; + const LAYOUT_COMPACT = 2; + + /** + * Table headers. + * + * @var array + */ + private $headers = array(); + + /** + * Table rows. + * + * @var array + */ + private $rows = array(); + + // Rendering options + private $paddingChar; + private $horizontalBorderChar; + private $verticalBorderChar; + private $crossingChar; + private $cellHeaderFormat; + private $cellRowFormat; + private $cellRowContentFormat; + private $borderFormat; + private $padType; + + /** + * Column widths cache. + * + * @var array + */ + private $columnWidths = array(); + + /** + * Number of columns cache. + * + * @var array + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + public function __construct() + { + $this->setLayout(self::LAYOUT_DEFAULT); + } + + /** + * Sets table layout type. + * + * @param int $layout self::LAYOUT_* + * + * @return TableHelper + */ + public function setLayout($layout) + { + switch ($layout) { + case self::LAYOUT_BORDERLESS: + $this + ->setPaddingChar(' ') + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ->setCellRowContentFormat(' %s ') + ->setBorderFormat('%s') + ->setPadType(STR_PAD_RIGHT) + ; + break; + + case self::LAYOUT_COMPACT: + $this + ->setPaddingChar(' ') + ->setHorizontalBorderChar('') + ->setVerticalBorderChar(' ') + ->setCrossingChar('') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ->setCellRowContentFormat('%s') + ->setBorderFormat('%s') + ->setPadType(STR_PAD_RIGHT) + ; + break; + + case self::LAYOUT_DEFAULT: + $this + ->setPaddingChar(' ') + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar('|') + ->setCrossingChar('+') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ->setCellRowContentFormat(' %s ') + ->setBorderFormat('%s') + ->setPadType(STR_PAD_RIGHT) + ; + break; + + default: + throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); + break; + }; + + return $this; + } + + public function setHeaders(array $headers) + { + $this->headers = array_values($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(array $row) + { + $this->rows[] = array_values($row); + + $keys = array_keys($this->rows); + $rowKey = array_pop($keys); + + foreach ($row as $key => $cellValue) { + if (!strstr($cellValue, "\n")) { + continue; + } + + $lines = explode("\n", $cellValue); + $this->rows[$rowKey][$key] = $lines[0]; + unset($lines[0]); + + foreach ($lines as $lineKey => $line) { + $nextRowKey = $rowKey + $lineKey + 1; + + if (isset($this->rows[$nextRowKey])) { + $this->rows[$nextRowKey][$key] = $line; + } else { + $this->rows[$nextRowKey] = array($key => $line); + } + } + } + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return TableHelper + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new \LogicException('The padding char must not be empty'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return TableHelper + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return TableHelper + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return TableHelper + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return TableHelper + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return TableHelper + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return TableHelper + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return TableHelper + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Sets cell padding type. + * + * @param integer $padType STR_PAD_* + * + * @return TableHelper + */ + public function setPadType($padType) + { + $this->padType = $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) + { + $this->output = $output; + + $this->renderRowSeparator(); + $this->renderRow($this->headers, $this->cellHeaderFormat); + if (!empty($this->headers)) { + $this->renderRowSeparator(); + } + foreach ($this->rows as $row) { + $this->renderRow($row, $this->cellRowFormat); + } + if (!empty($this->rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->getNumberOfColumns()) { + return; + } + + if (!$this->horizontalBorderChar && !$this->crossingChar) { + return; + } + + $markup = $this->crossingChar; + for ($column = 0; $column < $count; $column++) { + $markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column)).$this->crossingChar; + } + + $this->output->writeln(sprintf($this->borderFormat, $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + $this->output->write(sprintf($this->borderFormat, $this->verticalBorderChar)); + } + + /** + * 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; + } + + $this->renderColumnSeparator(); + for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) { + $this->renderCell($row, $column, $cellFormat); + $this->renderColumnSeparator(); + } + $this->output->writeln(''); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param integer $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->getColumnWidth($column); + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($cell)) { + $width += strlen($cell) - mb_strlen($cell, $encoding); + } + + $width += $this->strlen($cell) - $this->computeLengthWithoutDecoration($cell); + + $content = sprintf($this->cellRowContentFormat, $cell); + + $this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->paddingChar, $this->padType))); + } + + /** + * Gets number of columns for this table. + * + * @return int + */ + private function getNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return $this->numberOfColumns; + } + + $columns = array(0); + $columns[] = count($this->headers); + foreach ($this->rows as $row) { + $columns[] = count($row); + } + + return $this->numberOfColumns = max($columns); + } + + /** + * Gets column width. + * + * @param integer $column + * + * @return int + */ + private function getColumnWidth($column) + { + if (isset($this->columnWidths[$column])) { + return $this->columnWidths[$column]; + } + + $lengths = array(0); + $lengths[] = $this->getCellWidth($this->headers, $column); + foreach ($this->rows as $row) { + $lengths[] = $this->getCellWidth($row, $column); + } + + return $this->columnWidths[$column] = max($lengths) + strlen($this->cellRowContentFormat) - 2; + } + + /** + * Gets cell width. + * + * @param array $row + * @param integer $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + return isset($row[$column]) ? $this->computeLengthWithoutDecoration($row[$column]) : 0; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->columnWidths = array(); + $this->numberOfColumns = null; + } + + private function computeLengthWithoutDecoration($string) + { + $formatter = $this->output->getFormatter(); + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + + $string = $formatter->format($string); + $formatter->setDecorated($isDecorated); + + return $this->strlen($string); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'table'; + } +} + + * + * 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; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = array(); + + /** + * Constructor. + */ + 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 + */ + 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 DescriptorHelper + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} + + * + * 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 + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritDoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} + + * + * 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; + +/** + * The Progress class provides helpers to display progress output. + * + * @author Chris Jones + * @author Fabien Potencier + */ +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 integer + */ + private $current; + + /** + * Maximum number of steps + * + * @var integer + */ + private $max; + + /** + * Start time of the progress bar + * + * @var integer + */ + 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), + ); + + /** + * 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 integer|null $max Maximum steps + */ + public function start(OutputInterface $output, $max = null) + { + $this->startTime = time(); + $this->current = 0; + $this->max = (int) $max; + $this->output = $output; + $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 integer $step Number of steps to advance + * @param Boolean $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 integer $current The current progress + * @param Boolean $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 = intval($this->current / $this->redrawFreq); + + $this->current = $current; + + $currPeriod = intval($this->current / $this->redrawFreq); + if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { + $this->display(); + } + } + + /** + * Outputs the current progress string. + * + * @param Boolean $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 Boolean $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'])) { + $completeBars = 0; + + 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 integer $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 (count($format) == 2) { + $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'; + } +} + + * + * 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\Formatter\OutputFormatterStyle; + +/** + * The Dialog class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class DialogHelper extends InputAwareHelper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * 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 Boolean|string $default The default answer if the user enters nothing + * @param Boolean|integer $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 Boolean $multiselect Select more than one value separated by comma + * + * @return integer|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) + { + $width = max(array_map('strlen', array_keys($choices))); + + $messages = (array) $question; + foreach ($choices as $key => $value) { + $messages[] = sprintf(" [%-${width}s] %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)); + } + array_push($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; + } + + $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 ($i === 0) { + $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 ('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(''.substr($matches[$ofs], $i).''); + // 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 Boolean $default The default answer if the user enters nothing + * + * @return Boolean 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 Boolean $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 (defined('PHP_WINDOWS_VERSION_BUILD')) { + $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 = $shell === 'csh' ? '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 integer $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 integer $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param Boolean $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 string + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'dialog'; + } + + /** + * Return a valid Unix shell + * + * @return string|Boolean 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 = $exitcode === 0; + } + + /** + * 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 integer $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) + { + $error = null; + while (false === $attempts || $attempts--) { + if (null !== $error) { + $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error')); + } + + try { + return call_user_func($validator, $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +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 A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strlen if it is available. + * + * @param string $string The string to check its length + * + * @return integer The length of the string + */ + protected function strlen($string) + { + if (!function_exists('mb_strlen')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strlen($string, $encoding); + } +} + + * + * 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; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet implements \IteratorAggregate +{ + private $helpers = array(); + private $command; + + /** + * Constructor. + * + * @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 Boolean true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws \InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + /** + * 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; + } + + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + */ +class CommandTester +{ + private $command; + private $input; + private $output; + private $statusCode; + + /** + * Constructor. + * + * @param Command $command A Command instance to test. + */ + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return integer The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input['command'] = $this->command->getName(); + } + + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param Boolean $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return integer The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + private $application; + private $input; + private $output; + private $statusCode; + + /** + * Constructor. + * + * @param Application $application An Application instance to test. + */ + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return integer The command exit code + */ + public function run(array $input, $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param Boolean $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return integer The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} + + * + * 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 + */ +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); +} + + * + * 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 + * + * @api + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * Constructor. + * + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @api + */ + 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; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + if (self::VERBOSITY_QUIET === $this->verbosity) { + return; + } + + $messages = (array) $messages; + + 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; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param Boolean $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} + + * + * 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; + +/** + * 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 + * + * @api + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * Constructor. + * + * @param mixed $stream A stream resource + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean|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 + * + * @api + */ + 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 ? PHP_EOL : ''))) { + // @codeCoverageIgnoreStart + // should never happen + throw new \RuntimeException('Unable to write output.'); + // @codeCoverageIgnoreEnd + } + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * - Windows without Ansicon and ConEmu + * - non tty consoles + * + * @return Boolean true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + // @codeCoverageIgnoreStart + if (DIRECTORY_SEPARATOR == '\\') { + return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); + } + + return function_exists('posix_isatty') && @posix_isatty($this->stream); + // @codeCoverageIgnoreEnd + } +} + + * + * 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 + * + * @api + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + + /** + * Constructor. + * + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @api + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + $outputStream = 'php://stdout'; + if (!$this->hasStdoutSupport()) { + $outputStream = 'php://output'; + } + + parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter); + + $this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $formatter); + } + + /** + * {@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. + * + * IBM iSeries (OS400) exhibits character-encoding issues when writing to + * STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage + * output. + * + * @return boolean + */ + protected function hasStdoutSupport() + { + return ('OS400' != php_uname('s')); + } +} + + * + * 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 + * @author Tobias Schultze + * + * @api + */ +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 writeln($messages, $type = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + // do nothing + } +} + + * + * 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 + */ +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 .= "\n"; + } + } +} + + * + * 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 + * + * @api + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + /** + * Writes a message to the output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param Boolean $newline Whether to add a newline + * @param integer $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + * + * @api + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL); + + /** + * 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 integer $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + * + * @api + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL); + + /** + * Sets the verbosity of the output. + * + * @param integer $level The level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return integer The current level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function getVerbosity(); + + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated(); + + /** + * Sets output formatter. + * + * @param OutputFormatterInterface $formatter + * + * @api + */ + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + * + * @api + */ + public function getFormatter(); +} + + * + * 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; + +/** + * @author Jean-François Simon + */ +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 boolean $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()); +} + + * + * 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 + */ +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($usageXML = $dom->createElement('usage')); + $usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), ''))); + + $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()))); + + $commandXML->appendChild($aliasesXML = $dom->createElement('aliases')); + foreach ($command->getAliases() as $alias) { + $aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); + $aliasXML->appendChild($dom->createTextNode($alias)); + } + + $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 ($application->getName() !== 'UNKNOWN') { + $rootXml->setAttribute('name', $application->getName()); + if ($application->getVersion() !== 'UNKNOWN') { + $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; + } +} + + * + * 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; + +/** + * Text descriptor. + * + * @author Jean-François Simon + */ +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(' (default: %s)', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($argument->getName()); + + $this->writeText(sprintf(" %-${nameWidth}s %s%s", + $argument->getName(), + str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $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(' (default: %s)', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($option->getName()); + $nameWithShortcutWidth = $nameWidth - strlen($option->getName()) - 2; + + $this->writeText(sprintf(" %s %-${nameWithShortcutWidth}s%s%s%s", + '--'.$option->getName(), + $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', + str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $nameWidth = 0; + foreach ($definition->getOptions() as $option) { + $nameLength = strlen($option->getName()) + 2; + if ($option->getShortcut()) { + $nameLength += strlen($option->getShortcut()) + 3; + } + $nameWidth = max($nameWidth, $nameLength); + } + foreach ($definition->getArguments() as $argument) { + $nameWidth = max($nameWidth, strlen($argument->getName())); + } + ++$nameWidth; + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, array('name_width' => $nameWidth))); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $this->writeText('Options:', $options); + $this->writeText("\n"); + foreach ($definition->getOptions() as $option) { + $this->describeInputOption($option, array_merge($options, array('name_width' => $nameWidth))); + $this->writeText("\n"); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->writeText('Usage:', $options); + $this->writeText("\n"); + $this->writeText(' '.$command->getSynopsis(), $options); + $this->writeText("\n"); + + if (count($command->getAliases()) > 0) { + $this->writeText("\n"); + $this->writeText('Aliases: '.implode(', ', $command->getAliases()).'', $options); + } + + if ($definition = $command->getNativeDefinition()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + } + + $this->writeText("\n"); + + if ($help = $command->getProcessedHelp()) { + $this->writeText('Help:', $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 { + $width = $this->getColumnWidth($description->getCommands()); + + $this->writeText($application->getHelp(), $options); + $this->writeText("\n\n"); + + if ($describedNamespace) { + $this->writeText(sprintf("Available commands for the \"%s\" namespace:", $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(''.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $this->writeText(sprintf(" %-${width}s %s", $name, $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 (version_compare(PHP_VERSION, '5.4', '<')) { + return str_replace('\/', '/', json_encode($default)); + } + + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } +} + + * + * 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; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->write( + '**'.$argument->getName().':**'."\n\n" + .'* Name: '.($argument->getName() ?: '')."\n" + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.($argument->getDescription() ?: '')."\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())).'`' : '')."\n" + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.($option->getDescription() ?: '')."\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('-', strlen($command->getName()))."\n\n" + .'* Description: '.($command->getDescription() ?: '')."\n" + .'* Usage: `'.$command->getSynopsis().'`'."\n" + .'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : '') + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n\n"); + $this->write($help); + } + + if ($definition = $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('=', 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)); + } + } +} + + * + * 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; + +/** + * @author Jean-François Simon + */ +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; + + /** + * Constructor. + * + * @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 \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(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(); + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (!$key) { + $key = '_global'; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commands) { + ksort($commands); + } + + return $namespacedCommands; + } +} + + * + * 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 + */ +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' => $argument->getDescription(), + 'default' => $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' => $option->getDescription(), + 'default' => $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' => $command->getSynopsis(), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'aliases' => $command->getAliases(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + ); + } +} + + * + * 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 + */ +interface DescriptorInterface +{ + /** + * Describes an InputArgument instance. + * + * @param OutputInterface $output + * @param object $object + * @param array $options + */ + public function describe(OutputInterface $output, $object, array $options = array()); +} + + * + * 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 + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + /** + * The exit code of the command. + * + * @var integer + */ + 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 integer $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return integer The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} + + * + * 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 + */ +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; + } +} + + * + * 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. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ +} + + * + * 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 + */ +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 integer The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); + * + * @author Fabien Potencier + * + * @api + */ +class ArrayInput extends Input +{ + private $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + * @param InputDefinition $definition A InputDefinition instance + * + * @api + */ + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument() + { + foreach ($this->parameters as $key => $value) { + if ($key && '-' === $key[0]) { + continue; + } + + return $value; + } + } + + /** + * 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 Boolean true if the value is contained in the raw parameters + */ + 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; + } + + /** + * 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) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (is_int($k) && 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]) { + $params[] = $param . ('' != $val ? '='.$this->escapeToken($val) : ''); + } else { + $params[] = $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * Processes command line arguments. + */ + 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 \InvalidArgumentException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \InvalidArgumentException(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 \InvalidArgumentException When option given doesn't exist + * @throws \InvalidArgumentException When a required value is missing + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \InvalidArgumentException(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; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + * + * @api + */ +class StringInput extends ArgvInput +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?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 + // @codeCoverageIgnoreStart + throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + // @codeCoverageIgnoreEnd + } + + $cursor += strlen($match[0]); + } + + return $tokens; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +if (!defined('JSON_UNESCAPED_UNICODE')) { + define('JSON_UNESCAPED_SLASHES', 64); + define('JSON_UNESCAPED_UNICODE', 256); +} + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * 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 + * + * @api + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * Constructor. + * + * @param array $definition An array of InputArgument and InputOption instance + * + * @api + */ + public function __construct(array $definition = array()) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + * + * @param array $definition The definition array + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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|integer $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws \InvalidArgumentException When argument given doesn't exist + * + * @api + */ + 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|integer $name The InputArgument name or position + * + * @return Boolean true if the InputArgument object exists, false otherwise + * + * @api + */ + 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 + * + * @api + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return integer The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return integer 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 + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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 + * + * @api + */ + 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. + * + * @param string $name The InputOption name + * + * @return Boolean true if the InputOption object exists, false otherwise + * + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + * + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return Boolean 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. + * + * @return string The synopsis + */ + public function getSynopsis() + { + $elements = array(); + foreach ($this->getOptions() as $option) { + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName()); + } + + foreach ($this->getArguments() as $argument) { + $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : '')); + + if ($argument->isArray()) { + $elements[] = sprintf('... [%sN]', $argument->getName()); + } + } + + return implode(' ', $elements); + } + + /** + * Returns a textual representation of the InputDefinition. + * + * @return string A string representing the InputDefinition + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + $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 Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the InputDefinition + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getInputDefinitionDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +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 Boolean 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 if arguments given are correct. + * + * Throws an exception when not enough arguments are given. + * + * @throws \RuntimeException + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Gets argument by name. + * + * @param string $name The name of the argument + * + * @return mixed + */ + 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|integer $name The InputArgument name or position + * + * @return Boolean 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(); + + /** + * Gets an option by name. + * + * @param string $name The name of the option + * + * @return mixed + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string|boolean $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 Boolean true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return Boolean + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + * + * @api + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * Constructor. + * + * @param string $name The argument name + * @param integer $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 + * + * @api + */ + 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 Boolean 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 Boolean 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; + } +} + + * + * 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 + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + * + * @param InputInterface + */ + public function setInput(InputInterface $input); +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + * + * @api + */ +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; + + /** + * Constructor. + * + * @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 integer $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_REQUIRED or self::VALUE_NONE) + * + * @throws \InvalidArgumentException If option mode is invalid or incompatible + * + * @api + */ + 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean + */ + 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() + ; + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * 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 + */ +abstract class Input implements InputInterface +{ + /** + * @var InputDefinition + */ + protected $definition; + protected $options = array(); + protected $arguments = array(); + protected $interactive = true; + + /** + * Constructor. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * Binds the current Input instance with the given arguments and options. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function bind(InputDefinition $definition) + { + $this->arguments = array(); + $this->options = array(); + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * Validates the input. + * + * @throws \RuntimeException When not enough arguments are given + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * Checks if the input is interactive. + * + * @return Boolean Returns true if the input is interactive + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ + public function setInteractive($interactive) + { + $this->interactive = (Boolean) $interactive; + } + + /** + * Returns the argument values. + * + * @return array An array of argument values + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 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) + { + 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(); + } + + /** + * 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) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|integer $name The InputArgument name or position + * + * @return Boolean true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * Returns the options values. + * + * @return array An array of option values + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 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) + { + 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(); + } + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string|boolean $value The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + 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; + } + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return Boolean true if the InputOption object exists, false otherwise + */ + 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); + } +} + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * 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 + * + * @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 + * + * @api + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * Constructor. + * + * @param array $argv An array of parameters from the CLI (in the argv format) + * @param InputDefinition $definition A InputDefinition instance + * + * @api + */ + 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; + } + + /** + * Processes command line arguments. + */ + 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, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } 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 { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 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 false values (from a previous call to substr()) to null + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + 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 = ''; + } 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; + } + } + + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + } + + /** + * 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 value(s) to look for in the raw parameters (can be an array) + * + * @return Boolean true if the value is contained in the raw parameters + */ + 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; + } + + /** + * 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) + { + $values = (array) $values; + + $tokens = $this->tokens; + while ($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); + } +} + + * + * 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 + */ +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, 'To output help in other formats', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + )) + ->setDescription('Displays help for a command') + ->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list 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')) { + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, array( + 'format' => $input->getOption('format'), + 'raw' => $input->getOption('raw'), + )); + + $this->command = null; + } +} + + * + * 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\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; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + * + * @api + */ +class Command +{ + private $application; + private $name; + private $aliases = array(); + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis; + private $helperSet; + + /** + * Constructor. + * + * @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 + * + * @api + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException('The command name cannot be empty.'); + } + } + + /** + * 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 + * + * @api + */ + 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 + * + * @api + */ + 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 Boolean + */ + 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|integer 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. + * + * @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 integer The command exit code + * + * @throws \Exception + * + * @see setCode() + * @see execute() + * + * @api + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $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 Command The current instance + * + * @throws \InvalidArgumentException + * + * @see execute() + * + * @api + */ + public function setCode($code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + $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 Boolean $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; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + $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 Command The current instance + * + * @api + */ + 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 + * + * @api + */ + 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 integer $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 Command The current instance + * + * @api + */ + 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 integer $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_REQUIRED or InputOption::VALUE_NONE) + * + * @return Command The current instance + * + * @api + */ + 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 Command The current instance + * + * @throws \InvalidArgumentException When the name is invalid + * + * @api + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Returns the command name. + * + * @return string The command name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return Command The current instance + * + * @api + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + * + * @api + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return Command The current instance + * + * @api + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + * + * @api + */ + 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()); + } + + /** + * Sets the aliases for the command. + * + * @param array $aliases An array of aliases for the command + * + * @return Command The current instance + * + * @throws \InvalidArgumentException When an alias is invalid + * + * @api + */ + public function setAliases($aliases) + { + 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 + * + * @api + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @return string The synopsis + */ + public function getSynopsis() + { + if (null === $this->synopsis) { + $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis())); + } + + return $this->synopsis; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws \InvalidArgumentException if the helper is not defined + * + * @api + */ + public function getHelper($name) + { + return $this->helperSet->get($name); + } + + /** + * Returns a text representation of the command. + * + * @return string A string representing the command + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + $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 Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the command + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + $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)); + } + } +} + + * + * 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 + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('xml')) { + $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, 'To output list in other formats', 'txt'), + )); + } +} + + * + * 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\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\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 + * + * @api + */ +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; + + /** + * Constructor. + * + * @param string $name The name of the application + * @param string $version The version of the application + * + * @api + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->helperSet = $this->getDefaultHelperSet(); + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + 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 integer 0 if everything went fine, or an error code + * + * @throws \Exception When doRun returns Exception + * + * @api + */ + 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 { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + 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; + } + // @codeCoverageIgnoreStart + exit($exitCode); + // @codeCoverageIgnoreEnd + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 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 = 'list'; + $input = new ArrayInput(array('command' => 'list')); + } + + // 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 + * + * @api + */ + 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 + * + * @api + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Set an input definition set to be used with this application + * + * @param InputDefinition $definition The input definition + * + * @api + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message. + */ + public function getHelp() + { + $messages = array( + $this->getLongVersion(), + '', + 'Usage:', + ' [options] command [arguments]', + '', + 'Options:', + ); + + foreach ($this->getDefinition()->getOptions() as $option) { + $messages[] = sprintf(' %-29s %s %s', + '--'.$option->getName().'', + $option->getShortcut() ? '-'.$option->getShortcut().'' : ' ', + $option->getDescription() + ); + } + + return implode(PHP_EOL, $messages); + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param Boolean $boolean Whether to catch exceptions or not during commands execution + * + * @api + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (Boolean) $boolean; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param Boolean $boolean Whether to automatically exit after a command execution or not + * + * @api + */ + public function setAutoExit($boolean) + { + $this->autoExit = (Boolean) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + * + * @api + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + * + * @api + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + * + * @api + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + * + * @api + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + * + * @api + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * @param Command[] $commands An array of commands + * + * @api + */ + 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. + * + * @param Command $command A Command object + * + * @return Command The registered command + * + * @api + */ + public function add(Command $command) + { + $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 \InvalidArgumentException When command name given does not exist + * + * @api + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException(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 Boolean true if the command exists, false otherwise + * + * @api + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not returns the global namespace which always exists. + * + * @return array An array of namespaces + */ + public function getNamespaces() + { + $namespaces = array(); + foreach ($this->commands as $command) { + $namespaces[] = $this->extractNamespace($command->getName()); + + foreach ($command->getAliases() as $alias) { + $namespaces[] = $this->extractNamespace($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 \InvalidArgumentException 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, array())) { + 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 \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(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 \InvalidArgumentException When command name is incorrect or ambiguous + * + * @api + */ + public function find($name) + { + $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, array())) { + 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 \InvalidArgumentException($message); + } + + // 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 \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + 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 + * + * @api + */ + public function all($namespace = null) + { + 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 boolean $raw Whether to return raw command list + * + * @return string A string representing the Application + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText($namespace = null, $raw = false) + { + $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 Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the Application + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($namespace = null, $asDom = false) + { + $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) + { + $strlen = function ($string) { + if (!function_exists('mb_strlen')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strlen($string, $encoding); + }; + + do { + $title = sprintf(' [%s] ', get_class($e)); + $len = $strlen($title); + // 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 + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : (defined('HHVM_VERSION') ? 1 << 31 : PHP_INT_MAX); + $formatter = $output->getFormatter(); + $lines = array(); + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach (str_split($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = $strlen(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; + $lines[] = array($line, $lineLength); + + $len = max($lineLength, $len); + } + } + + $messages = array('', ''); + $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); + $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $strlen($title))))); + foreach ($lines as $line) { + $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::OUTPUT_RAW); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:'); + + // exception related properties + $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(), + )); + + 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 %s:%s', $class, $type, $function, $file, $line)); + } + + $output->writeln(""); + $output->writeln(""); + } + } while ($e = $e->getPrevious()); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); + $output->writeln(""); + $output->writeln(""); + } + } + + /** + * 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 (defined('PHP_WINDOWS_VERSION_BUILD')) { + // 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 integer $width The width + * @param integer $height The height + * + * @return Application The current application + */ + 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('dialog')) { + $inputStream = $this->getHelperSet()->get('dialog')->getInputStream(); + if (!@posix_isatty($inputStream)) { + $input->setInteractive(false); + } + } + + if (true === $input->hasParameterOption(array('--quiet', '-q'))) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $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 integer 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); + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + try { + $exitCode = $command->run($input, $output); + } catch (\Exception $e) { + $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + throw $event->getException(); + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + 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(), + new ProgressHelper(), + new TableHelper(), + )); + } + + /** + * 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 x 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 array 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 || 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); + } +} + + * + * 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\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) + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class Shell +{ + private $application; + private $history; + private $output; + private $hasReadline; + private $processIsolation = false; + + /** + * Constructor. + * + * 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) + { + $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(<<Running with process isolation, you should consider this: + * 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('The command terminated with an error status (%s)', $ret)); + } + } + } + + /** + * Returns the shell header. + * + * @return string The header string + */ + protected function getHeader() + { + return <<{$this->application->getName()} shell ({$this->application->getVersion()}). + +At the prompt, type help for some help, +or list to get a list of available commands. + +To exit the shell, type ^D. + +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 Boolean|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 = (!$line && strlen($line) == 0) ? false : rtrim($line); + } + + return $line; + } + + public function getProcessIsolation() + { + return $this->processIsolation; + } + + public function setProcessIsolation($processIsolation) + { + $this->processIsolation = (Boolean) $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.'); + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package phpdcd + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\PHPDCD\Log; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/phpdcd/tree + * @since Class available since Release 1.0.0 + */ +class Text +{ + /** + * Prints a result set from PHPDCD_Detector::detectDeadCode(). + * + * @param Symfony\Component\Console\Output\OutputInterface $output + * @param array $result + */ + public function printResult(OutputInterface $output, array $result) + { + foreach ($result as $name => $source) { + $output->writeln( + sprintf( + " - %s()\n LOC: %d, declared in %s:%d\n", + $name, + $source['loc'], + $source['file'], + $source['line'] + ) + ); + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package phpdcd + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\PHPDCD; + +/** + * PHPDCD code analyser to be used on a body of source code. + * + * Analyses given source code (files) for declared and called functions + * and aggregates this information. + * + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/phpdcd/tree + * @since Class available since Release 1.0.0 + */ +class Analyser +{ + /** + * Function declaration mapping: maps declared function name to file and line number + * TODO: make mapping to file and line number optional for memory usage reduction? + * @var array + */ + private $functionDeclarations = array(); + + /** + * Function call mapping: maps "callees" to array of "callers" + * TODO: make callers array optional for memory usage reduction? + * @var array + */ + private $functionCalls = array(); + + /** + * Class hierarchy data: maps classes to their direct parent. + * @var array + */ + private $classParents = array(); + + + public function getFunctionDeclarations() + { + return $this->functionDeclarations; + } + + /** + * Get function calls we detected + * @return array maps "callees" to array of "callers" + */ + public function getFunctionCalls() + { + // Resolve parent(class) calls if possible + foreach ($this->functionCalls as $call => $callers) { + if (strpos($call, 'parent(') === 0) { + preg_match('/parent\\((.*?)\\)::(.*)/', $call, $matches); + $class = $matches[1]; + $method = $matches[2]; + foreach ($this->getAncestors($class) as $ancestor) { + $resolvedCall = $ancestor . '::' . $method; + if (isset($this->functionDeclarations[$resolvedCall])) { + $this->functionCalls[$resolvedCall] = $callers; + // TODO: also remove unresolved parent(class) entries? + break; + } + } + } + } + + return $this->functionCalls; + } + + /** + * Get array of a class's ancestors. + * @param $child + * @return array of ancestors + */ + public function getAncestors($child) + { + $ancestors = array(); + while (isset($this->classParents[$child])) { + $child = $this->classParents[$child]; + if (in_array($child, $ancestors)) { + $cycle = implode(' -> ', $ancestors) . ' -> ' . $child; + throw new \RuntimeException('Class hierarchy cycle detected: ' . $cycle); + } + $ancestors[] = $child; + } + return $ancestors; + } + + /** + * Build a mapping between parent classes and all their descendants + * @return array maps each parent classes to array of its subclasses, subsubclasses, ... + */ + public function getClassDescendants() + { + $descendants = array(); + foreach ($this->classParents as $child => $parent) { + // Direct child + $descendants[$parent][] = $child; + // Store child for further ancestors + $ancestor = $parent; + while (isset($this->classParents[$ancestor])) { + $ancestor = $this->classParents[$ancestor]; + $descendants[$ancestor][] = $child; + } + } + return $descendants; + } + + /** + * Analyse a PHP source code file for defined and called functions. + * @param $filename + */ + public function analyseFile($filename) + { + $sourceCode = file_get_contents($filename); + return $this->analyseSourceCode($sourceCode, $filename); + } + + /** + * Analyse PHP source code for defined and called functions + * + * @param string $sourceCode source code. + * @param string $filename optional file name to use in declaration definition + */ + public function analyseSourceCode($sourceCode, $filename = 'undefined') + { + + $blocks = array(); + $currentBlock = null; + $currentClass = ''; + $currentFunction = ''; + $currentInterface = ''; + $namespace = ''; + $variables = array(); + + $tokens = new \PHP_Token_Stream($sourceCode); + $count = count($tokens); + + for ($i = 0; $i < $count; $i++) { + if ($tokens[$i] instanceof \PHP_Token_NAMESPACE) { + $namespace = $tokens[$i]->getName(); + } elseif ($tokens[$i] instanceof \PHP_Token_CLASS) { + $currentClass = $tokens[$i]->getName(); + + if ($namespace != '') { + $currentClass = $namespace . '\\' . $currentClass; + } + + $currentBlock = $currentClass; + } elseif ($tokens[$i] instanceof \PHP_Token_EXTENDS + && $tokens[$i+2] instanceof \PHP_Token_STRING) { + // Store parent-child class relationship. + $this->classParents[$currentClass] = (string)$tokens[$i+2]; + } elseif ($tokens[$i] instanceof \PHP_Token_INTERFACE) { + $currentInterface = $tokens[$i]->getName(); + + if ($namespace != '') { + $currentInterface = $namespace . '\\' . $currentClass; + } + + $currentBlock = $currentInterface; + } elseif ($tokens[$i] instanceof \PHP_Token_NEW && + !$tokens[$i+2] instanceof \PHP_Token_VARIABLE) { + if ($tokens[$i-1] instanceof \PHP_Token_EQUAL) { + $j = -1; + } elseif ($tokens[$i-1] instanceof \PHP_Token_WHITESPACE && + $tokens[$i-2] instanceof \PHP_Token_EQUAL) { + $j = -2; + } else { + continue; + } + + if ($tokens[$i+$j-1] instanceof \PHP_Token_WHITESPACE) { + $j--; + } + + if ($tokens[$i+$j-1] instanceof \PHP_Token_VARIABLE) { + $name = (string)$tokens[$i+$j-1]; + $variables[$name] = (string)$tokens[$i+2]; + } elseif ($tokens[$i+$j-1] instanceof \PHP_Token_STRING && + $tokens[$i+$j-2] instanceof \PHP_Token_OBJECT_OPERATOR && + $tokens[$i+$j-3] instanceof \PHP_Token_VARIABLE) { + $name = (string)$tokens[$i+$j-3] . '->' . + (string)$tokens[$i+$j-1]; + $variables[$name] = (string)$tokens[$i+2]; + } + } elseif ($tokens[$i] instanceof \PHP_Token_FUNCTION) { + if ($currentInterface != '') { + continue; + } + + // Ignore abstract methods. + for ($j=1; $j<=4; $j++) { + if (isset($tokens[$i-$j]) && + $tokens[$i-$j] instanceof \PHP_Token_ABSTRACT) { + continue 2; + } + } + + $function = $tokens[$i]->getName(); + + if ($function == 'anonymous function') { + continue; + } + + $variables = $tokens[$i]->getArguments(); + + if ($currentClass != '') { + $function = $currentClass . '::' . $function; + $variables['$this'] = $currentClass; + } + + $currentFunction = $function; + $currentBlock = $currentFunction; + + $this->functionDeclarations[$function] = array( + 'file' => $filename, 'line' => $tokens[$i]->getLine() + ); + } elseif ($tokens[$i] instanceof \PHP_Token_OPEN_CURLY + || $tokens[$i] instanceof \PHP_Token_CURLY_OPEN + || $tokens[$i] instanceof \PHP_Token_DOLLAR_OPEN_CURLY_BRACES ) { + array_push($blocks, $currentBlock); + $currentBlock = null; + } elseif ($tokens[$i] instanceof \PHP_Token_CLOSE_CURLY) { + $block = array_pop($blocks); + + if ($block == $currentClass) { + $currentClass = ''; + } elseif ($block == $currentFunction) { + $this->functionDeclarations[$currentFunction]['loc'] = + $tokens[$i]->getLine() - $this->functionDeclarations[$currentFunction]['line'] + 1; + $currentFunction = ''; + $variables = array(); + } + } elseif ($tokens[$i] instanceof \PHP_Token_OPEN_BRACKET) { + for ($j = 1; $j <= 4; $j++) { + if (isset($tokens[$i-$j]) && + $tokens[$i-$j] instanceof \PHP_Token_FUNCTION) { + continue 2; + } + } + + if ($tokens[$i-1] instanceof \PHP_Token_STRING) { + $j = -1; + } elseif ($tokens[$i-1] instanceof \PHP_Token_WHITESPACE && + $tokens[$i-2] instanceof \PHP_Token_STRING) { + $j = -2; + } else { + continue; + } + + $function = (string)$tokens[$i+$j]; + $lookForNamespace = true; + + if (isset($tokens[$i+$j-2]) && + $tokens[$i+$j-2] instanceof \PHP_Token_NEW) { + $function .= '::__construct'; + } elseif ((isset($tokens[$i+$j-1]) && + $tokens[$i+$j-1] instanceof \PHP_Token_OBJECT_OPERATOR) || + (isset($tokens[$i+$j-2]) && + $tokens[$i+$j-2] instanceof \PHP_Token_OBJECT_OPERATOR)) { + $_function = $tokens[$i+$j]; + $lookForNamespace = false; + + if ($tokens[$i+$j-1] instanceof \PHP_Token_OBJECT_OPERATOR) { + $j -= 2; + } else { + $j -= 3; + } + + if ($tokens[$i+$j] instanceof \PHP_Token_VARIABLE) { + if (isset($variables[(string)$tokens[$i+$j]])) { + $function = $variables[(string)$tokens[$i+$j]] . + '::' . $_function; + } else { + $function = '::' . $_function; + } + } elseif ($tokens[$i+$j] instanceof \PHP_Token_STRING && + $tokens[$i+$j-1] instanceof \PHP_Token_OBJECT_OPERATOR && + $tokens[$i+$j-2] instanceof \PHP_Token_VARIABLE) { + $variable = (string)$tokens[$i+$j-2] . '->' . + (string)$tokens[$i+$j]; + + if (isset($variables[$variable])) { + $function = $variables[$variable] . '::' . + $_function; + } + } + } elseif ($tokens[$i+$j-1] instanceof \PHP_Token_DOUBLE_COLON) { + $class = (string)$tokens[$i+$j-2]; + + if ($class == 'self' || $class == 'static') { + $class = $currentClass; + } elseif ($class == 'parent') { + $class = "parent($currentClass)"; + } + + $function = $class . '::' . $function; + $j -= 2; + } + + if ($lookForNamespace) { + while ($tokens[$i+$j-1] instanceof \PHP_Token_NS_SEPARATOR) { + $function = $tokens[$i+$j-2] . '\\' . $function; + $j -= 2; + } + } + + if (!isset($this->functionCalls[$function])) { + $this->functionCalls[$function] = array(); + } + $this->functionCalls[$function][] = $currentFunction; + } + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package phpdcd + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\PHPDCD; + +/** + * PHPDCD detector for unused functions. + * + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/phpdcd/tree + * @since Class available since Release 1.0.0 + */ +class Detector +{ + /** + * @param array $files + * @param boolean $recursive + * @return array + */ + public function detectDeadCode(array $files, $recursive = false) + { + + // Analyse files and collect declared and called functions + $analyser = new Analyser(); + foreach ($files as $file) { + $analyser->analyseFile($file); + } + + // Get info on declared and called functions. + $declared = $analyser->getFunctionDeclarations(); + $called = $analyser->getFunctionCalls(); + $classDescendants = $analyser->getClassDescendants(); + + // Search for declared, unused functions. + $result = array(); + foreach ($declared as $name => $source) { + if (!isset($called[$name])) { + // Unused function/method at first sight. + $used = false; + // For methods: check calls from subclass instances as well + $parts = explode('::', $name); + if (count($parts) == 2) { + $class = $parts[0]; + $subclasses = isset($classDescendants[$class]) ? $classDescendants[$class] : array(); + foreach ($subclasses as $subclass) { + if (isset($called[$subclass . '::' . $parts[1]])) { + $used = true; + break; + } + } + } + + if (!$used) { + $result[$name] = $source; + } + } + } + + if ($recursive) { + $done = false; + + while (!$done) { + $done = true; + + foreach ($called as $callee => $callers) { + $_called = false; + + foreach ($callers as $caller) { + if (!isset($result[$caller])) { + $_called = true; + break; + } + } + + if (!$_called) { + if (isset($declared[$callee])) { + $result[$callee] = $declared[$callee]; + } + + $done = false; + + unset($called[$callee]); + } + } + } + } + + ksort($result); + + return $result; + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package phpdcd + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\PHPDCD\CLI; + +use SebastianBergmann\PHPDCD\Detector; +use SebastianBergmann\PHPDCD\Log\Text; +use SebastianBergmann\FinderFacade\FinderFacade; +use Symfony\Component\Console\Command\Command as AbstractCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://github.com/sebastianbergmann/phpdcd/tree + * @since Class available since Release 1.0.0 + */ +class Command extends AbstractCommand +{ + /** + * Configures the current command. + */ + protected function configure() + { + $this->setName('phpdcd') + ->setDefinition( + array( + new InputArgument( + 'values', + InputArgument::IS_ARRAY + ) + ) + ) + ->addOption( + 'names', + null, + InputOption::VALUE_REQUIRED, + 'A comma-separated list of file names to check', + array('*.php') + ) + ->addOption( + 'names-exclude', + null, + InputOption::VALUE_REQUIRED, + 'A comma-separated list of file names to exclude', + array() + ) + ->addOption( + 'exclude', + null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Exclude a directory from code analysis' + ) + ->addOption( + 'recursive', + null, + InputOption::VALUE_NONE, + 'Report code as dead if it is only called by dead code' + ); + } + + /** + * Executes the current command. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null|integer null or 0 if everything went fine, or an error code + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $finder = new FinderFacade( + $input->getArgument('values'), + $input->getOption('exclude'), + $this->handleCSVOption($input, 'names'), + $this->handleCSVOption($input, 'names-exclude') + ); + + $files = $finder->findFiles(); + + if (empty($files)) { + $output->writeln('No files found to scan'); + exit(1); + } + + $quiet = $output->getVerbosity() == OutputInterface::VERBOSITY_QUIET; + + $detector = new Detector; + + $result = $detector->detectDeadCode( + $files, + $input->getOption('recursive') + ); + + if (!$quiet) { + $printer = new Text; + $printer->printResult($output, $result); + + $output->writeln(\PHP_Timer::resourceUsage()); + } + } + + /** + * @param Symfony\Component\Console\Input\InputOption $input + * @param string $option + * @return array + */ + private function handleCSVOption(InputInterface $input, $option) + { + $result = $input->getOption($option); + + if (!is_array($result)) { + $result = explode(',', $result); + array_map('trim', $result); + } + + return $result; + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package phpdcd + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\PHPDCD\CLI; + +use SebastianBergmann\Version; +use Symfony\Component\Console\Application as AbstractApplication; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\ArrayInput; + +/** + * TextUI frontend for PHPDCD. + * + * @author Sebastian Bergmann + * @copyright 2009-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://github.com/sebastianbergmann/phpdcd/tree + * @since Class available since Release 1.0.0 + */ +class Application extends AbstractApplication +{ + public function __construct() + { + $version = new Version('1.0.2', dirname(dirname(__DIR__))); + parent::__construct('phpdcd', $version->getVersion()); + } + + /** + * 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 'phpdcd'; + } + + /** + * Gets the default commands that should always be available. + * + * @return array An array of default Command instances + */ + protected function getDefaultCommands() + { + $defaultCommands = parent::getDefaultCommands(); + + $defaultCommands[] = new Command; + + return $defaultCommands; + } + + /** + * Overridden so that the application doesn't expect the command + * name to be the first argument. + */ + public function getDefinition() + { + $inputDefinition = parent::getDefinition(); + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (!$input->hasParameterOption('--quiet')) { + $output->write( + sprintf( + "phpdcd %s by Sebastian Bergmann.\n\n", + $this->getVersion() + ) + ); + } + + if ($input->hasParameterOption('--version') || + $input->hasParameterOption('-V')) { + exit; + } + + if (!$input->getFirstArgument()) { + $input = new ArrayInput(array('--help')); + } + + parent::doRun($input, $output); + } +} +sebastian/phpdcd: 1.0.2 +phpunit/php-timer: 1.0.5 +phpunit/php-token-stream: 1.2.2 +sebastian/finder-facade: 1.1.0 +sebastian/version: 1.0.3 +symfony/console: v2.4.3 +symfony/finder: v2.4.3 +theseer/fdomdocument: 1.5.0 +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package Version + * @author Sebastian Bergmann + * @copyright 2013-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://github.com/sebastianbergmann/version + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann; + +/** + * @package Version + * @author Sebastian Bergmann + * @copyright 2013-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://github.com/sebastianbergmann/version + * @since Class available since Release 1.0.0 + */ +class Version +{ + private $path; + private $release; + private $version; + + /** + * @param string $release + * @param string $path + */ + public function __construct($release, $path) + { + $this->release = $release; + $this->path = $path; + } + + /** + * @return string + */ + public function getVersion() + { + if ($this->version === null) { + if (count(explode('.', $this->release)) == 3) { + $this->version = $this->release; + } else { + $this->version = $this->release . '-dev'; + } + + $git = $this->getGitInformation($this->path); + + if ($git) { + if (count(explode('.', $this->release)) == 3) { + $this->version = $git; + } else { + $git = explode('-', $git); + + $this->version = $this->release . '-' . $git[2]; + } + } + } + + return $this->version; + } + + /** + * @param string $path + * @return boolean|string + */ + private function getGitInformation($path) + { + if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { + return false; + } + + $dir = getcwd(); + chdir($path); + $result = @exec('git describe --tags 2>&1', $output, $returnCode); + chdir($dir); + + if ($returnCode !== 0) { + return false; + } + + return $result; + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package FinderFacade + * @author Sebastian Bergmann + * @copyright 2012-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\FinderFacade +{ + use Symfony\Component\Finder\Finder; + + /** + * Convenience wrapper for Symfony's Finder component. + * + * @author Sebastian Bergmann + * @copyright 2012-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/finder-facade/tree + * @since Class available since Release 1.0.0 + */ + class FinderFacade + { + /** + * @var array + */ + protected $items = array(); + + /** + * @var array + */ + protected $excludes = array(); + + /** + * @var array + */ + protected $names = array(); + + + /** + * @var array + */ + protected $notNames = array(); + + /** + * @param array $items + * @param array $excludes + * @param array $names + * @param array $notNames + */ + public function __construct(array $items = array(), array $excludes = array(), array $names = array(), array $notNames = array()) + { + $this->items = $items; + $this->excludes = $excludes; + $this->names = $names; + $this->notNames = $notNames; + } + + /** + * @return array + */ + public function findFiles() + { + $files = array(); + $finder = new Finder; + $iterate = FALSE; + + foreach ($this->items as $item) { + if (!is_file($item)) { + $finder->in($item); + $iterate = TRUE; + } + + else { + $files[] = realpath($item); + } + } + + foreach ($this->excludes as $exclude) { + $finder->exclude($exclude); + } + + foreach ($this->names as $name) { + $finder->name($name); + } + + foreach ($this->notNames as $notName) { + $finder->notName($notName); + } + + if ($iterate) { + foreach ($finder as $file) { + $files[] = $file->getRealpath(); + } + } + + return $files; + } + + /** + * @param string $file + */ + public function loadConfiguration($file) + { + $configuration = new Configuration($file); + $configuration = $configuration->parse(); + + $this->items = $configuration['items']; + $this->excludes = $configuration['excludes']; + $this->names = $configuration['names']; + $this->notNames = $configuration['notNames']; + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package FinderFacade + * @author Sebastian Bergmann + * @copyright 2012-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +namespace SebastianBergmann\FinderFacade +{ + use TheSeer\fDOM\fDOMDocument; + + /** + * + * + * + * /path/to/directory + * /path/to/file + * + * /path/to/directory + * *.php + * + * + * + * @author Sebastian Bergmann + * @copyright 2012-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/finder-facade/tree + * @since Class available since Release 1.0.0 + */ + class Configuration + { + /** + * @var string + */ + protected $basePath; + + /** + * @var fDOMDocument + */ + protected $xml; + + /** + * @param string $file + */ + public function __construct($file) + { + $this->basePath = dirname($file); + + $this->xml = new fDOMDocument; + $this->xml->load($file); + } + + /** + * @param string $xpath + * @return array + */ + public function parse($xpath = '') + { + $result = array( + 'items' => array(), 'excludes' => array(), 'names' => array(), 'notNames' => array() + ); + + foreach ($this->xml->getDOMXPath()->query($xpath . 'include/directory') as $item) { + $result['items'][] = $this->toAbsolutePath($item->nodeValue); + } + + foreach ($this->xml->getDOMXPath()->query($xpath . 'include/file') as $item) { + $result['items'][] = $this->toAbsolutePath($item->nodeValue); + } + + foreach ($this->xml->getDOMXPath()->query($xpath . 'exclude') as $exclude) { + $result['excludes'][] = $exclude->nodeValue; + } + + foreach ($this->xml->getDOMXPath()->query($xpath . 'name') as $name) { + $result['names'][] = $name->nodeValue; + } + + foreach ($this->xml->getDOMXPath()->query($xpath . 'notName') as $name) { + $result['notNames'][] = $name->nodeValue; + } + + return $result; + } + + /** + * @param string $path + * @return string + */ + protected function toAbsolutePath($path) + { + // Check whether the path is already absolute. + if ($path[0] === '/' || $path[0] === '\\' || + (strlen($path) > 3 && ctype_alpha($path[0]) && + $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/'))) { + return $path; + } + + // Check whether a stream is used. + if (strpos($path, '://') !== FALSE) { + return $path; + } + + return $this->basePath . DIRECTORY_SEPARATOR . $path; + } + } +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + /** + * fDomElement + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMElement extends \DOMElement { + + public function __toString() { + return $this->C14N(); + } + + /** + * Forward to fDomDocument->query() + * + * @param string $q XPath to use + * @param \DOMNode $ctx \DOMNode to overwrite context + * @param boolean $registerNodeNS Register flag pass thru + * + * @return \DomNodeList + */ + public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); + } + + /** + * Forward to fDomDocument->queryOne() + * + * @param string $q XPath to use + * @param \DOMNode $ctx (optional) \DOMNode to overwrite context + * @param boolean $registerNodeNS Register flag pass thru + * + * @return mixed + */ + public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); + } + + /** + * Forward to fDomDocument->select() + * + * @param string $selector A CSS Level 3 Selector string + * @param \DOMNode $ctx + * @param bool $registerNodeNS + * + * @return \DOMNodeList + */ + + public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); + } + + /** + * Parse and append XML String to node + * + * @param String $str string to process + * + * @return fDomDocumentFragment Reference to the created Fragment + */ + public function appendXML($str) { + $frag = $this->ownerDocument->createDocumentFragment(); + $frag->appendXML($str); + $this->appendChild($frag); + return $frag; + } + + /** + * Create a new element and append it + * + * @param string $name Name of not element to create + * @param string $content Optional content to be set + * + * @return fDOMElement Reference to created fDOMElement + */ + public function appendElement($name, $content = null) { + $node = $this->ownerDocument->createElement($name, $content); + $this->appendChild($node); + return $node; + } + + /** + * Create a new element in given namespace and append it + * + * @param string $ns Namespace of node to create + * @param string $name Name of not element to create + * @param string $content Optional content to be set + * + * @return fDOMElement Reference to created fDOMElement + */ + public function appendElementNS($ns, $name, $content = null) { + $node = $this->ownerDocument->createElementNS($ns, $name, $content); + $this->appendChild($node); + return $node; + } + + /** + * Create a new element in given namespace and append it + * + * @param string $prefix Namespace prefix for node to create + * @param string $name Name of not element to create + * @param string $content Optional content to be set + * + * @return fDOMElement Reference to created fDOMElement + */ + public function appendElementPrefix($prefix, $name, $content = null) { + $node = $this->ownerDocument->createElementPrefix($prefix, $name, $content); + $this->appendChild($node); + return $node; + } + + /** + * Create a new text node and append it + * + * @param string $content Text content to be added + * + * @return \DOMText + */ + public function appendTextNode($content) { + $text = $this->ownerDocument->createTextNode($content); + $this->appendChild($text); + return $text; + } + + /** + * Wrapper to DomElement->getAttribute with default value option + * + * Note: A set but emptry attribute does NOT trigger use of the default + * + * @param string $attr Attribute to access + * @param string $default Default value to use if the attribute is not set + * + * @return string + */ + public function getAttribute($attr, $default='') { + return $this->hasAttribute($attr) ? parent::getAttribute($attr) : $default; + } + + /** + * Wrapper to DomElement->getAttributeNS with default value option + * + * Note: A set but empty attribute does NOT trigger use of the default + * + * @param string $ns Namespace of attribute + * @param string $attr Attribute to access + * @param string $default Default value to use if the attribute is not set + * + * @return string + */ + public function getAttributeNS($ns, $attr, $default='') { + return $this->hasAttributeNS($ns, $attr) ? parent::getAttributeNS($ns, $attr) : $default; + } + + /** + * Wrapper to DOMElement::setAttribute with additional entities support + * + * @param string $attr Attribute name to set + * @param string $value Value to set attribute to + * @param bool $keepEntitites Flag to signale if entities should be kept + * + * @throws fDOMException + * + * @return DOMAttr + * + * @see DOMElement::setAttribute() + */ + public function setAttribute($attr, $value, $keepEntities=false) { + if ($keepEntities === true) { + $attrNode = $this->ownerDocument->createAttribute($attr); + if (!$attrNode) { + throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); + } + $attrNode->value = $value; + $this->appendChild($attrNode); + return $attrNode; + } + return parent::setAttribute($attr, $value); + } + + /** + * Wrapper to namespace aware DOMElement::setAttributeNS with additional entities support + * + * @param string $ns namespace attribute should be in + * @param string $attr Attribute name to set + * @param string $value Value to set attribute to + * @param boolean $keepEntitites Flag to signale if entities should be kept + * + * @return \DOMAttr + * + * @see DOMElement::setAttribute() + */ + public function setAttributeNS($ns, $attr, $value, $keepEntities=false) { + if ($keepEntities === true) { + $attrNode = $this->ownerDocument->createAttributeNS($ns, $attr); + if (!$attrNode) { + throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); + } + $attrNode->value = $value; + $this->appendChild($attrNode); + return $attrNode; + } + return parent::setAttributeNS($ns, $attr, $value); + } + + /** + * Helper to add multiple attributes to an element + * + * @param array $attr Attributes to add as key-value pair + * @param bool $keepEntities Flag wether to keep entities + * + * @return array List with references to created DOMAttr + */ + public function setAttributes(array $attr, $keepEntities=false) { + $attList = array(); + foreach($attr as $name => $value) { + $attList[] = $this->setAttribute($name, $value, $keepEntities); + } + return $attList; + } + + /** + * Helper to add multiple attributes with the given namespace and prefix + * + * @param string $ns Namespace of attribute + * @param string $prefix Namespace prefix for attribute to create + * @param array $attr Attributes to add + * @param bool $keepEntities Flag wether to keep entities + * + * @return void + */ + public function setAttributesNS($ns, $prefix, array $attr, $keepEntities=false) { + foreach($attr as $name => $value) { + $this->setAttributeNS($ns, $prefix.':'.$name, $value, $keepEntities); + } + } + + /** + * Helper method to get children by name + * + * @param string $tagName tagname to search for + * + * @return DomNodeList + */ + public function getChildrenByTagName($tagName) { + return $this->query("*[local-name()='$tagName']"); + } + + /** + * Helper method to get children by name and namespace + * + * @param string $ns namespace nodes have to be in + * @param string $tagName tagname to search for + * + * @return DomNodeList + */ + public function getChildrenByTagNameNS($ns, $tagName) { + return $this->query("*[local-name()='$tagName' and namespace-uri()='$ns']"); + } + + /** + * Check if the given node is in the same document + * + * @param \DomNode $node Node to compare with + * + * @return boolean true on match, false if they differ + * + */ + public function inSameDocument(\DomNode $node) { + return $this->ownerDocument->inSameDocument($node); + } + + /** + * Wrapper to DomDocument::saveXML() with current node as context + * + * @return string + */ + public function saveXML() { + return $this->ownerDocument->saveXML($this); + } + + /** + * Wrapper to DomDocument::saveHTML() with current node as context + * + * @return string + */ + public function saveHTML() { + return $this->ownerDocument->saveHTML($this); + } + + } // fDOMElement + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + /** + * fDomNode + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMNode extends \DOMNode { + + public function __toString() { + return $this->C14N(); + } + + /** + * Forward to fDomDocument->query() + * + * @param string $q XPath to use + * @param \DOMNode $ctx \DOMNode to overwrite context + * @param boolean $registerNodeNS Register flag pass thru + * + * @return \DomNodeList + */ + public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); + } + + /** + * Forward to fDomDocument->queryOne() + * + * @param string $q XPath to use + * @param \DOMNode $ctx (optional) \DOMNode to overwrite context + * @param boolean $registerNodeNS Register flag pass thru + * + * @return mixed + */ + public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); + } + + /** + * Forward to fDomDocument->select() + * + * @param string $selector A CSS Level 3 Selector string + * @param \DOMNode $ctx + * @param bool $registerNodeNS + * + * @return \DOMNodeList + */ + + public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { + return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); + } + + /** + * Check if the given node is in the same document + * + * @param \DomNode $node Node to compare with + * + * @return boolean true on match, false if they differ + * + */ + public function inSameDocument(\DOMNode $node) { + return $this->ownerDocument->inSameDocument($node); + } + + /** + * Wrapper to DomDocument::saveXML() with current node as context + * + * @return string + */ + public function saveXML() { + return $this->ownerDocument->saveXML($this); + } + + /** + * Wrapper to DomDocument::saveHTML() with current node as context + * + * @return string + */ + public function saveHTML() { + return $this->ownerDocument->saveHTML($this); + } + + + + } // fDOMNode + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + /** + * Class XPathQuery + * + * @package TheSeer\fDOM + */ + class XPathQuery { + + /** + * @var string + */ + private $query; + + /** + * Key-value Map for bound values + * + * @var array + */ + private $values = array(); + + /** + * @param string $query + */ + public function __construct($query) { + $this->setQuery($query); + } + + /** + * @param string $query + */ + private function setQuery($query) { + $this->query = $query; + $res = preg_match_all('/(:(\w*))/', $query, $matches); + if ($res > 0) { + $this->values = array_fill_keys($matches[2], ''); + } + } + + public function getKeys() { + return array_keys($this->values); + } + + public function bind($key, $value) { + if (!array_key_exists($key, $this->values)) { + throw new XPathQueryException("'$key' not found in query'", XPathQueryException::KeyNotFound ); + } + $this->values[$key] = $value; + } + + public function generate(\DOMNode $ctx, array $values = NULL) { + return $this->buildQuery($this->getXPathObjectFor($ctx), $values); + } + + public function evaluate(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { + $xp = $this->getXPathObjectFor($ctx); + return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); + } + + public function query(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { + $xp = $this->getXPathObjectFor($ctx); + return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); + } + + public function queryOne(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { + $xp = $this->getXPathObjectFor($ctx); + return $xp->queryOne($this->buildQuery($xp, $values), $ctx, $registerNodeNS); + } + + private function getXPathObjectFor(\DOMNode $ctx) { + $dom = $ctx instanceof \DOMDocument ? $ctx : $ctx->ownerDocument; + if ($dom instanceOf fDOMDocument) { + return $dom->getDOMXPath(); + } + return new fDOMXPath($dom); + } + + private function buildQuery(fDOMXPath $xp, array $values = NULL) { + $backup = $this->values; + if (count($values) > 0) { + foreach($values as $k => $v) { + $this->bind($k, $v); + } + } + $query = $xp->prepare($this->query, $this->values); + $this->values = $backup; + return $query; + } + } + +} +translator = $translator; + } + + /** + * @param $selector + * + * @return string + */ + public function apply($selector) { + return preg_replace_callback( + '/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/', + array($this, 'callback'), + $selector + ); + } + + private function callback(array $matches) { + $subresult = preg_replace( + '/^[^\[]+\[([^\]]*)\].*$/', + '$1', + $this->translator->translate($matches[2]) + ); + return $matches[1] . '[not(' . $subresult . ')]'; + } + + } + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM\CSS { + + /** + * Class Translator + * + * The regular expressions used in this class are heavily inspired by and mostly adopted from + * the css2xpath.js code by Andrea Giammarchi (http://code.google.com/p/css2xpath/). + * The JavaScript version (css2xpath.js) is licensed under the MIT License + * + */ + class Translator { + + /** + * @var array + */ + private $rules; + + /** + * @param string $selector A CSS Selector string + * + * @return string + */ + public function translate($selector) { + foreach($this->getRules() as $rule) { + /** @var RuleInterface $rule */ + $selector = $rule->apply($selector); + } + return '//' . $selector; + } + + /** + * @return array + */ + private function getRules() { + if ($this->rules != NULL) { + return $this->rules; + } + + $this->rules = array( + + // prefix|name + new RegexRule('/([a-zA-Z0-9\_\-\*]+)\|([a-zA-Z0-9\_\-\*]+)/', '$1:$2'), + + // add @ for attribs + new RegexRule("/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/", '[@$1$2]'), + + // multiple queries + new RegexRule("/\s*,\s*/", '|'), + + // , + ~ > + new RegexRule("/\s*(\+|~|>)\s*/", '$1'), + + //* ~ + > + new RegexRule("/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::$2'), + new RegexRule("/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::*[1]/self::$2'), + new RegexRule("/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/", '$1/$2'), + + // all unescaped stuff escaped + new RegexRule("/\[([^=]+)=([^'|'][^\]]*)\]/", '[$1="$2"]'), + + // all descendant or self to // + new RegexRule("/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/", '$1*$2$3'), + new RegexRule("/([\>\+\|\~\,\s])([a-zA-Z\*]+)/", '$1//$2'), + new RegexRule("/\s+\/\//", '//'), + + // :first-child + new RegexRule("/([a-zA-Z0-9\_\-\*]+):first-child/", '*[1]/self::$1'), + + // :last-child + new RegexRule("/([a-zA-Z0-9\_\-\*]+):last-child/", '$1[not(following-sibling::*)]'), + + // :only-child + new RegexRule("/([a-zA-Z0-9\_\-\*]+):only-child/", '*[last()=1]/self::$1'), + + // :empty + new RegexRule("/([a-zA-Z0-9\_\-\*]+):empty/", '$1[not(*) and not(normalize-space())]'), + + // :not + new NotRule($this), + + // :nth-child + new NthChildRule(), + + // :contains(selectors) + new RegexRule('/:contains\(([^\)]*)\)/', '[contains(string(.),"$1")]'), + + // |= attrib + new RegexRule("/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/", '[@$1=$2 or starts-with(@$1,concat($2,"-"))]'), + + // *= attrib + new RegexRule("/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/", '[contains(@$1,$2)]'), + + // ~= attrib + new RegexRule("/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/", '[contains(concat(" ",normalize-space(@$1)," "),concat(" ",$2," "))]'), + + // ^= attrib + new RegexRule("/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/", '[starts-with(@$1,$2)]'), + + // $= attrib + new DollarEqualRule(), + + // != attrib + new RegexRule("/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/", '[not(@$1) or @$1!=$2]'), + + // ids and classes + new RegexRule("/#([a-zA-Z0-9\_\-]+)/", '[@id="$1"]'), + new RegexRule("/\.([a-zA-Z0-9\_\-]+)/", '[contains(concat(" ",normalize-space(@class)," ")," $1 ")]'), + + // normalize multiple filters + new RegexRule("/\]\[([^\]]+)/", ' and ($1)') + + + ); + return $this->rules; + } + } + +} +=0]/self::' . $matches[1]; + } + case 'odd': { + return $matches[1] . '[(count(preceding-sibling::*) + 1) mod 2=1]'; + } + default: { + $b = !isset($matches[2]) || empty($matches[2]) ? '0' : $matches[2]; + $b = preg_replace('/^([0-9]*)n.*?([0-9]*)$/', '$1+$2', $b); + $b = explode('+', $b); + if (!isset($b[1])) { + $b[1] = '0'; + } + return '*[(position()-' . $b[1] . ') mod ' . $b[0] . '=0 and position()>=' . $b[1] . ']/self::' . $matches[1]; + } + } + } + + } + +} +regex = $regex; + $this->replacement = $replacement; + } + + /** + * @param $selector + * + * @return string + */ + public function apply($selector) { + return preg_replace($this->regex, $this->replacement, $selector); + } + + } + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + /** + * fDOMXPath extension to PHP's DOMXPath. + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMXPath extends \DOMXPath { + + protected $doc; + + public function __construct(\DOMDocument $doc) { + parent::__construct($doc); + $this->doc = $doc; + } + + public function prepare($xpath, array $valueMap) { + if (count($valueMap)==0) { + return $xpath; + } + foreach($valueMap as $key => $value) { + $xpath = str_replace(':'.$key, $this->quote($value), $xpath); + } + return $xpath; + } + + public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { + libxml_clear_errors(); + if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop')) { + $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); + } else { + $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); + } + + if (libxml_get_last_error()) { + throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); + } + return $rc; + } + + public function evaluate($q, \DOMNode $ctx = null, $registerNodeNS = true) { + libxml_clear_errors(); + if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop')) { + $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); + } else { + $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); + } + if (libxml_get_last_error()) { + throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); + } + return $rc; + } + + public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { + $rc = $this->evaluate($q, $ctx, $registerNodeNS); + if ($rc instanceof \DOMNodelist) { + return $rc->item(0); + } + return $rc; + } + + public function quote($str) { + if (strpos($str, '"') === false) { + return '"'.$str.'"'; + } + $parts = explode('"', $str); + return 'concat("' . join('",\'"\',"', $parts).'")'; + } + } +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + class XPathQueryException extends \Exception { + const KeyNotFound = 1; + } + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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. + * + * @package fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license BSD License + */ + +namespace TheSeer\fDOM { + + /** + * fDOMException + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMException extends \Exception { + + const LoadError = 1; + const ParseError = 2; + const SaveError = 3; + const QueryError = 4; + const RegistrationFailed = 5; + const NoDOMXPath = 6; + const UnboundPrefix = 7; + const SetFailedError = 8; + const NameInvalid = 9; + + /** + * List of libxml error objects + * + * @var array + */ + private $errorList; + + + /** + * Full Error message + * + * @var string + */ + private $fullMessage = null; + + + /** + * Short Error Message + * + * @var string + */ + private $shortMessage = null; + + + private static $fullMesageMode = true; + + /** + * Constructor + * + * @param string $message Exception message + * @param integer $code Exception code + * @param Exception $chain optional chained exception + * + */ + public function __construct($message, $code = 0, \Exception $chain = NULL) { + $this->shortMessage = $message; + $this->errorList = libxml_get_errors(); + libxml_clear_errors(); + parent :: __construct($message, $code, $chain); + + $this->fullMessage = $message."\n\n"; + + foreach ($this->errorList as $error) { + // hack, skip "attempt to load external pseudo error" + if ($error->code=='1543') { + continue; + } + + if (empty($error->file)) { + $this->fullMessage .= '[XML-STRING] '; + } else { + $this->fullMessage .= '['.$error->file.'] '; + } + + $this->fullMessage .= '[Line: '.$error->line.' - Column: '.$error->column.'] '; + + switch ($error->level) { + case LIBXML_ERR_WARNING: + $this->fullMessage .= "Warning $error->code: "; + break; + case LIBXML_ERR_ERROR: + $this->fullMessage .= "Error $error->code: "; + break; + case LIBXML_ERR_FATAL: + $this->fullMessage .= "Fatal Error $error->code: "; + break; + } + + $this->fullMessage .= str_replace("\n", '', $error->message)."\n"; + + if (self::$fullMesageMode) { + $this->message = $this->fullMessage; + } + + } + } + + /** + * Accessor to fullMessage + * + * @return string + */ + public function getFullMessage() { + return $this->fullMessage; + } + + /** + * Access to shortMessage + * + * @return string + */ + public function getShortMessage() { + return $this->shortMessage; + } + + /** + * Accessor to errorList objets + * + * @return array + */ + public function getErrorList() { + return $this->errorList; + } + + /** + * Toggle wehter getMessage() should return full or only exception message + * + * @param boolean $full Flag to enable or disable full message output + * + * @return void + */ + public function toggleFullMessage($full = true) { + $this->message = $full ? $this->fullMessage : $this->shortMessage; + } + + /** + * Magic method for string context + * + * @return string + */ + public function __toString() { + return $this->fullMessage; + } + + } // fDOMException + +} + '/css/DollarEqualRule.php', + 'theseer\\fdom\\css\\notrule' => '/css/NotRule.php', + 'theseer\\fdom\\css\\nthchildrule' => '/css/NthChildRule.php', + 'theseer\\fdom\\css\\regexrule' => '/css/RegexRule.php', + 'theseer\\fdom\\css\\ruleinterface' => '/css/RuleInterface.php', + 'theseer\\fdom\\css\\translator' => '/css/Translator.php', + 'theseer\\fdom\\fdomdocument' => '/fDOMDocument.php', + 'theseer\\fdom\\fdomdocumentfragment' => '/fDOMDocumentFragment.php', + 'theseer\\fdom\\fdomelement' => '/fDOMElement.php', + 'theseer\\fdom\\fdomexception' => '/fDOMException.php', + 'theseer\\fdom\\fdomnode' => '/fDOMNode.php', + 'theseer\\fdom\\fdomxpath' => '/fDOMXPath.php', + 'theseer\\fdom\\xpathquery' => '/XPathQuery.php', + 'theseer\\fdom\\xpathqueryexception' => '/XPathQueryException.php' + ); + } + $cn = strtolower($class); + if (isset($classes[$cn])) { + require __DIR__ . $classes[$cn]; + } + } +); +// @codeCoverageIgnoreEnd + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + use TheSeer\fDOM\CSS\Translator; + + /** + * fDOMDocument extension to PHP's DOMDocument. + * This class adds various convenience methods to simplify APIs + * It is set to final since further extending it would even more + * break the Object structure after use of registerNodeClass. + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMDocument extends \DOMDocument { + + /** + * XPath Object instance + * + * @var fDOMXPath + */ + private $xp = NULL; + + /** + * List of registered prefixes and their namespace uri + * @var Array + */ + private $prefixes = array(); + + /** + * Extended DOMDocument constructor + * + * @param string $version XML Version, should be 1.0 + * @param string $encoding Encoding, defaults to utf-8 + * @param array $streamOptions optional stream options array + * + * @return fDOMDocument + */ + public function __construct($version = '1.0', $encoding = 'utf-8', $streamOptions = NULL) { + if (!is_null($streamOptions)) { + $this->setStreamContext($streamOptions); + } + + libxml_use_internal_errors(TRUE); + $rc = parent::__construct($version, $encoding); + + $this->registerNodeClass('DOMDocument', get_called_class()); + $this->registerNodeClass('DOMNode', 'TheSeer\fDOM\fDOMNode'); + $this->registerNodeClass('DOMElement', 'TheSeer\fDOM\fDOMElement'); + $this->registerNodeClass('DOMDocumentFragment', 'TheSeer\fDOM\fDOMDocumentFragment'); + + return $rc; + } + + /** + * Reset XPath object so the clone gets a new instance when needed + */ + public function __clone() { + $this->xp = new fDOMXPath($this); + foreach($this->prefixes as $prefix => $uri) { + $this->xp->registerNamespace($prefix, $uri); + } + } + + public function __toString() { + return $this->C14N(); + } + + /** + * Set Stream context options + * + * @param Array $options Stream context options + * + * @return boolean true on success, false on failure + */ + public function setStreamContext(Array $options) { + if (!count($options)) { + return FALSE; + } + $context = stream_context_create($options); + libxml_set_streams_context($context); + return TRUE; + } + + /** + * Wrapper to DOMDocument load with exception handling + * Returns true on success to satisfy the compatibilty of the original DOM Api + * + * @param string $fname File to load + * @param int|null $options LibXML Flags to pass + * + * @throws fDOMException + * + * @return bool|mixed + */ + public function load($fname, $options = LIBXML_NONET) { + $this->xp = NULL; + $tmp = parent :: load($fname, $options); + if (!$tmp || libxml_get_last_error()) { + throw new fDOMException("loading file '$fname' failed.", fDOMException::LoadError); + } + return TRUE; + } + + /** + * Wrapper to DOMDocument loadXML with exception handling + * Returns true on success to satisfy the compatibilty of the original DOM Api + * + * @param string $source XML source code + * @param integer $options LibXML option flags + * + * @throws fDOMException + * + * @return boolean + */ + public function loadXML($source, $options = LIBXML_NONET) { + $this->xp = NULL; + $tmp = parent :: loadXML($source, $options); + if (!$tmp || libxml_get_last_error()) { + throw new fDOMException('parsing string failed', fDOMException::ParseError); + } + return TRUE; + } + + /** + * Wrapper to DOMDocument loadHTMLFile with exception handling. + * Returns true on success to satisfy the compatibilty of the original DOM Api + * + * @param string $fname html file to load + * @param integer $options Options bitmask (@see DOMDocument::loadHTMLFile) + * + * @throws fDOMException + * + * @return boolean + */ + public function loadHTMLFile($fname, $options = NULL) { + $this->xp = NULL; + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + if ($options != NULL) { + throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); + } + $tmp = parent :: loadHTMLFile($fname); + } else { + $tmp = parent :: loadHTMLFile($fname, $options); + } + if (!$tmp || libxml_get_last_error()) { + throw new fDOMException("loading html file '$fname' failed", fDOMException::LoadError); + } + return TRUE; + } + + /** + * Wrapper to DOMDocument loadHTML with exception handling + * Returns true on success to satisfy the compatibilty of the original DOM Api + * + * @param string $source html source code + * @param integer $options Options bitmask (@see DOMDocument::loadHTML) + * + * @throws fDOMException + * + * @return boolean + */ + public function loadHTML($source, $options = NULL) { + $this->xp = NULL; + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + if ($options != NULL) { + throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); + } + $tmp = parent :: loadHTML($source); + } else { + $tmp = parent :: loadHTML($source, $options); + } + if (!$tmp || libxml_get_last_error()) { + throw new fDOMException('parsing html string failed', fDOMException::ParseError); + } + return TRUE; + } + + /** + * Wrapper to DOMDocument::save with exception handling + * + * @param string $filename filename to save to + * @param integer $options Options bitmask (@see DOMDocument::save) + * + * @throws fDOMException + * + * @return integer bytes saved + */ + public function save($filename, $options = NULL) { + $tmp = parent::save($filename, $options); + if (!$tmp) { + throw new fDOMException('saving xml file failed', fDOMException::SaveError); + } + return $tmp; + } + + /** + * Wrapper to DOMDocument::saveHTML with exception handling + * + * @param DOMNode|null $node Context DOMNode (optional) + * + * @throws fDOMException + * @return string html content + */ + public function saveHTML(\DOMNode $node = NULL) { + if (version_compare(PHP_VERSION, '5.3.6', '<') && $node != NULL) { + throw new fDOMException('Passing a context node requires PHP 5.3.6+', fDOMException::SaveError); + } + $tmp = parent::saveHTML($node); + if (!$tmp) { + throw new fDOMException('serializing to HTML failed', fDOMException::SaveError); + } + return $tmp; + } + + /** + * Wrapper to DOMDocument::saveHTMLfile with exception handling + * + * @param string $filename filename to save to + * @param integer $options Options bitmask (@see DOMDocument::saveHTMLFile) + * + * @throws fDOMException + * + * @return integer bytes saved + */ + public function saveHTMLFile($filename, $options = NULL) { + $tmp = parent::saveHTMLFile($filename, $options); + if (!$tmp) { + throw new fDOMException('saving to HTML file failed', fDOMException::SaveError); + } + return $tmp; + } + + /** + * Wrapper to DOMDocument::saveXML with exception handling + * + * @param \DOMNode $node node to start serializing at + * @param integer $options options flags as bitmask + * + * @throws fDOMException + * + * @return string serialized XML + */ + public function saveXML(\DOMNode $node = NULL, $options = NULL) { + try { + $tmp = parent::saveXML($node, $options); + if (!$tmp) { + throw new fDOMException('serializing to XML failed', fDOMException::SaveError); + } + return $tmp; + } catch (\Exception $e) { + if (!$e instanceof fDOMException) { + throw new fDOMException($e->getMessage(), fDOMException::SaveError, $e); + } + throw $e; + } + } + + /** + * get Instance of DOMXPath Object for current DOM + * + * @throws fDOMException + * + * @return fDOMXPath + */ + public function getDOMXPath() { + if (is_null($this->xp)) { + $this->xp = new fDOMXPath($this); + } + if (!$this->xp) { + throw new fDOMException('creating DOMXPath object failed.', fDOMException::NoDOMXPath); + } + return $this->xp; + } + + /** + * Convert a given DOMNodeList into a DOMFragment + * + * @param \DOMNodeList $list The Nodelist to process + * @param boolean $move Signale if nodes are to be moved into fragment or not + * + * @return fDOMDocumentFragment + */ + public function nodeList2Fragment(\DOMNodeList $list, $move=FALSE) { + $frag = $this->createDocumentFragment(); + /** @var fDOMNode $node */ + foreach($list as $node) { + $frag->appendChild($move ? $node : $node->cloneNode(TRUE)); + } + return $frag; + } + + /** + * Perform an xpath query + * + * @param String $q query string containing xpath + * @param \DOMNode|null $ctx (optional) Context DOMNode + * @param boolean $registerNodeNS Register flag pass through + * + * @return \DOMNodeList + */ + public function query($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { + if (is_null($this->xp)) { + $this->getDOMXPath(); + } + return $this->xp->evaluate($q, $ctx, $registerNodeNS); + } + + /** + * Perform an xpath query and return only the 1st match + * + * @param String $q query string containing xpath + * @param \DOMNode $ctx (optional) Context DOMNode + * @param boolean $registerNodeNS Register flag pass thru + * + * @return fDOMNode + */ + public function queryOne($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { + if (is_null($this->xp)) { + $this->getDOMXPath(); + } + return $this->xp->queryOne($q, $ctx, $registerNodeNS); + } + + /** + * Forwarder to fDOMXPath's prepare method allowing for easy and secure + * placeholder replacement comparable to sql's prepared statements + * . + * @param string $xpath String containing xpath with :placeholder markup + * @param array $valueMap Array containing keys (:placeholder) and value pairs to be quoted + * + * @return string + */ + public function prepareQuery($xpath, array $valueMap) { + if (is_null($this->xp)) { + $this->getDOMXPath(); + } + return $this->xp->prepare($xpath, $valueMap); + } + + /** + * Use a CSS Level 3 Selector string to query select nodes + * + * @param string $selector A CSS Level 3 Selector string + * @param \DOMNode $ctx + * @param bool $registerNodeNS + * + * @return \DOMNodeList + */ + public function select($selector, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { + $translator = new Translator(); + $xpath = $translator->translate($selector); + if ($ctx != NULL) { + $xpath = '.' . $xpath; + } + return $this->query($xpath, $ctx, $registerNodeNS); + } + + /** + * Forward to DOMXPath->registerNamespace() + * + * @param string $prefix The prefix to use + * @param string $uri The uri to assign to this prefix + * + * @throws fDOMException + * + * @return void + */ + public function registerNamespace($prefix, $uri) { + if (is_null($this->xp)) { + $this->getDOMXPath(); + } + if (!$this->xp->registerNamespace($prefix, $uri)) { + throw new fDOMException("Registering namespace '$uri' with prefix '$prefix' failed.", fDOMException::RegistrationFailed); + } + $this->prefixes[$prefix] = $uri; + } + + /** + * Forward to DOMXPath->registerPHPFunctions() + * + * @param mixed $restrict Array of function names or string with functionname to restrict callabilty to + * + * @throws fDOMException + * + * @return void + */ + public function registerPHPFunctions($restrict = NULL) { + if (is_null($this->xp)) { + $this->getDOMXPath(); + } + $this->xp->registerPHPFunctions($restrict); + if (libxml_get_last_error()) { + throw new fDOMException("Registering php functions failed.", fDOMException::RegistrationFailed); + } + } + + /** + * Create a new element in namespace defined by given prefix + * + * @param string $prefix Namespace prefix for node to create + * @param string $name Name of not element to create + * @param string $content Optional content to be set + * @param bool $asTextNode Create content as textNode rather then setting nodeValue + * + * @throws fDOMException + * + * @return fDOMElement Reference to created fDOMElement + */ + public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { + if (!isset($this->prefixes[$prefix])) { + throw new fDOMException("'$prefix' not bound", fDOMException::UnboundPrefix); + } + return $this->createElementNS($this->prefixes[$prefix], $prefix.':'.$name, $content, $asTextNode); + } + + /** + * Create a new fDOMElement and return it, optionally set content + * + * @param string $name Name of node to create + * @param null $content Content to set (optional) + * @param bool $asTextNode Create content as textNode rather then setting nodeValue + * + * @throws fDOMException + * + * @return fDOMElement Reference to created fDOMElement + */ + public function createElement($name, $content = NULL, $asTextnode = FALSE) { + try { + $node = parent::createElement($name); + if (!$node) { + throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); + } + if ($content !== NULL) { + if ($asTextnode) { + $node->appendChild($this->createTextnode($content)); + } else { + $node->nodeValue = $content; + } + if (libxml_get_errors()) { + throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); + } + } + } catch (\DOMException $e) { + throw new fDOMException("Creating elemnt with name '$name' failed", 0, $e); + } + return $node; + } + + /** + * Create a new fDOMElement within given namespace and return it + * + * @param string $namespace Namespace URI for node to create + * @param string $name Name of node to create + * @param null $content Content to set (optional) + * @param bool $asTextNode Create content as textNode rather then setting nodeValue + * + * @throws fDOMException + * + * @return fDOMElement + */ + public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { + $node = parent::createElementNS($namespace, $name); + if (!$node) { + throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); + } + if ($content !== NULL) { + if ($asTextNode) { + $node->appendChild($this->createTextnode($content)); + } else { + $node->nodeValue = $content; + } + if (libxml_get_errors()) { + throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); + } + } + return $node; + } + + /** + * Check if the given node is in the same document + * + * @param \DOMNode $node Node to compare with + * + * @return boolean true on match, false if they differ + * + */ + public function inSameDocument(\DOMNode $node) { + if ($node instanceof \DOMDocument) { + return $this->isSameNode($node); + } + return $this->isSameNode($node->ownerDocument); + } + + /** + * Create a new element and append it as documentElement + * + * @param $name Name of not element to create + * @param $content Optional content to be set + * + * @return fDOMElement Reference to created fDOMElement + */ + public function appendElement($name, $content = NULL, $asTextNode = FALSE) { + return $this->appendChild( + $this->createElement($name, $content, $asTextNode) + ); + } + + /** + * Create a new element in given namespace and append it as documentElement + * + * @param $ns Namespace of node to create + * @param $name Name of not element to create + * @param $content Optional content to be set + * + * @return fDOMElement Reference to created fDOMElement + */ + public function appendElementNS($ns, $name, $content = NULL, $asTextNode = FALSE) { + return $this->appendChild( + $this->createElementNS($ns, $name, $content, $asTextNode) + ); + } + + } // fDOMDocument + +} + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Arne Blankerts nor the names of contributors + * may 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 HOLDER ORCONTRIBUTORS + * 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 PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @copyright Arne Blankerts , All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://github.com/theseer/fdomdocument + * + */ + +namespace TheSeer\fDOM { + + /** + * fDOMDocumentFragment + * + * @category PHP + * @package TheSeer\fDOM + * @author Arne Blankerts + * @access public + * + */ + class fDOMDocumentFragment extends \DOMDocumentFragment { + + public function __toString() { + return $this->C14N(); + } + + /** + * Wrapper to standard method with exception support + * + * @param string $str Data string to parse and append + * + * @return boolean true on success + */ + public function appendXML($str) { + if (!parent::appendXML($str)) { + throw new fDOMException('Appending xml string failed', fDOMException::ParseError); + } + return true; + } + + /** + * Check if the given node is in the same document + * + * @param \DOMNode $node Node to compare with + * + * @return boolean true on match, false if they differ + * + */ + public function inSameDocument(\DOMNode $node) { + return $this->ownerDocument->inSameDocument($node); + } + + } // fDOMDocumentFragment + +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package PHP + * @subpackage Timer + * @author Sebastian Bergmann + * @copyright 2010-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://github.com/sebastianbergmann/php-timer + * @since File available since Release 1.0.0 + */ + +/** + * Utility class for timing. + * + * @package PHP + * @subpackage Timer + * @author Sebastian Bergmann + * @copyright 2010-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/php-timer + * @since Class available since Release 1.0.0 + */ +class PHP_Timer +{ + /** + * @var array + */ + private static $times = array( + 'hour' => 3600000, + 'minute' => 60000, + 'second' => 1000 + ); + + /** + * @var array + */ + private static $startTimes = array(); + + /** + * @var float + */ + public static $requestTime; + + /** + * Starts the timer. + */ + public static function start() + { + array_push(self::$startTimes, microtime(TRUE)); + } + + /** + * Stops the timer and returns the elapsed time. + * + * @return float + */ + public static function stop() + { + return microtime(TRUE) - array_pop(self::$startTimes); + } + + /** + * Formats the elapsed time as a string. + * + * @param float $time + * @return string + */ + public static function secondsToTimeString($time) + { + $ms = round($time * 1000); + + foreach (self::$times as $unit => $value) { + if ($ms >= $value) { + $time = floor($ms / $value * 100.0) / 100.0; + return $time . ' ' . ($time == 1 ? $unit : $unit . 's'); + } + } + + return $ms . ' ms'; + } + + /** + * Formats the elapsed time since the start of the request as a string. + * + * @return string + */ + public static function timeSinceStartOfRequest() + { + return self::secondsToTimeString(microtime(TRUE) - self::$requestTime); + } + + /** + * Returns the resources (time, memory) of the request as a string. + * + * @return string + */ + public static function resourceUsage() + { + return sprintf( + 'Time: %s, Memory: %4.2fMb', + self::timeSinceStartOfRequest(), + memory_get_peak_usage(TRUE) / 1048576 + ); + } +} + +if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { + PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME_FLOAT']; +} + +else { + PHP_Timer::$requestTime = microtime(TRUE); +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package PHP_TokenStream + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +/** + * A caching factory for token stream objects. + * + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/php-token-stream/tree + * @since Class available since Release 1.0.0 + */ +class PHP_Token_Stream_CachingFactory +{ + /** + * @var array + */ + protected static $cache = array(); + + /** + * @param string $filename + * @return PHP_Token_Stream + */ + public static function get($filename) + { + if (!isset(self::$cache[$filename])) { + self::$cache[$filename] = new PHP_Token_Stream($filename); + } + + return self::$cache[$filename]; + } + + /** + * @param string $filename + */ + public static function clear($filename = NULL) + { + if (is_string($filename)) { + unset(self::$cache[$filename]); + } else { + self::$cache = array(); + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package PHP_TokenStream + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +/** + * A stream of PHP tokens. + * + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/php-token-stream/tree + * @since Class available since Release 1.0.0 + */ +class PHP_Token_Stream implements ArrayAccess, Countable, SeekableIterator +{ + /** + * @var array + */ + protected static $customTokens = array( + '(' => 'PHP_Token_OPEN_BRACKET', + ')' => 'PHP_Token_CLOSE_BRACKET', + '[' => 'PHP_Token_OPEN_SQUARE', + ']' => 'PHP_Token_CLOSE_SQUARE', + '{' => 'PHP_Token_OPEN_CURLY', + '}' => 'PHP_Token_CLOSE_CURLY', + ';' => 'PHP_Token_SEMICOLON', + '.' => 'PHP_Token_DOT', + ',' => 'PHP_Token_COMMA', + '=' => 'PHP_Token_EQUAL', + '<' => 'PHP_Token_LT', + '>' => 'PHP_Token_GT', + '+' => 'PHP_Token_PLUS', + '-' => 'PHP_Token_MINUS', + '*' => 'PHP_Token_MULT', + '/' => 'PHP_Token_DIV', + '?' => 'PHP_Token_QUESTION_MARK', + '!' => 'PHP_Token_EXCLAMATION_MARK', + ':' => 'PHP_Token_COLON', + '"' => 'PHP_Token_DOUBLE_QUOTES', + '@' => 'PHP_Token_AT', + '&' => 'PHP_Token_AMPERSAND', + '%' => 'PHP_Token_PERCENT', + '|' => 'PHP_Token_PIPE', + '$' => 'PHP_Token_DOLLAR', + '^' => 'PHP_Token_CARET', + '~' => 'PHP_Token_TILDE', + '`' => 'PHP_Token_BACKTICK' + ); + + /** + * @var string + */ + protected $filename; + + /** + * @var array + */ + protected $tokens = array(); + + /** + * @var integer + */ + protected $position = 0; + + /** + * @var array + */ + protected $linesOfCode = array('loc' => 0, 'cloc' => 0, 'ncloc' => 0); + + /** + * @var array + */ + protected $classes; + + /** + * @var array + */ + protected $functions; + + /** + * @var array + */ + protected $includes; + + /** + * @var array + */ + protected $interfaces; + + /** + * @var array + */ + protected $traits; + + /** + * @var array + */ + protected $lineToFunctionMap = array(); + + /** + * Constructor. + * + * @param string $sourceCode + */ + public function __construct($sourceCode) + { + if (is_file($sourceCode)) { + $this->filename = $sourceCode; + $sourceCode = file_get_contents($sourceCode); + } + + $this->scan($sourceCode); + } + + /** + * Destructor. + */ + public function __destruct() + { + $this->tokens = array(); + } + + /** + * @return string + */ + public function __toString() + { + $buffer = ''; + + foreach ($this as $token) { + $buffer .= $token; + } + + return $buffer; + } + + /** + * @return string + * @since Method available since Release 1.1.0 + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Scans the source for sequences of characters and converts them into a + * stream of tokens. + * + * @param string $sourceCode + */ + protected function scan($sourceCode) + { + $line = 1; + $tokens = token_get_all($sourceCode); + $numTokens = count($tokens); + + $lastNonWhitespaceTokenWasDoubleColon = FALSE; + + for ($i = 0; $i < $numTokens; ++$i) { + $token = $tokens[$i]; + unset($tokens[$i]); + + if (is_array($token)) { + $name = substr(token_name($token[0]), 2); + $text = $token[1]; + + if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') { + $name = 'CLASS_NAME_CONSTANT'; + } + + $tokenClass = 'PHP_Token_' . $name; + } else { + $text = $token; + $tokenClass = self::$customTokens[$token]; + } + + $this->tokens[] = new $tokenClass($text, $line, $this, $i); + $lines = substr_count($text, "\n"); + $line += $lines; + + if ($tokenClass == 'PHP_Token_HALT_COMPILER') { + break; + } + + else if ($tokenClass == 'PHP_Token_COMMENT' || + $tokenClass == 'PHP_Token_DOC_COMMENT') { + $this->linesOfCode['cloc'] += $lines + 1; + } + + if ($name == 'DOUBLE_COLON') { + $lastNonWhitespaceTokenWasDoubleColon = TRUE; + } + + else if ($name != 'WHITESPACE') { + $lastNonWhitespaceTokenWasDoubleColon = FALSE; + } + } + + $this->linesOfCode['loc'] = substr_count($sourceCode, "\n"); + $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] - + $this->linesOfCode['cloc']; + } + + /** + * @return integer + */ + public function count() + { + return count($this->tokens); + } + + /** + * @return PHP_Token[] + */ + public function tokens() + { + return $this->tokens; + } + + /** + * @return array + */ + public function getClasses() + { + if ($this->classes !== NULL) { + return $this->classes; + } + + $this->parse(); + + return $this->classes; + } + + /** + * @return array + */ + public function getFunctions() + { + if ($this->functions !== NULL) { + return $this->functions; + } + + $this->parse(); + + return $this->functions; + } + + /** + * @return array + */ + public function getInterfaces() + { + if ($this->interfaces !== NULL) { + return $this->interfaces; + } + + $this->parse(); + + return $this->interfaces; + } + + /** + * @return array + * @since Method available since Release 1.1.0 + */ + public function getTraits() + { + if ($this->traits !== NULL) { + return $this->traits; + } + + $this->parse(); + + return $this->traits; + } + + /** + * Gets the names of all files that have been included + * using include(), include_once(), require() or require_once(). + * + * Parameter $categorize set to TRUE causing this function to return a + * multi-dimensional array with categories in the keys of the first dimension + * and constants and their values in the second dimension. + * + * Parameter $category allow to filter following specific inclusion type + * + * @param bool $categorize OPTIONAL + * @param string $category OPTIONAL Either 'require_once', 'require', + * 'include_once', 'include'. + * @return array + * @since Method available since Release 1.1.0 + */ + public function getIncludes($categorize = FALSE, $category = NULL) + { + if ($this->includes === NULL) { + $this->includes = array( + 'require_once' => array(), + 'require' => array(), + 'include_once' => array(), + 'include' => array() + ); + + foreach ($this->tokens as $token) { + switch (get_class($token)) { + case 'PHP_Token_REQUIRE_ONCE': + case 'PHP_Token_REQUIRE': + case 'PHP_Token_INCLUDE_ONCE': + case 'PHP_Token_INCLUDE': { + $this->includes[$token->getType()][] = $token->getName(); + } + break; + } + } + } + + if (isset($this->includes[$category])) { + $includes = $this->includes[$category]; + } + + else if ($categorize === FALSE) { + $includes = array_merge( + $this->includes['require_once'], + $this->includes['require'], + $this->includes['include_once'], + $this->includes['include'] + ); + } else { + $includes = $this->includes; + } + + return $includes; + } + + /** + * Returns the name of the function or method a line belongs to. + * + * @return string or null if the line is not in a function or method + * @since Method available since Release 1.2.0 + */ + public function getFunctionForLine($line) + { + $this->parse(); + + if (isset($this->lineToFunctionMap[$line])) { + return $this->lineToFunctionMap[$line]; + } + } + + protected function parse() + { + $this->interfaces = array(); + $this->classes = array(); + $this->traits = array(); + $this->functions = array(); + $class = FALSE; + $classEndLine = FALSE; + $trait = FALSE; + $traitEndLine = FALSE; + $interface = FALSE; + $interfaceEndLine = FALSE; + + foreach ($this->tokens as $token) { + switch (get_class($token)) { + case 'PHP_Token_HALT_COMPILER': { + return; + } + break; + + case 'PHP_Token_INTERFACE': { + $interface = $token->getName(); + $interfaceEndLine = $token->getEndLine(); + + $this->interfaces[$interface] = array( + 'methods' => array(), + 'parent' => $token->getParent(), + 'keywords' => $token->getKeywords(), + 'docblock' => $token->getDocblock(), + 'startLine' => $token->getLine(), + 'endLine' => $interfaceEndLine, + 'package' => $token->getPackage(), + 'file' => $this->filename + ); + } + break; + + case 'PHP_Token_CLASS': + case 'PHP_Token_TRAIT': { + $tmp = array( + 'methods' => array(), + 'parent' => $token->getParent(), + 'interfaces'=> $token->getInterfaces(), + 'keywords' => $token->getKeywords(), + 'docblock' => $token->getDocblock(), + 'startLine' => $token->getLine(), + 'endLine' => $token->getEndLine(), + 'package' => $token->getPackage(), + 'file' => $this->filename + ); + + if ($token instanceof PHP_Token_CLASS) { + $class = $token->getName(); + $classEndLine = $token->getEndLine(); + $this->classes[$class] = $tmp; + } else { + $trait = $token->getName(); + $traitEndLine = $token->getEndLine(); + $this->traits[$trait] = $tmp; + } + } + break; + + case 'PHP_Token_FUNCTION': { + $name = $token->getName(); + $tmp = array( + 'docblock' => $token->getDocblock(), + 'keywords' => $token->getKeywords(), + 'visibility'=> $token->getVisibility(), + 'signature' => $token->getSignature(), + 'startLine' => $token->getLine(), + 'endLine' => $token->getEndLine(), + 'ccn' => $token->getCCN(), + 'file' => $this->filename + ); + + if ($class === FALSE && + $trait === FALSE && + $interface === FALSE) { + $this->functions[$name] = $tmp; + + $this->addFunctionToMap( + $name, $tmp['startLine'], $tmp['endLine'] + ); + } + + else if ($class !== FALSE) { + $this->classes[$class]['methods'][$name] = $tmp; + + $this->addFunctionToMap( + $class . '::' . $name, + $tmp['startLine'], + $tmp['endLine'] + ); + } + + else if ($trait !== FALSE) { + $this->traits[$trait]['methods'][$name] = $tmp; + + $this->addFunctionToMap( + $trait . '::' . $name, + $tmp['startLine'], + $tmp['endLine'] + ); + } + + else { + $this->interfaces[$interface]['methods'][$name] = $tmp; + } + } + break; + + case 'PHP_Token_CLOSE_CURLY': { + if ($classEndLine !== FALSE && + $classEndLine == $token->getLine()) { + $class = FALSE; + $classEndLine = FALSE; + } + + else if ($traitEndLine !== FALSE && + $traitEndLine == $token->getLine()) { + $trait = FALSE; + $traitEndLine = FALSE; + } + + else if ($interfaceEndLine !== FALSE && + $interfaceEndLine == $token->getLine()) { + $interface = FALSE; + $interfaceEndLine = FALSE; + } + } + break; + } + } + } + + /** + * @return array + */ + public function getLinesOfCode() + { + return $this->linesOfCode; + } + + /** + */ + public function rewind() + { + $this->position = 0; + } + + /** + * @return boolean + */ + public function valid() + { + return isset($this->tokens[$this->position]); + } + + /** + * @return integer + */ + public function key() + { + return $this->position; + } + + /** + * @return PHP_Token + */ + public function current() + { + return $this->tokens[$this->position]; + } + + /** + */ + public function next() + { + $this->position++; + } + + /** + * @param mixed $offset + */ + public function offsetExists($offset) + { + return isset($this->tokens[$offset]); + } + + /** + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->tokens[$offset]; + } + + /** + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->tokens[$offset] = $value; + } + + /** + * @param mixed $offset + */ + public function offsetUnset($offset) + { + unset($this->tokens[$offset]); + } + + /** + * Seek to an absolute position. + * + * @param integer $position + * @throws OutOfBoundsException + */ + public function seek($position) + { + $this->position = $position; + + if (!$this->valid()) { + throw new OutOfBoundsException('Invalid seek position'); + } + } + + private function addFunctionToMap($name, $startLine, $endLine) + { + for ($line = $startLine; $line <= $endLine; $line++) { + $this->lineToFunctionMap[$line] = $name; + } + } +} +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may 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. + * + * @package PHP_TokenStream + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @since File available since Release 1.0.0 + */ + +/** + * A PHP token. + * + * @author Sebastian Bergmann + * @copyright 2009-2013 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @version Release: @package_version@ + * @link http://github.com/sebastianbergmann/php-token-stream/tree + * @since Class available since Release 1.0.0 + */ +abstract class PHP_Token +{ + /** + * @var string + */ + protected $text; + + /** + * @var integer + */ + protected $line; + + /** + * @var PHP_Token_Stream + */ + protected $tokenStream; + + /** + * @var integer + */ + protected $id; + + /** + * Constructor. + * + * @param string $text + * @param integer $line + * @param PHP_Token_Stream $tokenStream + * @param integer $id + */ + public function __construct($text, $line, PHP_Token_Stream $tokenStream, $id) + { + $this->text = $text; + $this->line = $line; + $this->tokenStream = $tokenStream; + $this->id = $id; + } + + /** + * @return string + */ + public function __toString() + { + return $this->text; + } + + /** + * @return integer + */ + public function getLine() + { + return $this->line; + } +} + +abstract class PHP_TokenWithScope extends PHP_Token +{ + protected $endTokenId; + + /** + * Get the docblock for this token + * + * This method will fetch the docblock belonging to the current token. The + * docblock must be placed on the line directly above the token to be + * recognized. + * + * @return string|null Returns the docblock as a string if found + */ + public function getDocblock() + { + $tokens = $this->tokenStream->tokens(); + $currentLineNumber = $tokens[$this->id]->getLine(); + $prevLineNumber = $currentLineNumber - 1; + + for ($i = $this->id - 1; $i; $i--) { + if (!isset($tokens[$i])) { + return; + } + + if ($tokens[$i] instanceof PHP_Token_FUNCTION || + $tokens[$i] instanceof PHP_Token_CLASS || + $tokens[$i] instanceof PHP_Token_TRAIT) { + // Some other trait, class or function, no docblock can be + // used for the current token + break; + } + + $line = $tokens[$i]->getLine(); + + if ($line == $currentLineNumber || + ($line == $prevLineNumber && + $tokens[$i] instanceof PHP_Token_WHITESPACE)) { + continue; + } + + if ($line < $currentLineNumber && + !$tokens[$i] instanceof PHP_Token_DOC_COMMENT) { + break; + } + + return (string)$tokens[$i]; + } + } + + public function getEndTokenId() + { + $block = 0; + $i = $this->id; + $tokens = $this->tokenStream->tokens(); + + while ($this->endTokenId === NULL && isset($tokens[$i])) { + if ($tokens[$i] instanceof PHP_Token_OPEN_CURLY || + $tokens[$i] instanceof PHP_Token_CURLY_OPEN) { + $block++; + } + + else if ($tokens[$i] instanceof PHP_Token_CLOSE_CURLY) { + $block--; + + if ($block === 0) { + $this->endTokenId = $i; + } + } + + else if (($this instanceof PHP_Token_FUNCTION || + $this instanceof PHP_Token_NAMESPACE) && + $tokens[$i] instanceof PHP_Token_SEMICOLON) { + if ($block === 0) { + $this->endTokenId = $i; + } + } + + $i++; + } + + if ($this->endTokenId === NULL) { + $this->endTokenId = $this->id; + } + + return $this->endTokenId; + } + + public function getEndLine() + { + return $this->tokenStream[$this->getEndTokenId()]->getLine(); + } + +} + +abstract class PHP_TokenWithScopeAndVisibility extends PHP_TokenWithScope { + + public function getVisibility() + { + $tokens = $this->tokenStream->tokens(); + + for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { + if (isset($tokens[$i]) && + ($tokens[$i] instanceof PHP_Token_PRIVATE || + $tokens[$i] instanceof PHP_Token_PROTECTED || + $tokens[$i] instanceof PHP_Token_PUBLIC)) { + return strtolower( + str_replace('PHP_Token_', '', get_class($tokens[$i])) + ); + } + if (isset($tokens[$i]) && + !($tokens[$i] instanceof PHP_Token_STATIC || + $tokens[$i] instanceof PHP_Token_FINAL || + $tokens[$i] instanceof PHP_Token_ABSTRACT)) { + // no keywords; stop visibility search + break; + } + } + } + + public function getKeywords() + { + $keywords = array(); + $tokens = $this->tokenStream->tokens(); + + for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { + if (isset($tokens[$i]) && + ($tokens[$i] instanceof PHP_Token_PRIVATE || + $tokens[$i] instanceof PHP_Token_PROTECTED || + $tokens[$i] instanceof PHP_Token_PUBLIC)) { + continue; + } + + if (isset($tokens[$i]) && + ($tokens[$i] instanceof PHP_Token_STATIC || + $tokens[$i] instanceof PHP_Token_FINAL || + $tokens[$i] instanceof PHP_Token_ABSTRACT)) { + $keywords[] = strtolower( + str_replace('PHP_Token_', '', get_class($tokens[$i])) + ); + } + } + + return implode(',', $keywords); + } + +} + +abstract class PHP_Token_Includes extends PHP_Token +{ + protected $name; + protected $type; + + public function getName() + { + if ($this->name !== NULL) { + return $this->name; + } + + $tokens = $this->tokenStream->tokens(); + + if ($tokens[$this->id+2] instanceof PHP_Token_CONSTANT_ENCAPSED_STRING) { + $this->name = trim($tokens[$this->id+2], "'\""); + $this->type = strtolower( + str_replace('PHP_Token_', '', get_class($tokens[$this->id])) + ); + } + + return $this->name; + } + + public function getType() + { + $this->getName(); + return $this->type; + } +} + +class PHP_Token_REQUIRE_ONCE extends PHP_Token_Includes {} +class PHP_Token_REQUIRE extends PHP_Token_Includes {} +class PHP_Token_EVAL extends PHP_Token {} +class PHP_Token_INCLUDE_ONCE extends PHP_Token_Includes {} +class PHP_Token_INCLUDE extends PHP_Token_Includes {} +class PHP_Token_LOGICAL_OR extends PHP_Token {} +class PHP_Token_LOGICAL_XOR extends PHP_Token {} +class PHP_Token_LOGICAL_AND extends PHP_Token {} +class PHP_Token_PRINT extends PHP_Token {} +class PHP_Token_SR_EQUAL extends PHP_Token {} +class PHP_Token_SL_EQUAL extends PHP_Token {} +class PHP_Token_XOR_EQUAL extends PHP_Token {} +class PHP_Token_OR_EQUAL extends PHP_Token {} +class PHP_Token_AND_EQUAL extends PHP_Token {} +class PHP_Token_MOD_EQUAL extends PHP_Token {} +class PHP_Token_CONCAT_EQUAL extends PHP_Token {} +class PHP_Token_DIV_EQUAL extends PHP_Token {} +class PHP_Token_MUL_EQUAL extends PHP_Token {} +class PHP_Token_MINUS_EQUAL extends PHP_Token {} +class PHP_Token_PLUS_EQUAL extends PHP_Token {} +class PHP_Token_BOOLEAN_OR extends PHP_Token {} +class PHP_Token_BOOLEAN_AND extends PHP_Token {} +class PHP_Token_IS_NOT_IDENTICAL extends PHP_Token {} +class PHP_Token_IS_IDENTICAL extends PHP_Token {} +class PHP_Token_IS_NOT_EQUAL extends PHP_Token {} +class PHP_Token_IS_EQUAL extends PHP_Token {} +class PHP_Token_IS_GREATER_OR_EQUAL extends PHP_Token {} +class PHP_Token_IS_SMALLER_OR_EQUAL extends PHP_Token {} +class PHP_Token_SR extends PHP_Token {} +class PHP_Token_SL extends PHP_Token {} +class PHP_Token_INSTANCEOF extends PHP_Token {} +class PHP_Token_UNSET_CAST extends PHP_Token {} +class PHP_Token_BOOL_CAST extends PHP_Token {} +class PHP_Token_OBJECT_CAST extends PHP_Token {} +class PHP_Token_ARRAY_CAST extends PHP_Token {} +class PHP_Token_STRING_CAST extends PHP_Token {} +class PHP_Token_DOUBLE_CAST extends PHP_Token {} +class PHP_Token_INT_CAST extends PHP_Token {} +class PHP_Token_DEC extends PHP_Token {} +class PHP_Token_INC extends PHP_Token {} +class PHP_Token_CLONE extends PHP_Token {} +class PHP_Token_NEW extends PHP_Token {} +class PHP_Token_EXIT extends PHP_Token {} +class PHP_Token_IF extends PHP_Token {} +class PHP_Token_ELSEIF extends PHP_Token {} +class PHP_Token_ELSE extends PHP_Token {} +class PHP_Token_ENDIF extends PHP_Token {} +class PHP_Token_LNUMBER extends PHP_Token {} +class PHP_Token_DNUMBER extends PHP_Token {} +class PHP_Token_STRING extends PHP_Token {} +class PHP_Token_STRING_VARNAME extends PHP_Token {} +class PHP_Token_VARIABLE extends PHP_Token {} +class PHP_Token_NUM_STRING extends PHP_Token {} +class PHP_Token_INLINE_HTML extends PHP_Token {} +class PHP_Token_CHARACTER extends PHP_Token {} +class PHP_Token_BAD_CHARACTER extends PHP_Token {} +class PHP_Token_ENCAPSED_AND_WHITESPACE extends PHP_Token {} +class PHP_Token_CONSTANT_ENCAPSED_STRING extends PHP_Token {} +class PHP_Token_ECHO extends PHP_Token {} +class PHP_Token_DO extends PHP_Token {} +class PHP_Token_WHILE extends PHP_Token {} +class PHP_Token_ENDWHILE extends PHP_Token {} +class PHP_Token_FOR extends PHP_Token {} +class PHP_Token_ENDFOR extends PHP_Token {} +class PHP_Token_FOREACH extends PHP_Token {} +class PHP_Token_ENDFOREACH extends PHP_Token {} +class PHP_Token_DECLARE extends PHP_Token {} +class PHP_Token_ENDDECLARE extends PHP_Token {} +class PHP_Token_AS extends PHP_Token {} +class PHP_Token_SWITCH extends PHP_Token {} +class PHP_Token_ENDSWITCH extends PHP_Token {} +class PHP_Token_CASE extends PHP_Token {} +class PHP_Token_DEFAULT extends PHP_Token {} +class PHP_Token_BREAK extends PHP_Token {} +class PHP_Token_CONTINUE extends PHP_Token {} +class PHP_Token_GOTO extends PHP_Token {} +class PHP_Token_CALLABLE extends PHP_Token {} +class PHP_Token_INSTEADOF extends PHP_Token {} + +class PHP_Token_FUNCTION extends PHP_TokenWithScopeAndVisibility +{ + protected $arguments; + protected $ccn; + protected $name; + protected $signature; + + public function getArguments() + { + if ($this->arguments !== NULL) { + return $this->arguments; + } + + $this->arguments = array(); + $tokens = $this->tokenStream->tokens(); + $typeHint = NULL; + + // Search for first token inside brackets + $i = $this->id + 2; + while (!$tokens[$i-1] instanceof PHP_Token_OPEN_BRACKET) { + $i++; + } + + while (!$tokens[$i] instanceof PHP_Token_CLOSE_BRACKET) { + if ($tokens[$i] instanceof PHP_Token_STRING) { + $typeHint = (string)$tokens[$i]; + } + + else if ($tokens[$i] instanceof PHP_Token_VARIABLE) { + $this->arguments[(string)$tokens[$i]] = $typeHint; + $typeHint = NULL; + } + + $i++; + } + + return $this->arguments; + } + + public function getName() + { + if ($this->name !== NULL) { + return $this->name; + } + + $tokens = $this->tokenStream->tokens(); + + for ($i = $this->id + 1; $i < count($tokens); $i++) { + if ($tokens[$i] instanceof PHP_Token_STRING) { + $this->name = (string)$tokens[$i]; + break; + } + + else if ($tokens[$i] instanceof PHP_Token_AMPERSAND && + $tokens[$i+1] instanceof PHP_Token_STRING) { + $this->name = (string)$tokens[$i+1]; + break; + } + + else if ($tokens[$i] instanceof PHP_Token_OPEN_BRACKET) { + $this->name = 'anonymous function'; + break; + } + } + + if ($this->name != 'anonymous function') { + for ($i = $this->id; $i; --$i) { + if ($tokens[$i] instanceof PHP_Token_NAMESPACE) { + $this->name = $tokens[$i]->getName() . '\\' . $this->name; + break; + } + + if ($tokens[$i] instanceof PHP_Token_INTERFACE) { + break; + } + } + } + + return $this->name; + } + + public function getCCN() + { + if ($this->ccn !== NULL) { + return $this->ccn; + } + + $this->ccn = 1; + $end = $this->getEndTokenId(); + $tokens = $this->tokenStream->tokens(); + + for ($i = $this->id; $i <= $end; $i++) { + switch (get_class($tokens[$i])) { + case 'PHP_Token_IF': + case 'PHP_Token_ELSEIF': + case 'PHP_Token_FOR': + case 'PHP_Token_FOREACH': + case 'PHP_Token_WHILE': + case 'PHP_Token_CASE': + case 'PHP_Token_CATCH': + case 'PHP_Token_BOOLEAN_AND': + case 'PHP_Token_LOGICAL_AND': + case 'PHP_Token_BOOLEAN_OR': + case 'PHP_Token_LOGICAL_OR': + case 'PHP_Token_QUESTION_MARK': { + $this->ccn++; + } + break; + } + } + + return $this->ccn; + } + + public function getSignature() + { + if ($this->signature !== NULL) { + return $this->signature; + } + + if ($this->getName() == 'anonymous function') { + $this->signature = 'anonymous function'; + $i = $this->id + 1; + } else { + $this->signature = ''; + $i = $this->id + 2; + } + + $tokens = $this->tokenStream->tokens(); + + while (isset($tokens[$i]) && + !$tokens[$i] instanceof PHP_Token_OPEN_CURLY && + !$tokens[$i] instanceof PHP_Token_SEMICOLON) { + $this->signature .= $tokens[$i++]; + } + + $this->signature = trim($this->signature); + + return $this->signature; + } +} + +class PHP_Token_CONST extends PHP_Token {} +class PHP_Token_RETURN extends PHP_Token {} +class PHP_Token_YIELD extends PHP_Token {} +class PHP_Token_TRY extends PHP_Token {} +class PHP_Token_CATCH extends PHP_Token {} +class PHP_Token_FINALLY extends PHP_Token {} +class PHP_Token_THROW extends PHP_Token {} +class PHP_Token_USE extends PHP_Token {} +class PHP_Token_GLOBAL extends PHP_Token {} +class PHP_Token_PUBLIC extends PHP_Token {} +class PHP_Token_PROTECTED extends PHP_Token {} +class PHP_Token_PRIVATE extends PHP_Token {} +class PHP_Token_FINAL extends PHP_Token {} +class PHP_Token_ABSTRACT extends PHP_Token {} +class PHP_Token_STATIC extends PHP_Token {} +class PHP_Token_VAR extends PHP_Token {} +class PHP_Token_UNSET extends PHP_Token {} +class PHP_Token_ISSET extends PHP_Token {} +class PHP_Token_EMPTY extends PHP_Token {} +class PHP_Token_HALT_COMPILER extends PHP_Token {} + +class PHP_Token_INTERFACE extends PHP_TokenWithScopeAndVisibility +{ + protected $interfaces; + + public function getName() + { + return (string)$this->tokenStream[$this->id + 2]; + } + + public function hasParent() + { + return $this->tokenStream[$this->id + 4] instanceof PHP_Token_EXTENDS; + } + + public function getPackage() + { + $className = $this->getName(); + $docComment = $this->getDocblock(); + + $result = array( + 'namespace' => '', + 'fullPackage' => '', + 'category' => '', + 'package' => '', + 'subpackage' => '' + ); + + for ($i = $this->id; $i; --$i) { + if ($this->tokenStream[$i] instanceof PHP_Token_NAMESPACE) { + $result['namespace'] = $this->tokenStream[$i]->getName(); + break; + } + } + + if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) { + $result['category'] = $matches[1]; + } + + if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) { + $result['package'] = $matches[1]; + $result['fullPackage'] = $matches[1]; + } + + if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) { + $result['subpackage'] = $matches[1]; + $result['fullPackage'] .= '.' . $matches[1]; + } + + if (empty($result['fullPackage'])) { + $result['fullPackage'] = $this->arrayToName( + explode('_', str_replace('\\', '_', $className)), '.' + ); + } + + return $result; + } + + protected function arrayToName(array $parts, $join = '\\') + { + $result = ''; + + if (count($parts) > 1) { + array_pop($parts); + + $result = join($join, $parts); + } + + return $result; + } + + public function getParent() + { + if (!$this->hasParent()) { + return FALSE; + } + + $i = $this->id + 6; + $tokens = $this->tokenStream->tokens(); + $className = (string)$tokens[$i]; + + while (isset($tokens[$i+1]) && + !$tokens[$i+1] instanceof PHP_Token_WHITESPACE) { + $className .= (string)$tokens[++$i]; + } + + return $className; + } + + public function hasInterfaces() + { + return (isset($this->tokenStream[$this->id + 4]) && + $this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) || + (isset($this->tokenStream[$this->id + 8]) && + $this->tokenStream[$this->id + 8] instanceof PHP_Token_IMPLEMENTS); + } + + public function getInterfaces() + { + if ($this->interfaces !== NULL) { + return $this->interfaces; + } + + if (!$this->hasInterfaces()) { + return ($this->interfaces = FALSE); + } + + if ($this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) { + $i = $this->id + 3; + } else { + $i = $this->id + 7; + } + + $tokens = $this->tokenStream->tokens(); + + while (!$tokens[$i+1] instanceof PHP_Token_OPEN_CURLY) { + $i++; + + if ($tokens[$i] instanceof PHP_Token_STRING) { + $this->interfaces[] = (string)$tokens[$i]; + } + } + + return $this->interfaces; + } +} + +class PHP_Token_CLASS extends PHP_Token_INTERFACE {} +class PHP_Token_CLASS_NAME_CONSTANT extends PHP_Token {} +class PHP_Token_TRAIT extends PHP_Token_INTERFACE {} +class PHP_Token_EXTENDS extends PHP_Token {} +class PHP_Token_IMPLEMENTS extends PHP_Token {} +class PHP_Token_OBJECT_OPERATOR extends PHP_Token {} +class PHP_Token_DOUBLE_ARROW extends PHP_Token {} +class PHP_Token_LIST extends PHP_Token {} +class PHP_Token_ARRAY extends PHP_Token {} +class PHP_Token_CLASS_C extends PHP_Token {} +class PHP_Token_TRAIT_C extends PHP_Token {} +class PHP_Token_METHOD_C extends PHP_Token {} +class PHP_Token_FUNC_C extends PHP_Token {} +class PHP_Token_LINE extends PHP_Token {} +class PHP_Token_FILE extends PHP_Token {} +class PHP_Token_COMMENT extends PHP_Token {} +class PHP_Token_DOC_COMMENT extends PHP_Token {} +class PHP_Token_OPEN_TAG extends PHP_Token {} +class PHP_Token_OPEN_TAG_WITH_ECHO extends PHP_Token {} +class PHP_Token_CLOSE_TAG extends PHP_Token {} +class PHP_Token_WHITESPACE extends PHP_Token {} +class PHP_Token_START_HEREDOC extends PHP_Token {} +class PHP_Token_END_HEREDOC extends PHP_Token {} +class PHP_Token_DOLLAR_OPEN_CURLY_BRACES extends PHP_Token {} +class PHP_Token_CURLY_OPEN extends PHP_Token {} +class PHP_Token_PAAMAYIM_NEKUDOTAYIM extends PHP_Token {} + +class PHP_Token_NAMESPACE extends PHP_TokenWithScope +{ + public function getName() + { + $tokens = $this->tokenStream->tokens(); + $namespace = (string)$tokens[$this->id+2]; + + for ($i = $this->id + 3; ; $i += 2) { + if (isset($tokens[$i]) && + $tokens[$i] instanceof PHP_Token_NS_SEPARATOR) { + $namespace .= '\\' . $tokens[$i+1]; + } else { + break; + } + } + + return $namespace; + } +} + +class PHP_Token_NS_C extends PHP_Token {} +class PHP_Token_DIR extends PHP_Token {} +class PHP_Token_NS_SEPARATOR extends PHP_Token {} +class PHP_Token_DOUBLE_COLON extends PHP_Token {} +class PHP_Token_OPEN_BRACKET extends PHP_Token {} +class PHP_Token_CLOSE_BRACKET extends PHP_Token {} +class PHP_Token_OPEN_SQUARE extends PHP_Token {} +class PHP_Token_CLOSE_SQUARE extends PHP_Token {} +class PHP_Token_OPEN_CURLY extends PHP_Token {} +class PHP_Token_CLOSE_CURLY extends PHP_Token {} +class PHP_Token_SEMICOLON extends PHP_Token {} +class PHP_Token_DOT extends PHP_Token {} +class PHP_Token_COMMA extends PHP_Token {} +class PHP_Token_EQUAL extends PHP_Token {} +class PHP_Token_LT extends PHP_Token {} +class PHP_Token_GT extends PHP_Token {} +class PHP_Token_PLUS extends PHP_Token {} +class PHP_Token_MINUS extends PHP_Token {} +class PHP_Token_MULT extends PHP_Token {} +class PHP_Token_DIV extends PHP_Token {} +class PHP_Token_QUESTION_MARK extends PHP_Token {} +class PHP_Token_EXCLAMATION_MARK extends PHP_Token {} +class PHP_Token_COLON extends PHP_Token {} +class PHP_Token_DOUBLE_QUOTES extends PHP_Token {} +class PHP_Token_AT extends PHP_Token {} +class PHP_Token_AMPERSAND extends PHP_Token {} +class PHP_Token_PERCENT extends PHP_Token {} +class PHP_Token_PIPE extends PHP_Token {} +class PHP_Token_DOLLAR extends PHP_Token {} +class PHP_Token_CARET extends PHP_Token {} +class PHP_Token_TILDE extends PHP_Token {} +class PHP_Token_BACKTICK extends PHP_Token {} +á3rMEŠ»Ž‚2Xjªx.Ô)ßGBMB \ No newline at end of file diff --git a/search.sh b/search.sh new file mode 100755 index 00000000..1c08ee18 --- /dev/null +++ b/search.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Execute this script in the root directory of the project +# Ex: /search.sh bl-kernel/dbpages.class.php + +CLASS_FILENAME=$1 + +echo "Class name" +CLASS_NAME=`grep "class " $CLASS_FILENAME | awk '{print $2}' | xargs` + +echo "Search Object" +OBJECT_NAME=`find -name "*.php" -exec grep "new $CLASS_NAME" {} \; | awk '{print $1}' | xargs` + +echo "List of methods" +grep "public function" $CLASS_FILENAME | awk '{print $3}' | tr "(" " " | awk '{print $1}' > /tmp/methods.list + +while read -r METHOD +do + echo "" + echo "-------------------------------------" + echo "Searching for $CLASS_NAME->$METHOD(" + echo "-------------------------------------" + + grep -r -w "$CLASS_NAME->$METHOD(" * + let STATUS=$? + if [ $STATUS -eq 1 ] + then + echo "Searching for this->$METHOD( inside $CLASS_FILENAME" + echo "-------------------------------------" + + grep -r -w "this->$METHOD(" $CLASS_FILENAME + let STATUS=$? + if [ $STATUS -eq 1 ] + then + echo "Not found" + fi + fi + + echo "-------------------------------------" + +done < /tmp/methods.list \ No newline at end of file