Explorar o código

vendor加入 配置加入后台

chengwl %!s(int64=5) %!d(string=hai) anos
pai
achega
5e71a5a89d
Modificáronse 100 ficheiros con 9245 adicións e 176 borrados
  1. 0 2
      .gitignore
  2. 26 26
      appadmin/config/fecshop_local_services/FecshopLang.php
  3. 29 29
      appfront/config/fecshop_local_services/FecshopLang.php
  4. 1 1
      appfront/config/fecshop_local_services/Store.php
  5. 12 5
      appfront/local/local_modules/Cms/block/home/Index.php
  6. 29 29
      apphtml5/config/fecshop_local_services/FecshopLang.php
  7. 1 1
      apphtml5/config/fecshop_local_services/Store.php
  8. 29 29
      appserver/config/fecshop_local_services/FecshopLang.php
  9. 1 0
      common/config/YiiRewriteMap.php
  10. 7 0
      common/config/fecshop_local.php
  11. 29 29
      common/config/fecshop_local_services/Email.php
  12. 1 1
      common/config/fecshop_local_services/FecshopLang.php
  13. 24 24
      common/config/fecshop_local_services/Page.php
  14. 6 0
      common/config/fecshop_local_services/StoreBaseConfig.php
  15. 72 0
      common/local/local_components/Store.php
  16. 28 0
      common/local/local_models/mysqldb/StoreBaseConfig.php
  17. 197 0
      common/local/local_services/StoreBaseConfig.php
  18. 7 0
      vendor/autoload.php
  19. 4 0
      vendor/behat/gherkin/.gitignore
  20. 33 0
      vendor/behat/gherkin/.travis.yml
  21. 388 0
      vendor/behat/gherkin/CHANGES.md
  22. 33 0
      vendor/behat/gherkin/CONTRIBUTING.md
  23. 22 0
      vendor/behat/gherkin/LICENSE
  24. 68 0
      vendor/behat/gherkin/README.md
  25. 70 0
      vendor/behat/gherkin/bin/update_i18n
  26. 47 0
      vendor/behat/gherkin/composer.json
  27. 1197 0
      vendor/behat/gherkin/i18n.php
  28. 3 0
      vendor/behat/gherkin/libpath.php
  29. 71 0
      vendor/behat/gherkin/package.xml.tpl
  30. 14 0
      vendor/behat/gherkin/phpdoc.ini.dist
  31. 25 0
      vendor/behat/gherkin/phpunit.xml.dist
  32. 48 0
      vendor/behat/gherkin/src/Behat/Gherkin/Cache/CacheInterface.php
  33. 109 0
      vendor/behat/gherkin/src/Behat/Gherkin/Cache/FileCache.php
  34. 66 0
      vendor/behat/gherkin/src/Behat/Gherkin/Cache/MemoryCache.php
  35. 22 0
      vendor/behat/gherkin/src/Behat/Gherkin/Exception/CacheException.php
  36. 15 0
      vendor/behat/gherkin/src/Behat/Gherkin/Exception/Exception.php
  37. 17 0
      vendor/behat/gherkin/src/Behat/Gherkin/Exception/LexerException.php
  38. 17 0
      vendor/behat/gherkin/src/Behat/Gherkin/Exception/NodeException.php
  39. 17 0
      vendor/behat/gherkin/src/Behat/Gherkin/Exception/ParserException.php
  40. 52 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilter.php
  41. 32 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilterInterface.php
  42. 39 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/FeatureFilterInterface.php
  43. 30 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/FilterInterface.php
  44. 122 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineFilter.php
  45. 134 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineRangeFilter.php
  46. 68 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/NameFilter.php
  47. 61 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/NarrativeFilter.php
  48. 72 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/PathsFilter.php
  49. 63 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/RoleFilter.php
  50. 56 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/SimpleFilter.php
  51. 90 0
      vendor/behat/gherkin/src/Behat/Gherkin/Filter/TagFilter.php
  52. 142 0
      vendor/behat/gherkin/src/Behat/Gherkin/Gherkin.php
  53. 200 0
      vendor/behat/gherkin/src/Behat/Gherkin/Keywords/ArrayKeywords.php
  54. 31 0
      vendor/behat/gherkin/src/Behat/Gherkin/Keywords/CachedArrayKeywords.php
  55. 121 0
      vendor/behat/gherkin/src/Behat/Gherkin/Keywords/CucumberKeywords.php
  56. 365 0
      vendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsDumper.php
  57. 103 0
      vendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsInterface.php
  58. 614 0
      vendor/behat/gherkin/src/Behat/Gherkin/Lexer.php
  59. 72 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/AbstractFileLoader.php
  60. 269 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/ArrayLoader.php
  61. 80 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/DirectoryLoader.php
  62. 26 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/FileLoaderInterface.php
  63. 102 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/GherkinFileLoader.php
  64. 39 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/LoaderInterface.php
  65. 73 0
      vendor/behat/gherkin/src/Behat/Gherkin/Loader/YamlFileLoader.php
  66. 20 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ArgumentInterface.php
  67. 112 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/BackgroundNode.php
  68. 274 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleNode.php
  69. 57 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleTableNode.php
  70. 243 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/FeatureNode.php
  71. 33 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/KeywordNodeInterface.php
  72. 33 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/NodeInterface.php
  73. 218 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/OutlineNode.php
  74. 90 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/PyStringNode.php
  75. 20 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioInterface.php
  76. 20 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioLikeInterface.php
  77. 150 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioNode.php
  78. 33 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/StepContainerInterface.php
  79. 152 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/StepNode.php
  80. 347 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/TableNode.php
  81. 42 0
      vendor/behat/gherkin/src/Behat/Gherkin/Node/TaggedNodeInterface.php
  82. 699 0
      vendor/behat/gherkin/src/Behat/Gherkin/Parser.php
  83. 75 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Cache/FileCacheTest.php
  84. 51 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Cache/MemoryCacheTest.php
  85. 64 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/FilterTest.php
  86. 0 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full/file1
  87. 0 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full/file2
  88. 0 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full_path/file1
  89. 103 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/LineFilterTest.php
  90. 101 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php
  91. 79 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/NameFilterTest.php
  92. 34 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/NarrativeFilterTest.php
  93. 61 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/PathsFilterTest.php
  94. 76 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/RoleFilterTest.php
  95. 144 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Filter/TagFilterTest.php
  96. 0 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/directories/phps/some_file.php
  97. 29 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/addition.yml
  98. 18 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/background.yml
  99. 26 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/background_title.yml
  100. 0 0
      vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/big_pystring.yml

+ 0 - 2
.gitignore

@@ -23,8 +23,6 @@ nbproject
 Thumbs.db
 Thumbs.db
 
 
 # composer vendor dir
 # composer vendor dir
-/vendor
-
 /packages
 /packages
 
 
 # composer itself is not needed
 # composer itself is not needed

+ 26 - 26
appadmin/config/fecshop_local_services/FecshopLang.php

@@ -22,31 +22,31 @@ return [
                 'name'                   => '中文',
                 'name'                   => '中文',
             ],
             ],
          ],
          ],
-        'allLangCode' => [
-            // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
-            // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
-            'en_US' => [
-                'code'                    => 'en',
-            ],
-            'zh_CN' => [
-                'code'                    => 'zh',
-            ],
-            'fr_FR' => [
-                'code' 					=> 'fr',
-            ],
-            'de_DE' => [
-                'code' 					=> 'de',
-            ],
-            'es_ES' => [
-                'code' 					=> 'es',
-            ],
-            'ru_RU' => [
-                'code' 					=> 'ru',
-            ],
-            'pt_PT' => [
-                'code' 					=> 'pt',
-            ],
-        ],
-        'defaultLangCode' => 'en',
+        // 'allLangCode' => [
+        //     // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
+        //     // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
+        //     'en_US' => [
+        //         'code'                    => 'en',
+        //     ],
+        //     'zh_CN' => [
+        //         'code'                    => 'zh',
+        //     ],
+        //     'fr_FR' => [
+        //         'code' 					=> 'fr',
+        //     ],
+        //     'de_DE' => [
+        //         'code' 					=> 'de',
+        //     ],
+        //     'es_ES' => [
+        //         'code' 					=> 'es',
+        //     ],
+        //     'ru_RU' => [
+        //         'code' 					=> 'ru',
+        //     ],
+        //     'pt_PT' => [
+        //         'code' 					=> 'pt',
+        //     ],
+        // ],
+        // 'defaultLangCode' => 'en',
     ],
     ],
 ];
 ];

+ 29 - 29
appfront/config/fecshop_local_services/FecshopLang.php

@@ -12,34 +12,34 @@ return [
         //'class' => 'fecshop\services\FecshopLang',
         //'class' => 'fecshop\services\FecshopLang',
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
-        'allLangCode' => [
-            // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
-            // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
-            'en_US' => [
-                'code'                    => 'en',
-            ],
-            'fr_FR' => [
-                'code'                    => 'fr',
-            ],
-            'de_DE' => [
-                'code'                    => 'de',
-            ],
-            'es_ES' => [
-                'code'                    => 'es',
-            ],
-            'ru_RU' => [
-                'code'                    => 'ru',
-            ],
-            'pt_PT' => [
-                'code'                    => 'pt',
-            ],
-            'zh_CN' => [
-                'code'                    => 'zh',
-            ],
-            'it_IT' => [
-                'code'                    => 'it',
-            ],
-        ],
-        'defaultLangCode' => 'en',
+        // 'allLangCode' => [
+        //     // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
+        //     // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
+        //     'en_US' => [
+        //         'code'                    => 'en',
+        //     ],
+        //     'fr_FR' => [
+        //         'code'                    => 'fr',
+        //     ],
+        //     'de_DE' => [
+        //         'code'                    => 'de',
+        //     ],
+        //     'es_ES' => [
+        //         'code'                    => 'es',
+        //     ],
+        //     'ru_RU' => [
+        //         'code'                    => 'ru',
+        //     ],
+        //     'pt_PT' => [
+        //         'code'                    => 'pt',
+        //     ],
+        //     'zh_CN' => [
+        //         'code'                    => 'zh',
+        //     ],
+        //     'it_IT' => [
+        //         'code'                    => 'it',
+        //     ],
+        // ],
+        // 'defaultLangCode' => 'en',
     ],
     ],
 ];
 ];

+ 1 - 1
appfront/config/fecshop_local_services/Store.php

@@ -1,7 +1,7 @@
 <?php
 <?php
    return [
    return [
    'store' => [
    'store' => [
-        'class'  => '\common\local\local_services\Store',
+        'class'  => 'fecshop\services\Store',
         'stores' => [
         'stores' => [
             // store key:域名去掉http部分,作为key,这个必须这样定义。
             // store key:域名去掉http部分,作为key,这个必须这样定义。
             'appfront.fecshoptest.com' => [
             'appfront.fecshoptest.com' => [

+ 12 - 5
appfront/local/local_modules/Cms/block/home/Index.php

@@ -10,12 +10,19 @@ use Yii;
 
 
 class Index extends \fecshop\app\appfront\modules\Cms\block\home\Index
 class Index extends \fecshop\app\appfront\modules\Cms\block\home\Index
 {
 {
-    public function getLastData()
+    public function getFeaturedProduct()
     {
     {
-        return [
-        
-        ];
+        $appName = Yii::$service->helper->getAppName();
+        $bestFeatureSkuConfig = Yii::$app->store->get($appName.'_home', 'best_feature_sku');
+        $featured_skus = explode(',', $bestFeatureSkuConfig);
+        return $this->getProductBySkus($featured_skus);
+    }
+    public function getBestSellerProducts()
+    {
+        $appName = Yii::$service->helper->getAppName();
+        $bestSellSkusConfig = Yii::$app->store->get($appName.'_home', 'best_seller_sku');
+        $bestSellSkus = explode(',', $bestSellSkusConfig);
+        return $this->getProductBySkus($bestSellSkus);
     }
     }
-
     
     
 }
 }

+ 29 - 29
apphtml5/config/fecshop_local_services/FecshopLang.php

@@ -12,34 +12,34 @@ return [
         //'class' => 'fecshop\services\FecshopLang',
         //'class' => 'fecshop\services\FecshopLang',
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
-        'allLangCode' => [
-            // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
-            // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
-            'en_US' => [
-                'code'                    => 'en',
-            ],
-            'fr_FR' => [
-                'code'                    => 'fr',
-            ],
-            'de_DE' => [
-                'code'                    => 'de',
-            ],
-            'es_ES' => [
-                'code'                    => 'es',
-            ],
-            'ru_RU' => [
-                'code'                    => 'ru',
-            ],
-            'pt_PT' => [
-                'code'                    => 'pt',
-            ],
-            'zh_CN' => [
-                'code'                    => 'zh',
-            ],
-            'it_IT' => [
-                'code'                    => 'it',
-            ],
-        ],
-        'defaultLangCode' => 'en',
+        // 'allLangCode' => [
+        //     // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
+        //     // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
+        //     'en_US' => [
+        //         'code'                    => 'en',
+        //     ],
+        //     'fr_FR' => [
+        //         'code'                    => 'fr',
+        //     ],
+        //     'de_DE' => [
+        //         'code'                    => 'de',
+        //     ],
+        //     'es_ES' => [
+        //         'code'                    => 'es',
+        //     ],
+        //     'ru_RU' => [
+        //         'code'                    => 'ru',
+        //     ],
+        //     'pt_PT' => [
+        //         'code'                    => 'pt',
+        //     ],
+        //     'zh_CN' => [
+        //         'code'                    => 'zh',
+        //     ],
+        //     'it_IT' => [
+        //         'code'                    => 'it',
+        //     ],
+        // ],
+        // 'defaultLangCode' => 'en',
     ],
     ],
 ];
 ];

+ 1 - 1
apphtml5/config/fecshop_local_services/Store.php

@@ -4,7 +4,7 @@
         'class'  => 'fecshop\services\Store',
         'class'  => 'fecshop\services\Store',
         'stores' => [
         'stores' => [
             // store key:域名去掉http部分,作为key,这个必须这样定义。
             // store key:域名去掉http部分,作为key,这个必须这样定义。
-            'fecshop.apphtml5.fancyecommerce.com' => [
+            'apphtml5.fecshoptest.com' => [
                 'language'         => 'en_US',        // 语言简码需要在@common/config/fecshop_local_services/FecshopLang.php 中定义。
                 'language'         => 'en_US',        // 语言简码需要在@common/config/fecshop_local_services/FecshopLang.php 中定义。
                 'languageName'     => 'English',    // 语言简码对应的文字名称,将会出现在语言切换列表中显示。
                 'languageName'     => 'English',    // 语言简码对应的文字名称,将会出现在语言切换列表中显示。
                 'localThemeDir'    => '@apphtml5/theme/terry/theme01', // 设置当前store对应的模板路径。关于多模板的方面的知识,您可以参看fecshop多模板的知识。
                 'localThemeDir'    => '@apphtml5/theme/terry/theme01', // 设置当前store对应的模板路径。关于多模板的方面的知识,您可以参看fecshop多模板的知识。

+ 29 - 29
appserver/config/fecshop_local_services/FecshopLang.php

@@ -12,34 +12,34 @@ return [
         //'class' => 'fecshop\services\FecshopLang',
         //'class' => 'fecshop\services\FecshopLang',
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  mongoSearchLangName 在各个语言下字段参考资料如下:(不支持中文)
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
         //  https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages
-        'allLangCode' => [
-            // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
-            // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
-            'en_US' => [
-                'code'                    => 'en',
-            ],
-            'fr_FR' => [
-                'code'                    => 'fr',
-            ],
-            'de_DE' => [
-                'code'                    => 'de',
-            ],
-            'es_ES' => [
-                'code'                    => 'es',
-            ],
-            'ru_RU' => [
-                'code'                    => 'ru',
-            ],
-            'pt_PT' => [
-                'code'                    => 'pt',
-            ],
-            'zh_CN' => [
-                'code'                    => 'zh',
-            ],
-            'it_IT' => [
-                'code'                    => 'it',
-            ],
-        ],
-        'defaultLangCode' => 'en',
+        // 'allLangCode' => [
+        //     // 'en_US' 是标准语言简码  code对应的值en取 “标准语言简码”的前两位字符,
+        //     // 该值设置后,进行了产品分类数据的添加后,不能修改,否则会出现部分翻译语言丢失。
+        //     'en_US' => [
+        //         'code'                    => 'en',
+        //     ],
+        //     'fr_FR' => [
+        //         'code'                    => 'fr',
+        //     ],
+        //     'de_DE' => [
+        //         'code'                    => 'de',
+        //     ],
+        //     'es_ES' => [
+        //         'code'                    => 'es',
+        //     ],
+        //     'ru_RU' => [
+        //         'code'                    => 'ru',
+        //     ],
+        //     'pt_PT' => [
+        //         'code'                    => 'pt',
+        //     ],
+        //     'zh_CN' => [
+        //         'code'                    => 'zh',
+        //     ],
+        //     'it_IT' => [
+        //         'code'                    => 'it',
+        //     ],
+        // ],
+        // 'defaultLangCode' => 'en',
     ],
     ],
 ];
 ];

+ 1 - 0
common/config/YiiRewriteMap.php

@@ -8,4 +8,5 @@ return [
      */
      */
     //'\fecshop\models\mongodb\Category'  => '\appfront\local\local_models\mongodb\Category',
     //'\fecshop\models\mongodb\Category'  => '\appfront\local\local_models\mongodb\Category',
     //'\fecshop\app\appfront\modules\Customer\block\address\Edit'  => '\fectfurnilife\appfront\modules\Customer\block\address\Edit',
     //'\fecshop\app\appfront\modules\Customer\block\address\Edit'  => '\fectfurnilife\appfront\modules\Customer\block\address\Edit',
+    '\fecshop\app\appfront\modules\Cms\block\home\Index'=>'appfront\local\local_modules\Cms\block\home\Index'
 ];
 ];

+ 7 - 0
common/config/fecshop_local.php

@@ -19,8 +19,15 @@ $services = [];
 foreach (glob(__DIR__.'/fecshop_local_services/*.php') as $filename) {
 foreach (glob(__DIR__.'/fecshop_local_services/*.php') as $filename) {
     $services = array_merge($services, require($filename));
     $services = array_merge($services, require($filename));
 }
 }
+$components = [
+    'store' => [
+        'class' => '\common\local\local_components\Store',
+    ],
+];
+
 
 
 return [
 return [
     'modules'  => $modules,
     'modules'  => $modules,
     'services' => $services,
     'services' => $services,
+    'components' => $components,
 ];
 ];

+ 29 - 29
common/config/fecshop_local_services/Email.php

@@ -9,34 +9,34 @@
  */
  */
 return [
 return [
     'email' => [
     'email' => [
-        'mailerConfig' => [
-            // 默认通用配置
-            'default' => [
-                'class'     => 'yii\swiftmailer\Mailer',
-                'transport' => [
-                    'class'       => 'Swift_SmtpTransport',
-                    'host'        => 'smtp.qq.com',            //SMTP Host
-                    'username'    => '2420577683@qq.com',   //SMTP 账号
-                    'password'    => 'dshagrytvnwjeabb',    //SMTP 密码
-                    'port'        => '587',                    //SMTP 端口
-                    'encryption'  => 'tls',
-                ],
-                'messageConfig'=> [
-                   'charset'=> 'UTF-8',
-                ],
-            ],
-        ],
-        'childService' => [
-            'customer' => [
-                /**
-                  * 注册账户是否需要邮件激活
-                  */
-                'registerAccountIsNeedEnableByEmail'  => false,
-                /**
-                 * 注册账户激活邮件的token的过期时间(秒),只有当 registerAccountIsNeedEnableByEmail 为true的时候有效。
-	             */
-                'registerAccountEnableTokenExpire' => 86400,
-            ],
-        ],
+        // 'mailerConfig' => [
+        //     // 默认通用配置
+        //     'default' => [
+        //         'class'     => 'yii\swiftmailer\Mailer',
+        //         'transport' => [
+        //             'class'       => 'Swift_SmtpTransport',
+        //             'host'        => 'smtp.qq.com',            //SMTP Host
+        //             'username'    => '2420577683@qq.com',   //SMTP 账号
+        //             'password'    => 'dshagrytvnwjeabb',    //SMTP 密码
+        //             'port'        => '587',                    //SMTP 端口
+        //             'encryption'  => 'tls',
+        //         ],
+        //         'messageConfig'=> [
+        //            'charset'=> 'UTF-8',
+        //         ],
+        //     ],
+        // ],
+        // 'childService' => [
+        //     'customer' => [
+        //         /**
+        //           * 注册账户是否需要邮件激活
+        //           */
+        //         'registerAccountIsNeedEnableByEmail'  => true,
+        //         /**
+        //          * 注册账户激活邮件的token的过期时间(秒),只有当 registerAccountIsNeedEnableByEmail 为true的时候有效。
+	    //          */
+        //         'registerAccountEnableTokenExpire' => 86400,
+        //     ],
+        // ],
     ],
     ],
 ];
 ];

+ 1 - 1
common/config/fecshop_local_services/FecshopLang.php

@@ -10,6 +10,6 @@
 return [
 return [
     'fecshoplang' => [
     'fecshoplang' => [
         // 默认语言。
         // 默认语言。
-        'defaultLangCode' => 'en',
+        // 'defaultLangCode' => 'en',
     ],
     ],
 ];
 ];

+ 24 - 24
common/config/fecshop_local_services/Page.php

@@ -11,31 +11,31 @@ return [
     'page' => [
     'page' => [
         'childService' => [
         'childService' => [
             'currency' => [
             'currency' => [
-                'baseCurrecy'     => 'USD',    // 基础货币,后台产品的价格都使用基础货币填写价格值。
-                'defaultCurrency' => 'USD', // 默认货币,如果store不设置货币,就使用这个store默认货币
-                'currencys'       => [
+                // 'baseCurrecy'     => 'USD',    // 基础货币,后台产品的价格都使用基础货币填写价格值。
+                // 'defaultCurrency' => 'USD', // 默认货币,如果store不设置货币,就使用这个store默认货币
+                // 'currencys'       => [
                     
                     
-                    'EUR' => [            // 欧元
-                        'rate'        => 0.93, // 汇率
-                        'symbol'      => '€',
-                    ],
-                    'USD' => [            // 货币简码,USD代表美元,这个是国际标准
-                        'rate'        => 1,    // 汇率  当前货币/基础货币的比值,譬如,人民币/美元 = 7
-                        'symbol'      => '$', //货币符号
-                    ],
-                    //'AUD' => [
-                    //	'rate' 		=> 1.33,
-                    //	'symbol' 	=> 'AU$',
-                    //],
-                    'GBP' => [            // 英镑
-                        'rate'        => 0.8,
-                        'symbol'      => '£',
-                    ],
-                    'CNY' => [            // 人民币
-                        'rate'        =>  6.3, //6.87,
-                        'symbol'      => '¥',
-                    ],
-                ],
+                //     'EUR' => [            // 欧元
+                //         'rate'        => 0.93, // 汇率
+                //         'symbol'      => '€',
+                //     ],
+                //     'USD' => [            // 货币简码,USD代表美元,这个是国际标准
+                //         'rate'        => 1,    // 汇率  当前货币/基础货币的比值,譬如,人民币/美元 = 7
+                //         'symbol'      => '$', //货币符号
+                //     ],
+                //     //'AUD' => [
+                //     //	'rate' 		=> 1.33,
+                //     //	'symbol' 	=> 'AU$',
+                //     //],
+                //     'GBP' => [            // 英镑
+                //         'rate'        => 0.8,
+                //         'symbol'      => '£',
+                //     ],
+                //     'CNY' => [            // 人民币
+                //         'rate'        =>  6.3, //6.87,
+                //         'symbol'      => '¥',
+                //     ],
+                // ],
             ],
             ],
             'trace' => [
             'trace' => [
                 'class' => 'fecshop\services\page\Trace',
                 'class' => 'fecshop\services\page\Trace',

+ 6 - 0
common/config/fecshop_local_services/StoreBaseConfig.php

@@ -0,0 +1,6 @@
+<?php
+return [
+    'storeBaseConfig' => [
+        'class' => '\common\local\local_services\StoreBaseConfig',
+    ],
+];

+ 72 - 0
common/local/local_components/Store.php

@@ -0,0 +1,72 @@
+<?php
+/**
+ * FecShop file.
+ * @link http://www.fecshop.com/
+ * @copyright Copyright (c) 2016 FecShop Software LLC
+ * @license http://www.fecshop.com/license/
+ */
+
+namespace common\local\local_components;
+
+use Yii;
+use yii\base\BootstrapInterface;
+use yii\base\Component;
+
+/**
+ * @author Terry Zhao <2358269014@qq.com>
+ * @since 1.0
+ */
+class Store extends Component implements BootstrapInterface
+{
+    public $appName;
+    
+    public $_base_config;
+    
+    protected $_modelName = '\fecshop\models\mysqldb\StoreBaseConfig';
+
+    protected $_model;
+    
+    //
+    public $serviceMongodbName = 'mongodb'; 
+    public $serviceMysqldbName = 'mysqldb'; 
+    //
+    public $enable = 1;
+    public $disable = 2;
+    
+    
+    // 初始化的bootstrap
+    public function bootstrap($app)
+    {
+        $this->initBaseConfig();
+        if ($this->appName == 'appadmin') {
+            Yii::$service->admin->bootstrap($app);
+        } else {
+            Yii::$service->store->bootstrap($app);
+        }
+    }
+    // 得到配置值
+    /**
+     * @param $key | string, 配置的主Key
+     * @param $subKey | string, 配置的子key
+     */
+    public function get($key, $subKey = '')
+    {
+        $this->initBaseConfig();
+        if (!$subKey) {
+            return isset($this->_base_config[$key]) ? $this->_base_config[$key] : null;
+        }
+        return isset($this->_base_config[$key][$subKey]) ? $this->_base_config[$key][$subKey] : null;
+    }
+    
+    
+    public function initBaseConfig()
+    {
+        if (!$this->_base_config) {
+            $this->_base_config = Yii::$service->storeBaseConfig->getAllConfig();
+        }
+    }
+    
+    
+    
+    
+}

+ 28 - 0
common/local/local_models/mysqldb/StoreBaseConfig.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * FecShop file.
+ *
+ * @link http://www.fecshop.com/
+ * @copyright Copyright (c) 2016 FecShop Software LLC
+ * @license http://www.fecshop.com/license/
+ */
+
+namespace common\local\local_models\mysqldb;
+
+use yii\db\ActiveRecord;
+use yii\base\InvalidValueException;
+
+/**
+ * @author Terry Zhao <2358269014@qq.com>
+ * @since 1.0
+ */
+class StoreBaseConfig extends ActiveRecord
+{
+    public static function tableName()
+    {
+        return '{{%store_base_config}}';
+    }
+
+}
+
+

+ 197 - 0
common/local/local_services/StoreBaseConfig.php

@@ -0,0 +1,197 @@
+<?php
+
+/*
+ * FecShop file.
+ *
+ * @link http://www.fecshop.com/
+ * @copyright Copyright (c) 2016 FecShop Software LLC
+ * @license http://www.fecshop.com/license/
+ */
+
+namespace common\local\local_services;
+
+use Yii;
+
+/**
+ * Cart services. 购物车service, 执行购物车部分对应的方法。
+ *
+ * @property \fecshop\services\cart\Coupon $coupon coupon sub-service of cart
+ * @property \fecshop\services\cart\Info $info info sub-service of cart
+ * @property \fecshop\services\cart\Quote $quote quote sub-service of cart
+ * @property \fecshop\services\cart\QuoteItem $quoteItem quoteItem sub-service of cart
+ *
+ * @method getCartInfo($activeProduct, $shippingMethod = '', $country = '', $region = '*') see [[\fecshop\services\Cart::actionGetCartInfo()]]
+ * @method mergeCartAfterUserLogin() see [[\fecshop\services\Cart::actionmergeCartAfterUserLogin()]]
+ *
+ * @author Terry Zhao <2358269014@qq.com>
+ * @since 1.0
+ */
+class StoreBaseConfig extends \fecshop\services\Service
+{
+    
+    public $numPerPage = 20;
+
+    protected $_modelName = '\common\local\local_models\mysqldb\StoreBaseConfig';
+
+    protected $_model;
+    
+    //protected $_serilizeAttr = [
+    //    "service_db",
+    //];
+    
+    public function init()
+    {
+        parent::init();
+        list($this->_modelName, $this->_model) = Yii::mapGet($this->_modelName);
+    }
+    
+    public function getPrimaryKey()
+    {
+        return 'id';
+    }
+
+    public function getByPrimaryKey($primaryKey)
+    {
+        if ($primaryKey) {
+            $one = $this->_model->findOne($primaryKey);
+            
+            return $one;
+        } else {
+            return new $this->_modelName();
+        }
+    }
+    
+    public function getByKey($key)
+    {
+        if ($key) {
+            $one = $this->_model->findOne(['key' => $key]);
+            
+            return $one;
+        }
+        
+        return null;
+    }
+    
+
+    /*
+     * example filter:
+     * [
+     * 		'numPerPage' 	=> 20,
+     * 		'pageNum'		=> 1,
+     * 		'orderBy'	=> ['_id' => SORT_DESC, 'sku' => SORT_ASC ],
+            'where'			=> [
+                ['>','price',1],
+                ['<=','price',10]
+     * 			['sku' => 'uk10001'],
+     * 		],
+     * 	'asArray' => true,
+     * ]
+     */
+    public function coll($filter = '')
+    {
+        $query = $this->_model->find();
+        $query = Yii::$service->helper->ar->getCollByFilter($query, $filter);
+        $coll = $query->all();
+        //if (!empty($coll)) {
+           // foreach ($coll as $k => $one) {
+                //if (in_array($one['key'], $this->_serilizeAttr)) {
+                //    $one['key'] = unserialize($one)
+                //}
+                
+                //foreach ($this->_lang_attr as $attr) {
+                //    $one[$attr] = $one[$attr] ? unserialize($one[$attr]) : '';
+                //}
+                $coll[$k] = $one;
+            //}
+        //}
+        //var_dump($one);
+        return [
+            'coll' => $coll,
+            'count'=> $query->limit(null)->offset(null)->count(),
+        ];
+    }
+    
+    public function getAllConfig()
+    {
+        $arr = [];
+        $baseConfigs = $this->_model->find()->select(['key', 'value'])->asArray()->all();
+        foreach ($baseConfigs as $one) {
+            $arr[$one['key']] = unserialize($one['value']);
+        }
+        
+        return $arr;
+    }
+
+    /**
+     * @param $one|array
+     * save $data to cms model,then,add url rewrite info to system service urlrewrite.
+     */
+    public function save($one)
+    {
+        $currentDateTime = \fec\helpers\CDate::getCurrentDateTime();
+        $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : '';
+        
+        if ($primaryVal) {
+            $model = $this->_model->findOne($primaryVal);
+            if (!$model) {
+                Yii::$service->helper->errors->add('config {primaryKey} is not exist', ['primaryKey' => $this->getPrimaryKey()]);
+
+                return;
+            }
+        } else {
+            $model = new $this->_modelName();
+            $model->created_at = time();
+        }
+        $model->updated_at = time();
+        
+        $primaryKey = $this->getPrimaryKey();
+        $model      = Yii::$service->helper->ar->save($model, $one);
+        $primaryVal = $model[$primaryKey];
+
+        return true;
+    }
+    // 保存配置值
+    public function saveConfig($one)
+    {
+        if (!$one['key'] || !$one['value']) {
+            return false;
+        }
+        $model = $this->_model->findOne(['key' => $one['key']]);
+        if (!$model['id']) {
+            $model = new $this->_modelName();
+            $model->created_at = time();
+            $model->key = $one['key'];
+        }
+        if (is_array($one['value'])) {
+            $model->value = serialize($one['value']);
+        } else {
+            $model->value = $one['value'];
+        }
+        $model->updated_at = time();
+        
+        return $model->save();
+    }
+
+    public function remove($ids)
+    {
+        if (!$ids) {
+            Yii::$service->helper->errors->add('remove id is empty');
+
+            return false;
+        }
+        if (is_array($ids) && !empty($ids)) {
+            foreach ($ids as $id) {
+                $model = $this->_model->findOne($id);
+                $model->delete();
+            }
+        } else {
+            $id = $ids;
+            foreach ($ids as $id) {
+                $model = $this->_model->findOne($id);
+                $model->delete();
+            }
+        }
+
+        return true;
+    }
+}

+ 7 - 0
vendor/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInita9106c7fbcfaa1378d3361810ea16418::getLoader();

+ 4 - 0
vendor/behat/gherkin/.gitignore

@@ -0,0 +1,4 @@
+*.tgz
+vendor
+composer.phar
+composer.lock

+ 33 - 0
vendor/behat/gherkin/.travis.yml

@@ -0,0 +1,33 @@
+language: php
+
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.composer/cache/files
+
+php: [5.6, 7.0, 7.1, 7.2, 7.2]
+
+matrix:
+  include:
+    - php: 5.6
+      env: SYMFONY_VERSION='2.3.*'
+    - php: 5.6
+      env: SYMFONY_VERSION='2.7.*'
+    - php: 5.6
+      env: SYMFONY_VERSION='2.8.*'
+    - php: 7.1
+      env: SYMFONY_VERSION='3.*'
+
+before_install:
+  - composer self-update
+  - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/yaml=$SYMFONY_VERSION; fi;
+
+install:
+  - composer install
+
+script: vendor/bin/phpunit -v --coverage-clover=coverage.clover
+
+after_script:
+  # don't upload coverage on PHP 7 and HHVM as it cannot be generated. We don't want to tell Scrutinizer that the coverage generation failed.
+  - if [[ "7.0" != "$TRAVIS_PHP_VERSION" && "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi

+ 388 - 0
vendor/behat/gherkin/CHANGES.md

@@ -0,0 +1,388 @@
+4.6.0 / 2019-01-16
+==================
+
+ * Updated translations (including 'Example' as synonym for 'Scenario' in `en`)
+
+4.5.1 / 2017-08-30
+==================
+
+  * Fix regression in `PathsFilter`
+
+4.5.0 / 2017-08-30
+==================
+
+  * Sync i18n with Cucumber Gherkin
+  * Drop support for HHVM tests on Travis
+  * Add `TableNode::fromList()` method (thanks @TravisCarden)
+  * Add `ExampleNode::getOutlineTitle()` method (thanks @duxet)
+  * Use realpath, so the feature receives the cwd prefixed (thanks @glennunipro)
+  * Explicitly handle non-two-dimensional arrays in TableNode (thanks @TravisCarden)
+  * Fix to line/linefilter scenario runs which take relative paths to files (thanks @generalconsensus)
+
+4.4.5 / 2016-10-30
+==================
+
+  * Fix partial paths matching in `PathsFilter`
+
+4.4.4 / 2016-09-18
+==================
+
+  * Provide clearer exception for non-writeable cache directories
+
+4.4.3 / 2016-09-18
+==================
+
+  * Ensure we reset tags between features
+
+4.4.2 / 2016-09-03
+==================
+
+  * Sync 18n with gherkin 3
+
+4.4.1 / 2015-12-30
+==================
+
+  * Ensure keywords are trimmed when syncing translations
+  * Sync 18n with cucumber
+
+4.4.0 / 2015-09-19
+==================
+
+  * Added validation enforcing that all rows of a `TableNode` have the same number of columns
+  * Added `TableNode::getColumn` to get a column from the table
+  * Sync 18n with cucumber
+
+4.3.0 / 2014-06-06
+==================
+
+  * Added `setFilters(array)` method to `Gherkin` class
+  * Added `NarrativeFilter` for non-english `RoleFilter` lovers
+
+4.2.1 / 2014-06-06
+==================
+
+  * Fix parsing of features without line feed at the end
+
+4.2.0 / 2014-05-27
+==================
+
+  * Added `getKeyword()` and `getKeywordType()` methods to `StepNode`, deprecated `getType()`.
+    Thanks to @kibao
+
+4.1.3 / 2014-05-25
+==================
+
+  * Properly handle tables with rows terminating in whitespace
+
+4.1.2 / 2014-05-14
+==================
+
+  * Handle case where Gherkin cache is broken
+
+4.1.1 / 2014-05-05
+==================
+
+  * Fixed the compatibility with PHP 5.6-beta by avoiding to use the broken PHP array function
+  * The YamlFileLoader no longer extend from ArrayLoader but from AbstractFileLoader
+
+4.1.0 / 2014-04-20
+==================
+
+  * Fixed scenario tag filtering
+  * Do not allow multiple multiline step arguments
+  * Sync 18n with cucumber
+
+4.0.0 / 2014-01-05
+==================
+
+  * Changed the behavior when no loader can be found for the resource. Instead of throwing an exception, the
+    Gherkin class now returns an empty array.
+
+3.1.3 / 2014-01-04
+==================
+
+  * Dropped the dependency on the Symfony Finder by using SPL iterators directly
+  * Added testing on HHVM on Travis. HHVM is officially supported (previous release was actually already compatible)
+
+3.1.2 / 2014-01-01
+==================
+
+  * All paths passed to PathsFilter are converted using realpath
+
+3.1.1 / 2013-12-31
+==================
+
+  * Add `ComplexFilterInterace` that has complex behavior for scenarios and requires to pass
+    feature too
+  * `TagFilter` is an instance of a `ComplexFilterInterace` now
+
+3.1.0 / 2013-12-31
+==================
+
+  * Example node is a scenario
+  * Nodes do not have uprefs (memory usage fix)
+  * Scenario filters do not depend on feature nodes
+
+3.0.5 / 2014-01-01
+==================
+
+  * All paths passed to PathsFilter are converted using realpath
+
+3.0.4 / 2013-12-31
+==================
+
+  * TableNode is now traversable using foreach
+  * All possibly thrown exceptions implement Gherkin\Exception interface
+  * Sync i18n with cucumber
+
+3.0.3 / 2013-09-15
+==================
+
+  * Extend ExampleNode with additional methods
+
+3.0.2 / 2013-09-14
+==================
+
+  * Extract `KeywordNodeInterface` and `ScenarioLikeInterface`
+  * Add `getIndex()` methods to scenarios, outlines, steps and examples
+  * Throw proper exception for fractured node tree
+
+3.0.1 / 2013-09-14
+==================
+
+  * Use versioned subfolder in FileCache
+
+3.0.0 / 2013-09-14
+==================
+
+  * A lot of optimizations in Parser and Lexer
+  * Node tree is now immutable by nature (no setters)
+  * Example nodes are now part of the node tree. They are lazily generated by Outline node
+  * Sync with latest cucumber i18n
+
+2.3.4 / 2013-08-11
+==================
+
+  * Fix leaks in memory cache
+
+2.3.3 / 2013-08-11
+==================
+
+  * Fix encoding bug introduced with previous release
+  * Sync i18n with cucumber
+
+2.3.2 / 2013-08-11
+==================
+
+  * Explicitly use utf8 encoding
+
+2.3.1 / 2013-08-10
+==================
+
+  * Support `an` prefix with RoleFilter
+
+2.3.0 / 2013-08-04
+==================
+
+  * Add RoleFilter
+  * Add PathsFilter
+  * Add MemoryCache
+
+2.2.9 / 2013-03-02
+==================
+
+  * Fix dependency version requirement
+
+2.2.8 / 2013-03-02
+==================
+
+  * Features filtering behavior change. Now emptified (by filtering) features
+    that do not match filter themselves are removed from resultset.
+  * Small potential bug fix in TableNode
+
+2.2.7 / 2013-01-27
+==================
+
+  * Fixed bug in i18n syncing script
+  * Resynced Gherkin i18n
+
+2.2.6 / 2013-01-26
+==================
+
+  * Support long row hashes in tables ([see](https://github.com/Behat/Gherkin/issues/40))
+  * Synced Gherkin i18n
+
+2.2.5 / 2012-09-26
+==================
+
+  * Fixed issue with loading empty features
+  * Synced Gherkin i18n
+
+2.2.4 / 2012-08-03
+==================
+
+  * Fixed exception message for "no loader found"
+
+2.2.3 / 2012-08-03
+==================
+
+  * Fixed minor loader bug with empty base path
+  * Synced Gherkin i18n
+
+2.2.2 / 2012-07-01
+==================
+
+  * Added ability to filter outline scenarios by line and range filters
+  * Synced Gherkin i18n
+  * Refactored table parser to read row line numbers too
+
+2.2.1 / 2012-05-04
+==================
+
+  * Fixed StepNode `getLanguage()` and `getFile()`
+
+2.2.0 / 2012-05-03
+==================
+
+  * Features freeze after parsing
+  * Implemented GherkinDumper (@Halleck45)
+  * Synced i18n with Cucumber
+  * Updated inline documentation
+
+2.1.1 / 2012-03-09
+==================
+
+  * Fixed caching bug, where `isFresh()` always returned false
+
+2.1.0 / 2012-03-09
+==================
+
+  * Added parser caching layer
+  * Added support for table delimiter escaping (use `\|` for that)
+  * Added LineRangeFilter (thanks @headrevision)
+  * Synced i18n dictionary with cucumber/gherkin
+
+2.0.2 / 2012-02-04
+==================
+
+  * Synced i18n dictionary with cucumber/gherkin
+
+2.0.1 / 2012-01-26
+==================
+
+  * Fixed issue about parsing features without indentation
+
+2.0.0 / 2012-01-19
+==================
+
+  * Background titles support
+  * Correct parsing of titles/descriptions (hirarchy lexing)
+  * Migration to the cucumber/gherkin i18n dictionary
+  * Speed optimizations
+  * Refactored KeywordsDumper
+  * New loaders
+  * Bugfixes
+
+1.1.4 / 2012-01-08
+==================
+
+  * Read feature description even if it looks like a step
+
+1.1.3 / 2011-12-14
+==================
+
+  * Removed file loading routines from Parser (fixes `is_file()` issue on some systems - thanks
+    @flodocteurklein)
+
+1.1.2 / 2011-12-01
+==================
+
+  * Updated spanish trasnaltion (@anbotero)
+  * Integration with Composer and Travis CI
+
+1.1.1 / 2011-07-29
+==================
+
+  * Updated pt language step types (@danielcsgomes)
+  * Updated vendors
+
+1.1.0 / 2011-07-16
+==================
+
+  * Return all tags, including inherited in `Scenario::getTags()`
+  * New `Feature::getOwnTags()` and `Scenario::getOwnTags()` method added,
+    which returns only own tags
+
+1.0.8 / 2011-06-29
+==================
+
+  * Fixed comments parsing.
+    You can’t have comments at the end of a line # like this
+    # But you can still have comments at the beginning of a line
+
+1.0.7 / 2011-06-28
+==================
+
+  * Added `getRaw()` method to PyStringNode
+  * Updated vendors
+
+1.0.6 / 2011-06-17
+==================
+
+  * Updated vendors
+
+1.0.5 / 2011-06-10
+==================
+
+  * Fixed bug, introduced with 1.0.4 - hash in PyStrings
+
+1.0.4 / 2011-06-10
+==================
+
+  * Fixed inability to comment pystrings
+
+1.0.3 / 2011-04-21
+==================
+
+  * Fixed introduced with 1.0.2 pystring parsing bug
+
+1.0.2 / 2011-04-18
+==================
+
+  * Fixed bugs in text with comments parsing
+
+1.0.1 / 2011-04-01
+==================
+
+  * Updated vendors
+
+1.0.0 / 2011-03-08
+==================
+
+  * Updated vendors
+
+1.0.0RC2 / 2011-02-25
+=====================
+
+  * Windows support
+  * Missing phpunit config
+
+1.0.0RC1 / 2011-02-15
+=====================
+
+  * Huge optimizations to Lexer & Parser
+  * Additional loaders (Yaml, Array, Directory)
+  * Filters (Tag, Name, Line)
+  * Code refactoring
+  * Nodes optimizations
+  * Additional tests for exceptions and translations
+  * Keywords dumper
+
+0.2.0 / 2011-01-05
+==================
+
+  * New Parser & Lexer (based on AST)
+  * New verbose parsing exception handling
+  * New translation mechanics
+  * 47 brand new translations (see i18n)
+  * Full test suite for everything from AST nodes to translations

+ 33 - 0
vendor/behat/gherkin/CONTRIBUTING.md

@@ -0,0 +1,33 @@
+Contributing
+------------
+
+Gherkin is an open source, community-driven project. If you'd like to contribute, feel free to do this, but remember to follow this few simple rules:
+
+- Make your feature addition or bug fix,
+- Always use the `master` branch as base for your changes (all new development happens in `master`),
+- Add tests for those changes (please look into `tests/` folder for some examples). This is important so we don't break it in a future version unintentionally,
+- Commit your code, but do not mess with `CHANGES.md`,
+- __Remember__: when you create Pull Request, always select `master` branch as target (done by default), otherwise it will be closed.
+
+Running tests
+-------------
+
+Make sure that you don't break anything with your changes by running:
+
+```bash
+$> phpunit
+```
+
+Contributing to Gherkin Translations
+------------------------------------
+
+Gherkin supports &rarr;40 different languages and you could add more! You might notice
+`i18n.php` file in the root of the library. This file is downloaded and **autogenerated**
+from original [cucumber/gherkin translations](https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json).
+So, in order to fix/update/add some translation, you should send Pull Request to the
+`cucumber/gherkin` repository. `Behat\Gherkin` will redownload/regenerate translations
+from there before each release.
+
+It might sounds difficult, but this way of dictionary sharing gives you ability to
+migrate your `*.feature` files from language to language and library to library without
+the need to rewrite/modify them - same dictionary (Gherkin) used everywhere.

+ 22 - 0
vendor/behat/gherkin/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2011-2013 Konstantin Kudryashov <ever.zet@gmail.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

+ 68 - 0
vendor/behat/gherkin/README.md

@@ -0,0 +1,68 @@
+Behat Gherkin Parser
+====================
+
+This is the php Gherkin parser for Behat. It comes bundled with more than 40 native languages
+(see `i18n.php`) support & clean architecture.
+
+[![Build Status](https://travis-ci.org/Behat/Gherkin.svg?branch=master)](https://travis-ci.org/Behat/Gherkin)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Behat/Gherkin/badges/quality-score.png?s=04d9d0237c89f4c45a94ba5304234db861dfd036)](https://scrutinizer-ci.com/g/Behat/Gherkin/)
+[![Code Coverage](https://scrutinizer-ci.com/g/Behat/Gherkin/badges/coverage.png?s=204ca44800469d295b73d18c91011cb9d2dc99fc)](https://scrutinizer-ci.com/g/Behat/Gherkin/)
+
+Useful Links
+------------
+
+- Official Google Group is at [http://groups.google.com/group/behat](http://groups.google.com/group/behat)
+- IRC channel on [#freenode](http://freenode.net/) is `#behat`
+- [Note on Patches/Pull Requests](CONTRIBUTING.md)
+
+Usage Example
+-------------
+
+``` php
+<?php
+
+$keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
+    'en' => array(
+        'feature'          => 'Feature',
+        'background'       => 'Background',
+        'scenario'         => 'Scenario',
+        'scenario_outline' => 'Scenario Outline|Scenario Template',
+        'examples'         => 'Examples|Scenarios',
+        'given'            => 'Given',
+        'when'             => 'When',
+        'then'             => 'Then',
+        'and'              => 'And',
+        'but'              => 'But'
+    ),
+    'en-pirate' => array(
+        'feature'          => 'Ahoy matey!',
+        'background'       => 'Yo-ho-ho',
+        'scenario'         => 'Heave to',
+        'scenario_outline' => 'Shiver me timbers',
+        'examples'         => 'Dead men tell no tales',
+        'given'            => 'Gangway!',
+        'when'             => 'Blimey!',
+        'then'             => 'Let go and haul',
+        'and'              => 'Aye',
+        'but'              => 'Avast!'
+    )
+));
+$lexer  = new Behat\Gherkin\Lexer($keywords);
+$parser = new Behat\Gherkin\Parser($lexer);
+
+$feature = $parser->parse(file_get_contents('some.feature'));
+```
+
+Installing Dependencies
+-----------------------
+
+``` bash
+$> curl http://getcomposer.org/installer | php
+$> php composer.phar update
+```
+
+Contributors
+------------
+
+* Konstantin Kudryashov [everzet](http://github.com/everzet) [lead developer]
+* Other [awesome developers](https://github.com/Behat/Gherkin/graphs/contributors)

+ 70 - 0
vendor/behat/gherkin/bin/update_i18n

@@ -0,0 +1,70 @@
+#!/usr/bin/env php
+<?php
+
+$gherkinUrl = 'https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json';
+$json  = file_get_contents($gherkinUrl);
+$array = array();
+
+foreach (json_decode($json, true) as $lang => $keywords) {
+    $langMessages = array();
+
+    foreach ($keywords as $type => $words) {
+        if (!is_array($words)) {
+            $words = array($words);
+        }
+
+        if ('scenarioOutline' === $type) {
+            $type = 'scenario_outline';
+        }
+
+        if (in_array($type, array('given', 'when', 'then', 'and', 'but'))) {
+            $formattedKeywords = array();
+
+            foreach ($words as $word) {
+                $formattedWord = trim($word);
+
+                if ($formattedWord === $word) {
+                    $formattedWord = $formattedWord.'<'; // Convert the keywords to the syntax used by Gherkin 2, which is expected by our Lexer.
+                }
+
+                $formattedKeywords[] = $formattedWord;
+            }
+
+            $words = $formattedKeywords;
+        }
+
+        usort($words, function($type1, $type2) {
+            return mb_strlen($type2, 'utf8') - mb_strlen($type1, 'utf8');
+        });
+
+        $langMessages[$type] = implode('|', $words);
+    }
+
+    // ensure that the order of keys is consistent between updates
+    ksort($langMessages);
+
+    $array[$lang] = $langMessages;
+}
+
+// ensure that the languages are sorted to avoid useless diffs between updates. We keep the English first though as it is the reference.
+$enData = $array['en'];
+unset($array['en']);
+ksort($array);
+$array = array_merge(array('en' => $enData), $array);
+$arrayString = var_export($array, true);
+
+file_put_contents(__DIR__.'/../i18n.php', <<<EOD
+<?php
+
+/*
+ * DO NOT TOUCH THIS FILE!
+ *
+ * This file is automatically generated by `bin/update_i18n`.
+ * Actual Gherkin translations live in cucumber/gherkin repo:
+ * {$gherkinUrl}
+ * Please send your translation changes there.
+ */
+
+return $arrayString;
+EOD
+);

+ 47 - 0
vendor/behat/gherkin/composer.json

@@ -0,0 +1,47 @@
+{
+    "name":         "behat/gherkin",
+    "description":  "Gherkin DSL parser for PHP 5.3",
+    "keywords":     ["BDD", "parser", "DSL", "Behat", "Gherkin", "Cucumber"],
+    "homepage":     "http://behat.org/",
+    "type":         "library",
+    "license":      "MIT",
+    "authors":      [
+        {
+            "name":      "Konstantin Kudryashov",
+            "email":     "ever.zet@gmail.com",
+            "homepage":  "http://everzet.com"
+        }
+    ],
+
+    "require": {
+        "php": ">=5.3.1"
+    },
+
+    "require-dev": {
+        "symfony/yaml": "~2.3|~3|~4",
+        "symfony/phpunit-bridge": "~2.7|~3|~4",
+        "phpunit/phpunit": "~4.5|~5"
+    },
+
+    "suggest": {
+        "symfony/yaml":   "If you want to parse features, represented in YAML files"
+    },
+
+    "autoload": {
+        "psr-0": {
+            "Behat\\Gherkin":   "src/"
+        }
+    },
+
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\Behat\\": "tests/Behat/"
+        }
+    },
+
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.4-dev"
+        }
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1197 - 0
vendor/behat/gherkin/i18n.php


+ 3 - 0
vendor/behat/gherkin/libpath.php

@@ -0,0 +1,3 @@
+<?php
+
+return __DIR__;

+ 71 - 0
vendor/behat/gherkin/package.xml.tpl

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.8.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+    http://pear.php.net/dtd/tasks-1.0.xsd
+    http://pear.php.net/dtd/package-2.0
+    http://pear.php.net/dtd/package-2.0.xsd">
+    <name>gherkin</name>
+    <channel>pear.behat.org</channel>
+    <summary>Behat\Gherkin is a BDD DSL for PHP</summary>
+    <description>
+        Behat\Gherkin is an open source behavior driven development DSL for php 5.3.
+    </description>
+    <lead>
+        <name>Konstantin Kudryashov</name>
+        <user>everzet</user>
+        <email>ever.zet@gmail.com</email>
+        <active>yes</active>
+    </lead>
+    <date>##CURRENT_DATE##</date>
+    <version>
+        <release>##GHERKIN_VERSION##</release>
+        <api>1.0.0</api>
+    </version>
+    <stability>
+        <release>##STABILITY##</release>
+        <api>##STABILITY##</api>
+    </stability>
+    <license uri="http://www.opensource.org/licenses/mit-license.php">MIT</license>
+    <notes>-</notes>
+    <contents>
+        <dir name="/">
+
+            ##SOURCE_FILES##
+
+            <file role="php" baseinstalldir="gherkin" name="autoload.php" />
+            <file role="php" baseinstalldir="gherkin" name="libpath.php" />
+            <file role="php" baseinstalldir="gherkin" name="i18n.php" />
+
+            <file role="php" baseinstalldir="gherkin" name="vendor/.composer/autoload.php" />
+            <file role="php" baseinstalldir="gherkin" name="vendor/.composer/autoload_namespaces.php" />
+            <file role="php" baseinstalldir="gherkin" name="phpdoc.ini.dist" />
+            <file role="php" baseinstalldir="gherkin" name="phpunit.xml.dist" />
+            <file role="php" baseinstalldir="gherkin" name="CHANGES.md" />
+            <file role="php" baseinstalldir="gherkin" name="LICENSE" />
+            <file role="php" baseinstalldir="gherkin" name="README.md" />
+
+        </dir>
+    </contents>
+    <dependencies>
+        <required>
+            <php>
+                <min>5.3.1</min>
+            </php>
+            <pearinstaller>
+                <min>1.4.0</min>
+            </pearinstaller>
+            <extension>
+                <name>pcre</name>
+            </extension>
+            <extension>
+                <name>simplexml</name>
+            </extension>
+            <extension>
+                <name>xml</name>
+            </extension>
+            <extension>
+                <name>mbstring</name>
+            </extension>
+        </required>
+    </dependencies>
+    <phprelease />
+</package>

+ 14 - 0
vendor/behat/gherkin/phpdoc.ini.dist

@@ -0,0 +1,14 @@
+files               = "*.php"
+ignore              = "CVS, .svn, .git, _compiled"
+source_path         = "./src"
+doclet              = standard
+overview            = readme.html
+package_comment_dir = ./
+public              = on
+d                   = "api"
+default_package     = "Behat Gherkin"
+windowtitle         = "Behat Gherkin"
+doctitle            = "Behat Gherkin: PHP 5.3 Gherkin parser"
+header              = "Behat Gherkin"
+footer              = "Behat Gherkin"
+tree                = on

+ 25 - 0
vendor/behat/gherkin/phpunit.xml.dist

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false"
+         bootstrap="vendor/autoload.php"
+>
+    <testsuites>
+        <testsuite name="Behat Gherkin test suite">
+            <directory>./tests/Behat/Gherkin/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./src/Behat/Gherkin/</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 48 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Cache/CacheInterface.php

@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Cache;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Parser cache interface.
+ *
+ * @author     Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface CacheInterface
+{
+    /**
+     * Checks that cache for feature exists and is fresh.
+     *
+     * @param string  $path      Feature path
+     * @param integer $timestamp The last time feature was updated
+     *
+     * @return Boolean
+     */
+    public function isFresh($path, $timestamp);
+
+    /**
+     * Reads feature cache from path.
+     *
+     * @param string $path Feature path
+     *
+     * @return FeatureNode
+     */
+    public function read($path);
+
+    /**
+     * Caches feature node.
+     *
+     * @param string      $path    Feature path
+     * @param FeatureNode $feature Feature instance
+     */
+    public function write($path, FeatureNode $feature);
+}

+ 109 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Cache/FileCache.php

@@ -0,0 +1,109 @@
+<?php
+
+/*
+* This file is part of the Behat Gherkin.
+* (c) Konstantin Kudryashov <ever.zet@gmail.com>
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+namespace Behat\Gherkin\Cache;
+
+use Behat\Gherkin\Exception\CacheException;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Gherkin;
+
+/**
+ * File cache.
+ * Caches feature into a file.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class FileCache implements CacheInterface
+{
+    private $path;
+
+    /**
+     * Initializes file cache.
+     *
+     * @param string $path Path to the folder where to store caches.
+     *
+     * @throws CacheException
+     */
+    public function __construct($path)
+    {
+        $this->path = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'v'.Gherkin::VERSION;
+
+        if (!is_dir($this->path)) {
+            @mkdir($this->path, 0777, true);
+        }
+
+        if (!is_writeable($this->path)) {
+            throw new CacheException(sprintf('Cache path "%s" is not writeable. Check your filesystem permissions or disable Gherkin file cache.', $this->path));
+        }
+    }
+
+    /**
+     * Checks that cache for feature exists and is fresh.
+     *
+     * @param string  $path      Feature path
+     * @param integer $timestamp The last time feature was updated
+     *
+     * @return Boolean
+     */
+    public function isFresh($path, $timestamp)
+    {
+        $cachePath = $this->getCachePathFor($path);
+
+        if (!file_exists($cachePath)) {
+            return false;
+        }
+
+        return filemtime($cachePath) > $timestamp;
+    }
+
+    /**
+     * Reads feature cache from path.
+     *
+     * @param string $path Feature path
+     *
+     * @return FeatureNode
+     *
+     * @throws CacheException
+     */
+    public function read($path)
+    {
+        $cachePath = $this->getCachePathFor($path);
+        $feature = unserialize(file_get_contents($cachePath));
+
+        if (!$feature instanceof FeatureNode) {
+            throw new CacheException(sprintf('Can not load cache for a feature "%s" from "%s".', $path, $cachePath ));
+        }
+
+        return $feature;
+    }
+
+    /**
+     * Caches feature node.
+     *
+     * @param string      $path    Feature path
+     * @param FeatureNode $feature Feature instance
+     */
+    public function write($path, FeatureNode $feature)
+    {
+        file_put_contents($this->getCachePathFor($path), serialize($feature));
+    }
+
+    /**
+     * Returns feature cache file path from features path.
+     *
+     * @param string $path Feature path
+     *
+     * @return string
+     */
+    protected function getCachePathFor($path)
+    {
+        return $this->path.'/'.md5($path).'.feature.cache';
+    }
+}

+ 66 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Cache/MemoryCache.php

@@ -0,0 +1,66 @@
+<?php
+
+/*
+* This file is part of the Behat Gherkin.
+* (c) Konstantin Kudryashov <ever.zet@gmail.com>
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+namespace Behat\Gherkin\Cache;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Memory cache.
+ * Caches feature into a memory.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class MemoryCache implements CacheInterface
+{
+    private $features = array();
+    private $timestamps = array();
+
+    /**
+     * Checks that cache for feature exists and is fresh.
+     *
+     * @param string  $path      Feature path
+     * @param integer $timestamp The last time feature was updated
+     *
+     * @return Boolean
+     */
+    public function isFresh($path, $timestamp)
+    {
+        if (!isset($this->features[$path])) {
+            return false;
+        }
+
+        return $this->timestamps[$path] > $timestamp;
+    }
+
+    /**
+     * Reads feature cache from path.
+     *
+     * @param string $path Feature path
+     *
+     * @return FeatureNode
+     */
+    public function read($path)
+    {
+        return $this->features[$path];
+    }
+
+    /**
+     * Caches feature node.
+     *
+     * @param string      $path    Feature path
+     * @param FeatureNode $feature Feature instance
+     */
+    public function write($path, FeatureNode $feature)
+    {
+        $this->features[$path]   = $feature;
+        $this->timestamps[$path] = time();
+    }
+}

+ 22 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Exception/CacheException.php

@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Exception;
+
+use RuntimeException;
+
+/**
+ * Cache exception.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class CacheException extends RuntimeException implements Exception
+{
+}

+ 15 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Exception/Exception.php

@@ -0,0 +1,15 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Exception;
+
+interface Exception
+{
+}

+ 17 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Exception/LexerException.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Exception;
+
+use RuntimeException;
+
+class LexerException extends RuntimeException implements Exception
+{
+}

+ 17 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Exception/NodeException.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Exception;
+
+use RuntimeException;
+
+class NodeException extends RuntimeException implements Exception
+{
+}

+ 17 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Exception/ParserException.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Exception;
+
+use RuntimeException;
+
+class ParserException extends RuntimeException implements Exception
+{
+}

+ 52 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilter.php

@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Abstract filter class.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+abstract class ComplexFilter implements ComplexFilterInterface
+{
+    /**
+     * Filters feature according to the filter.
+     *
+     * @param FeatureNode $feature
+     *
+     * @return FeatureNode
+     */
+    public function filterFeature(FeatureNode $feature)
+    {
+        $scenarios = array();
+        foreach ($feature->getScenarios() as $scenario) {
+            if (!$this->isScenarioMatch($feature, $scenario)) {
+                continue;
+            }
+
+            $scenarios[] = $scenario;
+        }
+
+        return new FeatureNode(
+            $feature->getTitle(),
+            $feature->getDescription(),
+            $feature->getTags(),
+            $feature->getBackground(),
+            $scenarios,
+            $feature->getKeyword(),
+            $feature->getLanguage(),
+            $feature->getFile(),
+            $feature->getLine()
+        );
+    }
+}

+ 32 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/ComplexFilterInterface.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filter interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface ComplexFilterInterface extends FeatureFilterInterface
+{
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param FeatureNode       $feature  Feature node instance
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario);
+}

+ 39 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/FeatureFilterInterface.php

@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Feature filter interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface FeatureFilterInterface
+{
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature);
+
+    /**
+     * Filters feature according to the filter and returns new one.
+     *
+     * @param FeatureNode $feature
+     *
+     * @return FeatureNode
+     */
+    public function filterFeature(FeatureNode $feature);
+}

+ 30 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/FilterInterface.php

@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filter interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface FilterInterface extends FeatureFilterInterface
+{
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario);
+}

+ 122 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineFilter.php

@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters scenarios by definition line number.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class LineFilter implements FilterInterface
+{
+    protected $filterLine;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $filterLine Line of the scenario to filter on
+     */
+    public function __construct($filterLine)
+    {
+        $this->filterLine = intval($filterLine);
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        return $this->filterLine === $feature->getLine();
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        if ($this->filterLine === $scenario->getLine()) {
+            return true;
+        }
+
+        if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
+            return $this->filterLine === $scenario->getLine()
+                || in_array($this->filterLine, $scenario->getExampleTable()->getLines());
+        }
+
+        return false;
+    }
+
+    /**
+     * Filters feature according to the filter and returns new one.
+     *
+     * @param FeatureNode $feature
+     *
+     * @return FeatureNode
+     */
+    public function filterFeature(FeatureNode $feature)
+    {
+        $scenarios = array();
+        foreach ($feature->getScenarios() as $scenario) {
+            if (!$this->isScenarioMatch($scenario)) {
+                continue;
+            }
+
+            if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
+                $table = $scenario->getExampleTable()->getTable();
+                $lines = array_keys($table);
+
+                if (in_array($this->filterLine, $lines)) {
+                    $filteredTable = array($lines[0] => $table[$lines[0]]);
+
+                    if ($lines[0] !== $this->filterLine) {
+                        $filteredTable[$this->filterLine] = $table[$this->filterLine];
+                    }
+
+                    $scenario = new OutlineNode(
+                        $scenario->getTitle(),
+                        $scenario->getTags(),
+                        $scenario->getSteps(),
+                        new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()),
+                        $scenario->getKeyword(),
+                        $scenario->getLine()
+                    );
+                }
+            }
+
+            $scenarios[] = $scenario;
+        }
+
+        return new FeatureNode(
+            $feature->getTitle(),
+            $feature->getDescription(),
+            $feature->getTags(),
+            $feature->getBackground(),
+            $scenarios,
+            $feature->getKeyword(),
+            $feature->getLanguage(),
+            $feature->getFile(),
+            $feature->getLine()
+        );
+    }
+}

+ 134 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/LineRangeFilter.php

@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters scenarios by definition line number range.
+ *
+ * @author Fabian Kiss <headrevision@gmail.com>
+ */
+class LineRangeFilter implements FilterInterface
+{
+    protected $filterMinLine;
+    protected $filterMaxLine;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $filterMinLine Minimum line of a scenario to filter on
+     * @param string $filterMaxLine Maximum line of a scenario to filter on
+     */
+    public function __construct($filterMinLine, $filterMaxLine)
+    {
+        $this->filterMinLine = intval($filterMinLine);
+        if ($filterMaxLine == '*') {
+            $this->filterMaxLine = PHP_INT_MAX;
+        } else {
+            $this->filterMaxLine = intval($filterMaxLine);
+        }
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        return $this->filterMinLine <= $feature->getLine()
+            && $this->filterMaxLine >= $feature->getLine();
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        if ($this->filterMinLine <= $scenario->getLine() && $this->filterMaxLine >= $scenario->getLine()) {
+            return true;
+        }
+
+        if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
+            foreach ($scenario->getExampleTable()->getLines() as $line) {
+                if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Filters feature according to the filter.
+     *
+     * @param FeatureNode $feature
+     *
+     * @return FeatureNode
+     */
+    public function filterFeature(FeatureNode $feature)
+    {
+        $scenarios = array();
+        foreach ($feature->getScenarios() as $scenario) {
+            if (!$this->isScenarioMatch($scenario)) {
+                continue;
+            }
+
+            if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
+                $table = $scenario->getExampleTable()->getTable();
+                $lines = array_keys($table);
+
+                $filteredTable = array($lines[0] => $table[$lines[0]]);
+                unset($table[$lines[0]]);
+
+                foreach ($table as $line => $row) {
+                    if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
+                        $filteredTable[$line] = $row;
+                    }
+                }
+
+                $scenario = new OutlineNode(
+                    $scenario->getTitle(),
+                    $scenario->getTags(),
+                    $scenario->getSteps(),
+                    new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()),
+                    $scenario->getKeyword(),
+                    $scenario->getLine()
+                );
+            }
+
+            $scenarios[] = $scenario;
+        }
+
+        return new FeatureNode(
+            $feature->getTitle(),
+            $feature->getDescription(),
+            $feature->getTags(),
+            $feature->getBackground(),
+            $scenarios,
+            $feature->getKeyword(),
+            $feature->getLanguage(),
+            $feature->getFile(),
+            $feature->getLine()
+        );
+    }
+}

+ 68 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/NameFilter.php

@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters scenarios by feature/scenario name.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class NameFilter extends SimpleFilter
+{
+    protected $filterString;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $filterString Name filter string
+     */
+    public function __construct($filterString)
+    {
+        $this->filterString = trim($filterString);
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        if ('/' === $this->filterString[0]) {
+            return 1 === preg_match($this->filterString, $feature->getTitle());
+        }
+
+        return false !== mb_strpos($feature->getTitle(), $this->filterString, 0, 'utf8');
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        if ('/' === $this->filterString[0] && 1 === preg_match($this->filterString, $scenario->getTitle())) {
+            return true;
+        } elseif (false !== mb_strpos($scenario->getTitle(), $this->filterString, 0, 'utf8')) {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 61 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/NarrativeFilter.php

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\ScenarioInterface;
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Filters features by their narrative using regular expression.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class NarrativeFilter extends SimpleFilter
+{
+    /**
+     * @var string
+     */
+    private $regex;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $regex
+     */
+    public function __construct($regex)
+    {
+        $this->regex = $regex;
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        return 1 === preg_match($this->regex, $feature->getDescription());
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        return false;
+    }
+}

+ 72 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/PathsFilter.php

@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters features by their paths.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class PathsFilter extends SimpleFilter
+{
+    protected $filterPaths = array();
+
+    /**
+     * Initializes filter.
+     *
+     * @param string[] $paths List of approved paths
+     */
+    public function __construct(array $paths)
+    {
+        $this->filterPaths = array_map(
+            function ($realpath) {
+                return rtrim($realpath, DIRECTORY_SEPARATOR) .
+                    (is_dir($realpath) ? DIRECTORY_SEPARATOR : '');
+            },
+            array_filter(
+                array_map('realpath', $paths)
+            )
+        );
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        foreach ($this->filterPaths as $path) {
+            if (0 === strpos(realpath($feature->getFile()), $path)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return false This filter is designed to work only with features
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        return false;
+    }
+}

+ 63 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/RoleFilter.php

@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters features by their actors role.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class RoleFilter extends SimpleFilter
+{
+    protected $pattern;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $role Approved role wildcard
+     */
+    public function __construct($role)
+    {
+        $this->pattern = '/as an? ' . strtr(preg_quote($role, '/'), array(
+            '\*' => '.*',
+            '\?' => '.',
+            '\[' => '[',
+            '\]' => ']'
+        )) . '[$\n]/i';
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        return 1 === preg_match($this->pattern, $feature->getDescription());
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return false This filter is designed to work only with features
+     */
+    public function isScenarioMatch(ScenarioInterface $scenario)
+    {
+        return false;
+    }
+}

+ 56 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/SimpleFilter.php

@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Abstract filter class.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+abstract class SimpleFilter implements FilterInterface
+{
+    /**
+     * Filters feature according to the filter.
+     *
+     * @param FeatureNode $feature
+     *
+     * @return FeatureNode
+     */
+    public function filterFeature(FeatureNode $feature)
+    {
+        if ($this->isFeatureMatch($feature)) {
+            return $feature;
+        }
+
+        $scenarios = array();
+        foreach ($feature->getScenarios() as $scenario) {
+            if (!$this->isScenarioMatch($scenario)) {
+                continue;
+            }
+
+            $scenarios[] = $scenario;
+        }
+
+        return new FeatureNode(
+            $feature->getTitle(),
+            $feature->getDescription(),
+            $feature->getTags(),
+            $feature->getBackground(),
+            $scenarios,
+            $feature->getKeyword(),
+            $feature->getLanguage(),
+            $feature->getFile(),
+            $feature->getLine()
+        );
+    }
+}

+ 90 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Filter/TagFilter.php

@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+
+/**
+ * Filters scenarios by feature/scenario tag.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class TagFilter extends ComplexFilter
+{
+    protected $filterString;
+
+    /**
+     * Initializes filter.
+     *
+     * @param string $filterString Name filter string
+     */
+    public function __construct($filterString)
+    {
+        $this->filterString = trim($filterString);
+    }
+
+    /**
+     * Checks if Feature matches specified filter.
+     *
+     * @param FeatureNode $feature Feature instance
+     *
+     * @return Boolean
+     */
+    public function isFeatureMatch(FeatureNode $feature)
+    {
+        return $this->isTagsMatchCondition($feature->getTags());
+    }
+
+    /**
+     * Checks if scenario or outline matches specified filter.
+     *
+     * @param FeatureNode       $feature  Feature node instance
+     * @param ScenarioInterface $scenario Scenario or Outline node instance
+     *
+     * @return Boolean
+     */
+    public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario)
+    {
+        return $this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags()));
+    }
+
+    /**
+     * Checks that node matches condition.
+     *
+     * @param string[] $tags
+     *
+     * @return Boolean
+     */
+    protected function isTagsMatchCondition($tags)
+    {
+        $satisfies = true;
+
+        foreach (explode('&&', $this->filterString) as $andTags) {
+            $satisfiesComma = false;
+
+            foreach (explode(',', $andTags) as $tag) {
+                $tag = str_replace('@', '', trim($tag));
+
+                if ('~' === $tag[0]) {
+                    $tag = mb_substr($tag, 1, mb_strlen($tag, 'utf8') - 1, 'utf8');
+                    $satisfiesComma = !in_array($tag, $tags) || $satisfiesComma;
+                } else {
+                    $satisfiesComma = in_array($tag, $tags) || $satisfiesComma;
+                }
+            }
+
+            $satisfies = (false !== $satisfiesComma && $satisfies && $satisfiesComma) || false;
+        }
+
+        return $satisfies;
+    }
+}

+ 142 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Gherkin.php

@@ -0,0 +1,142 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin;
+
+use Behat\Gherkin\Filter\FeatureFilterInterface;
+use Behat\Gherkin\Filter\LineFilter;
+use Behat\Gherkin\Filter\LineRangeFilter;
+use Behat\Gherkin\Loader\FileLoaderInterface;
+use Behat\Gherkin\Loader\LoaderInterface;
+
+/**
+ * Gherkin manager.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class Gherkin
+{
+    const VERSION = '4.4-dev';
+
+    /**
+     * @var LoaderInterface[]
+     */
+    protected $loaders = array();
+    /**
+     * @var FeatureFilterInterface[]
+     */
+    protected $filters = array();
+
+    /**
+     * Adds loader to manager.
+     *
+     * @param LoaderInterface $loader Feature loader
+     */
+    public function addLoader(LoaderInterface $loader)
+    {
+        $this->loaders[] = $loader;
+    }
+
+    /**
+     * Adds filter to manager.
+     *
+     * @param FeatureFilterInterface $filter Feature filter
+     */
+    public function addFilter(FeatureFilterInterface $filter)
+    {
+        $this->filters[] = $filter;
+    }
+
+    /**
+     * Sets filters to the parser.
+     *
+     * @param FeatureFilterInterface[] $filters
+     */
+    public function setFilters(array $filters)
+    {
+        $this->filters = array();
+        array_map(array($this, 'addFilter'), $filters);
+    }
+
+    /**
+     * Sets base features path.
+     *
+     * @param string $path Loaders base path
+     */
+    public function setBasePath($path)
+    {
+        foreach ($this->loaders as $loader) {
+            if ($loader instanceof FileLoaderInterface) {
+                $loader->setBasePath($path);
+            }
+        }
+    }
+
+    /**
+     * Loads & filters resource with added loaders.
+     *
+     * @param mixed                    $resource Resource to load
+     * @param FeatureFilterInterface[] $filters  Additional filters
+     *
+     * @return array
+     */
+    public function load($resource, array $filters = array())
+    {
+        $filters = array_merge($this->filters, $filters);
+
+        $matches = array();
+        if (preg_match('/^(.*)\:(\d+)-(\d+|\*)$/', $resource, $matches)) {
+            $resource = $matches[1];
+            $filters[] = new LineRangeFilter($matches[2], $matches[3]);
+        } elseif (preg_match('/^(.*)\:(\d+)$/', $resource, $matches)) {
+            $resource = $matches[1];
+            $filters[] = new LineFilter($matches[2]);
+        }
+
+        $loader = $this->resolveLoader($resource);
+
+        if (null === $loader) {
+            return array();
+        }
+
+        $features = array();
+        foreach ($loader->load($resource) as $feature) {
+            foreach ($filters as $filter) {
+                $feature = $filter->filterFeature($feature);
+
+                if (!$feature->hasScenarios() && !$filter->isFeatureMatch($feature)) {
+                    continue 2;
+                }
+            }
+
+            $features[] = $feature;
+        }
+
+        return $features;
+    }
+
+    /**
+     * Resolves loader by resource.
+     *
+     * @param mixed $resource Resource to load
+     *
+     * @return LoaderInterface
+     */
+    public function resolveLoader($resource)
+    {
+        foreach ($this->loaders as $loader) {
+            if ($loader->supports($resource)) {
+                return $loader;
+            }
+        }
+
+        return null;
+    }
+}

+ 200 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Keywords/ArrayKeywords.php

@@ -0,0 +1,200 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Keywords;
+
+/**
+ * Array initializable keywords holder.
+ *
+ * $keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
+ *     'en' => array(
+ *         'feature'          => 'Feature',
+ *         'background'       => 'Background',
+ *         'scenario'         => 'Scenario',
+ *         'scenario_outline' => 'Scenario Outline|Scenario Template',
+ *         'examples'         => 'Examples|Scenarios',
+ *         'given'            => 'Given',
+ *         'when'             => 'When',
+ *         'then'             => 'Then',
+ *         'and'              => 'And',
+ *         'but'              => 'But'
+ *     ),
+ *     'ru' => array(
+ *         'feature'          => 'Функционал',
+ *         'background'       => 'Предыстория',
+ *         'scenario'         => 'Сценарий',
+ *         'scenario_outline' => 'Структура сценария',
+ *         'examples'         => 'Примеры',
+ *         'given'            => 'Допустим',
+ *         'when'             => 'Если',
+ *         'then'             => 'То',
+ *         'and'              => 'И',
+ *         'but'              => 'Но'
+ *     )
+ * ));
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class ArrayKeywords implements KeywordsInterface
+{
+    private $keywords = array();
+    private $keywordString = array();
+    private $language;
+
+    /**
+     * Initializes holder with keywords.
+     *
+     * @param array $keywords Keywords array
+     */
+    public function __construct(array $keywords)
+    {
+        $this->keywords = $keywords;
+    }
+
+    /**
+     * Sets keywords holder language.
+     *
+     * @param string $language Language name
+     */
+    public function setLanguage($language)
+    {
+        if (!isset($this->keywords[$language])) {
+            $this->language = 'en';
+        } else {
+            $this->language = $language;
+        }
+    }
+
+    /**
+     * Returns Feature keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getFeatureKeywords()
+    {
+        return $this->keywords[$this->language]['feature'];
+    }
+
+    /**
+     * Returns Background keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getBackgroundKeywords()
+    {
+        return $this->keywords[$this->language]['background'];
+    }
+
+    /**
+     * Returns Scenario keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getScenarioKeywords()
+    {
+        return $this->keywords[$this->language]['scenario'];
+    }
+
+    /**
+     * Returns Scenario Outline keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getOutlineKeywords()
+    {
+        return $this->keywords[$this->language]['scenario_outline'];
+    }
+
+    /**
+     * Returns Examples keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getExamplesKeywords()
+    {
+        return $this->keywords[$this->language]['examples'];
+    }
+
+    /**
+     * Returns Given keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getGivenKeywords()
+    {
+        return $this->keywords[$this->language]['given'];
+    }
+
+    /**
+     * Returns When keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getWhenKeywords()
+    {
+        return $this->keywords[$this->language]['when'];
+    }
+
+    /**
+     * Returns Then keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getThenKeywords()
+    {
+        return $this->keywords[$this->language]['then'];
+    }
+
+    /**
+     * Returns And keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getAndKeywords()
+    {
+        return $this->keywords[$this->language]['and'];
+    }
+
+    /**
+     * Returns But keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getButKeywords()
+    {
+        return $this->keywords[$this->language]['but'];
+    }
+
+    /**
+     * Returns all step keywords (Given, When, Then, And, But).
+     *
+     * @return string
+     */
+    public function getStepKeywords()
+    {
+        if (!isset($this->keywordString[$this->language])) {
+            $keywords = array_merge(
+                explode('|', $this->getGivenKeywords()),
+                explode('|', $this->getWhenKeywords()),
+                explode('|', $this->getThenKeywords()),
+                explode('|', $this->getAndKeywords()),
+                explode('|', $this->getButKeywords())
+            );
+
+            usort($keywords, function ($keyword1, $keyword2) {
+                return mb_strlen($keyword2, 'utf8') - mb_strlen($keyword1, 'utf8');
+            });
+
+            $this->keywordString[$this->language] = implode('|', $keywords);
+        }
+
+        return $this->keywordString[$this->language];
+    }
+}

+ 31 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Keywords/CachedArrayKeywords.php

@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Keywords;
+
+/**
+ * File initializable keywords holder.
+ *
+ * $keywords = new Behat\Gherkin\Keywords\CachedArrayKeywords($file);
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class CachedArrayKeywords extends ArrayKeywords
+{
+    /**
+     * Initializes holder with file.
+     *
+     * @param string $file Cached array path
+     */
+    public function __construct($file)
+    {
+        parent::__construct(include($file));
+    }
+}

+ 121 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Keywords/CucumberKeywords.php

@@ -0,0 +1,121 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Keywords;
+
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Cucumber-translations reader.
+ *
+ * $keywords = new Behat\Gherkin\Keywords\CucumberKeywords($i18nYmlPath);
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class CucumberKeywords extends ArrayKeywords
+{
+    /**
+     * Initializes holder with yaml string OR file.
+     *
+     * @param string $yaml Yaml string or file path
+     */
+    public function __construct($yaml)
+    {
+        // Handle filename explicitly for BC reasons, as Symfony Yaml 3.0 does not do it anymore
+        $file = null;
+        if (strpos($yaml, "\n") === false && is_file($yaml)) {
+            if (false === is_readable($yaml)) {
+                throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $yaml));
+            }
+
+            $file = $yaml;
+            $yaml = file_get_contents($file);
+        }
+
+        try {
+            $content = Yaml::parse($yaml);
+        } catch (ParseException $e) {
+            if ($file) {
+                $e->setParsedFile($file);
+            }
+
+            throw $e;
+        }
+
+        parent::__construct($content);
+    }
+
+    /**
+     * Returns Feature keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getGivenKeywords()
+    {
+        return $this->prepareStepString(parent::getGivenKeywords());
+    }
+
+    /**
+     * Returns When keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getWhenKeywords()
+    {
+        return $this->prepareStepString(parent::getWhenKeywords());
+    }
+
+    /**
+     * Returns Then keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getThenKeywords()
+    {
+        return $this->prepareStepString(parent::getThenKeywords());
+    }
+
+    /**
+     * Returns And keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getAndKeywords()
+    {
+        return $this->prepareStepString(parent::getAndKeywords());
+    }
+
+    /**
+     * Returns But keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getButKeywords()
+    {
+        return $this->prepareStepString(parent::getButKeywords());
+    }
+
+    /**
+     * Trim *| from the begining of the list.
+     *
+     * @param string $keywordsString Keywords string
+     *
+     * @return string
+     */
+    private function prepareStepString($keywordsString)
+    {
+        if (0 === mb_strpos($keywordsString, '*|', 0, 'UTF-8')) {
+            $keywordsString = mb_substr($keywordsString, 2, mb_strlen($keywordsString, 'utf8') - 2, 'utf8');
+        }
+
+        return $keywordsString;
+    }
+}

+ 365 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsDumper.php

@@ -0,0 +1,365 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Keywords;
+
+/**
+ * Gherkin keywords dumper.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class KeywordsDumper
+{
+    private $keywords;
+    private $keywordsDumper;
+
+    /**
+     * Initializes dumper.
+     *
+     * @param KeywordsInterface $keywords Keywords instance
+     */
+    public function __construct(KeywordsInterface $keywords)
+    {
+        $this->keywords = $keywords;
+        $this->keywordsDumper = array($this, 'dumpKeywords');
+    }
+
+    /**
+     * Sets keywords mapper function.
+     *
+     * Callable should accept 2 arguments (array $keywords and Boolean $isShort)
+     *
+     * @param callable $mapper Mapper function
+     */
+    public function setKeywordsDumperFunction($mapper)
+    {
+        $this->keywordsDumper = $mapper;
+    }
+
+    /**
+     * Defaults keywords dumper.
+     *
+     * @param array   $keywords Keywords list
+     * @param Boolean $isShort  Is short version
+     *
+     * @return string
+     */
+    public function dumpKeywords(array $keywords, $isShort)
+    {
+        if ($isShort) {
+            return 1 < count($keywords) ? '(' . implode('|', $keywords) . ')' : $keywords[0];
+        }
+
+        return $keywords[0];
+    }
+
+    /**
+     * Dumps keyworded feature into string.
+     *
+     * @param string  $language Keywords language
+     * @param Boolean $short    Dump short version
+     * @param bool    $excludeAsterisk
+     *
+     * @return string|array String for short version and array of features for extended
+     */
+    public function dump($language, $short = true, $excludeAsterisk = false)
+    {
+        $this->keywords->setLanguage($language);
+        $languageComment = '';
+        if ('en' !== $language) {
+            $languageComment = "# language: $language\n";
+        }
+
+        $keywords = explode('|', $this->keywords->getFeatureKeywords());
+
+        if ($short) {
+            $keywords = call_user_func($this->keywordsDumper, $keywords, $short);
+
+            return trim($languageComment . $this->dumpFeature($keywords, $short, $excludeAsterisk));
+        }
+
+        $features = array();
+        foreach ($keywords as $keyword) {
+            $keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
+            $features[] = trim($languageComment . $this->dumpFeature($keyword, $short, $excludeAsterisk));
+        }
+
+        return $features;
+    }
+
+    /**
+     * Dumps feature example.
+     *
+     * @param string  $keyword Item keyword
+     * @param Boolean $short   Dump short version?
+     *
+     * @return string
+     */
+    protected function dumpFeature($keyword, $short = true, $excludeAsterisk = false)
+    {
+        $dump = <<<GHERKIN
+{$keyword}: Internal operations
+  In order to stay secret
+  As a secret organization
+  We need to be able to erase past agents' memory
+
+
+GHERKIN;
+
+        // Background
+        $keywords = explode('|', $this->keywords->getBackgroundKeywords());
+        if ($short) {
+            $keywords = call_user_func($this->keywordsDumper, $keywords, $short);
+            $dump .= $this->dumpBackground($keywords, $short, $excludeAsterisk);
+        } else {
+            $keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short);
+            $dump .= $this->dumpBackground($keyword, $short, $excludeAsterisk);
+        }
+
+        // Scenario
+        $keywords = explode('|', $this->keywords->getScenarioKeywords());
+        if ($short) {
+            $keywords = call_user_func($this->keywordsDumper, $keywords, $short);
+            $dump .= $this->dumpScenario($keywords, $short, $excludeAsterisk);
+        } else {
+            foreach ($keywords as $keyword) {
+                $keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
+                $dump .= $this->dumpScenario($keyword, $short, $excludeAsterisk);
+            }
+        }
+
+        // Outline
+        $keywords = explode('|', $this->keywords->getOutlineKeywords());
+        if ($short) {
+            $keywords = call_user_func($this->keywordsDumper, $keywords, $short);
+            $dump .= $this->dumpOutline($keywords, $short, $excludeAsterisk);
+        } else {
+            foreach ($keywords as $keyword) {
+                $keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
+                $dump .= $this->dumpOutline($keyword, $short, $excludeAsterisk);
+            }
+        }
+
+        return $dump;
+    }
+
+    /**
+     * Dumps background example.
+     *
+     * @param string  $keyword Item keyword
+     * @param Boolean $short   Dump short version?
+     *
+     * @return string
+     */
+    protected function dumpBackground($keyword, $short = true, $excludeAsterisk = false)
+    {
+        $dump = <<<GHERKIN
+  {$keyword}:
+
+GHERKIN;
+
+        // Given
+        $dump .= $this->dumpStep(
+            $this->keywords->getGivenKeywords(),
+            'there is agent A',
+            $short,
+            $excludeAsterisk
+        );
+
+        // And
+        $dump .= $this->dumpStep(
+            $this->keywords->getAndKeywords(),
+            'there is agent B',
+            $short,
+            $excludeAsterisk
+        );
+
+        return $dump . "\n";
+    }
+
+    /**
+     * Dumps scenario example.
+     *
+     * @param string  $keyword Item keyword
+     * @param Boolean $short   Dump short version?
+     *
+     * @return string
+     */
+    protected function dumpScenario($keyword, $short = true, $excludeAsterisk = false)
+    {
+        $dump = <<<GHERKIN
+  {$keyword}: Erasing agent memory
+
+GHERKIN;
+
+        // Given
+        $dump .= $this->dumpStep(
+            $this->keywords->getGivenKeywords(),
+            'there is agent J',
+            $short,
+            $excludeAsterisk
+        );
+
+        // And
+        $dump .= $this->dumpStep(
+            $this->keywords->getAndKeywords(),
+            'there is agent K',
+            $short,
+            $excludeAsterisk
+        );
+
+        // When
+        $dump .= $this->dumpStep(
+            $this->keywords->getWhenKeywords(),
+            'I erase agent K\'s memory',
+            $short,
+            $excludeAsterisk
+        );
+
+        // Then
+        $dump .= $this->dumpStep(
+            $this->keywords->getThenKeywords(),
+            'there should be agent J',
+            $short,
+            $excludeAsterisk
+        );
+
+        // But
+        $dump .= $this->dumpStep(
+            $this->keywords->getButKeywords(),
+            'there should not be agent K',
+            $short,
+            $excludeAsterisk
+        );
+
+        return $dump . "\n";
+    }
+
+    /**
+     * Dumps outline example.
+     *
+     * @param string  $keyword Item keyword
+     * @param Boolean $short   Dump short version?
+     *
+     * @return string
+     */
+    protected function dumpOutline($keyword, $short = true, $excludeAsterisk = false)
+    {
+        $dump = <<<GHERKIN
+  {$keyword}: Erasing other agents' memory
+
+GHERKIN;
+
+        // Given
+        $dump .= $this->dumpStep(
+            $this->keywords->getGivenKeywords(),
+            'there is agent <agent1>',
+            $short,
+            $excludeAsterisk
+        );
+
+        // And
+        $dump .= $this->dumpStep(
+            $this->keywords->getAndKeywords(),
+            'there is agent <agent2>',
+            $short,
+            $excludeAsterisk
+        );
+
+        // When
+        $dump .= $this->dumpStep(
+            $this->keywords->getWhenKeywords(),
+            'I erase agent <agent2>\'s memory',
+            $short,
+            $excludeAsterisk
+        );
+
+        // Then
+        $dump .= $this->dumpStep(
+            $this->keywords->getThenKeywords(),
+            'there should be agent <agent1>',
+            $short,
+            $excludeAsterisk
+        );
+
+        // But
+        $dump .= $this->dumpStep(
+            $this->keywords->getButKeywords(),
+            'there should not be agent <agent2>',
+            $short,
+            $excludeAsterisk
+        );
+
+        $keywords = explode('|', $this->keywords->getExamplesKeywords());
+        if ($short) {
+            $keyword = call_user_func($this->keywordsDumper, $keywords, $short);
+        } else {
+            $keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short);
+        }
+
+        $dump .= <<<GHERKIN
+
+    {$keyword}:
+      | agent1 | agent2 |
+      | D      | M      |
+
+GHERKIN;
+
+        return $dump . "\n";
+    }
+
+    /**
+     * Dumps step example.
+     *
+     * @param string  $keywords Item keyword
+     * @param string  $text     Step text
+     * @param Boolean $short    Dump short version?
+     *
+     * @return string
+     */
+    protected function dumpStep($keywords, $text, $short = true, $excludeAsterisk = false)
+    {
+        $dump = '';
+
+        $keywords = explode('|', $keywords);
+        if ($short) {
+            $keywords = array_map(
+                function ($keyword) {
+                    return str_replace('<', '', $keyword);
+                },
+                $keywords
+            );
+            $keywords = call_user_func($this->keywordsDumper, $keywords, $short);
+            $dump .= <<<GHERKIN
+    {$keywords} {$text}
+
+GHERKIN;
+        } else {
+            foreach ($keywords as $keyword) {
+                if ($excludeAsterisk && '*' === $keyword) {
+                    continue;
+                }
+
+                $indent = ' ';
+                if (false !== mb_strpos($keyword, '<', 0, 'utf8')) {
+                    $keyword = mb_substr($keyword, 0, -1, 'utf8');
+                    $indent = '';
+                }
+                $keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
+                $dump .= <<<GHERKIN
+    {$keyword}{$indent}{$text}
+
+GHERKIN;
+            }
+        }
+
+        return $dump;
+    }
+}

+ 103 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Keywords/KeywordsInterface.php

@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Keywords;
+
+/**
+ * Keywords holder interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface KeywordsInterface
+{
+    /**
+     * Sets keywords holder language.
+     *
+     * @param string $language Language name
+     */
+    public function setLanguage($language);
+
+    /**
+     * Returns Feature keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getFeatureKeywords();
+
+    /**
+     * Returns Background keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getBackgroundKeywords();
+
+    /**
+     * Returns Scenario keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getScenarioKeywords();
+
+    /**
+     * Returns Scenario Outline keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getOutlineKeywords();
+
+    /**
+     * Returns Examples keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getExamplesKeywords();
+
+    /**
+     * Returns Given keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getGivenKeywords();
+
+    /**
+     * Returns When keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getWhenKeywords();
+
+    /**
+     * Returns Then keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getThenKeywords();
+
+    /**
+     * Returns And keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getAndKeywords();
+
+    /**
+     * Returns But keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getButKeywords();
+
+    /**
+     * Returns all step keywords (splitted by "|").
+     *
+     * @return string
+     */
+    public function getStepKeywords();
+}

+ 614 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Lexer.php

@@ -0,0 +1,614 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin;
+
+use Behat\Gherkin\Exception\LexerException;
+use Behat\Gherkin\Keywords\KeywordsInterface;
+
+/**
+ * Gherkin lexer.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class Lexer
+{
+    private $language;
+    private $lines;
+    private $linesCount;
+    private $line;
+    private $trimmedLine;
+    private $lineNumber;
+    private $eos;
+    private $keywords;
+    private $keywordsCache = array();
+    private $stepKeywordTypesCache = array();
+    private $deferredObjects = array();
+    private $deferredObjectsCount = 0;
+    private $stashedToken;
+    private $inPyString = false;
+    private $pyStringSwallow = 0;
+    private $featureStarted = false;
+    private $allowMultilineArguments = false;
+    private $allowSteps = false;
+
+    /**
+     * Initializes lexer.
+     *
+     * @param KeywordsInterface $keywords Keywords holder
+     */
+    public function __construct(KeywordsInterface $keywords)
+    {
+        $this->keywords = $keywords;
+    }
+
+    /**
+     * Sets lexer input.
+     *
+     * @param string $input    Input string
+     * @param string $language Language name
+     *
+     * @throws Exception\LexerException
+     */
+    public function analyse($input, $language = 'en')
+    {
+        // try to detect unsupported encoding
+        if ('UTF-8' !== mb_detect_encoding($input, 'UTF-8', true)) {
+            throw new LexerException('Feature file is not in UTF8 encoding');
+        }
+
+        $input = strtr($input, array("\r\n" => "\n", "\r" => "\n"));
+
+        $this->lines = explode("\n", $input);
+        $this->linesCount = count($this->lines);
+        $this->line = $this->lines[0];
+        $this->lineNumber = 1;
+        $this->trimmedLine = null;
+        $this->eos = false;
+
+        $this->deferredObjects = array();
+        $this->deferredObjectsCount = 0;
+        $this->stashedToken = null;
+        $this->inPyString = false;
+        $this->pyStringSwallow = 0;
+
+        $this->featureStarted = false;
+        $this->allowMultilineArguments = false;
+        $this->allowSteps = false;
+
+        $this->keywords->setLanguage($this->language = $language);
+        $this->keywordsCache = array();
+        $this->stepKeywordTypesCache = array();
+    }
+
+    /**
+     * Returns current lexer language.
+     *
+     * @return string
+     */
+    public function getLanguage()
+    {
+        return $this->language;
+    }
+
+    /**
+     * Returns next token or previously stashed one.
+     *
+     * @return array
+     */
+    public function getAdvancedToken()
+    {
+        return $this->getStashedToken() ?: $this->getNextToken();
+    }
+
+    /**
+     * Defers token.
+     *
+     * @param array $token Token to defer
+     */
+    public function deferToken(array $token)
+    {
+        $token['deferred'] = true;
+        $this->deferredObjects[] = $token;
+        ++$this->deferredObjectsCount;
+    }
+
+    /**
+     * Predicts for number of tokens.
+     *
+     * @return array
+     */
+    public function predictToken()
+    {
+        if (null === $this->stashedToken) {
+            $this->stashedToken = $this->getNextToken();
+        }
+
+        return $this->stashedToken;
+    }
+
+    /**
+     * Constructs token with specified parameters.
+     *
+     * @param string $type  Token type
+     * @param string $value Token value
+     *
+     * @return array
+     */
+    public function takeToken($type, $value = null)
+    {
+        return array(
+            'type'     => $type,
+            'line'     => $this->lineNumber,
+            'value'    => $value ?: null,
+            'deferred' => false
+        );
+    }
+
+    /**
+     * Consumes line from input & increments line counter.
+     */
+    protected function consumeLine()
+    {
+        ++$this->lineNumber;
+
+        if (($this->lineNumber - 1) === $this->linesCount) {
+            $this->eos = true;
+
+            return;
+        }
+
+        $this->line = $this->lines[$this->lineNumber - 1];
+        $this->trimmedLine = null;
+    }
+
+    /**
+     * Returns trimmed version of line.
+     *
+     * @return string
+     */
+    protected function getTrimmedLine()
+    {
+        return null !== $this->trimmedLine ? $this->trimmedLine : $this->trimmedLine = trim($this->line);
+    }
+
+    /**
+     * Returns stashed token or null if hasn't.
+     *
+     * @return array|null
+     */
+    protected function getStashedToken()
+    {
+        $stashedToken = $this->stashedToken;
+        $this->stashedToken = null;
+
+        return $stashedToken;
+    }
+
+    /**
+     * Returns deferred token or null if hasn't.
+     *
+     * @return array|null
+     */
+    protected function getDeferredToken()
+    {
+        if (!$this->deferredObjectsCount) {
+            return null;
+        }
+
+        --$this->deferredObjectsCount;
+
+        return array_shift($this->deferredObjects);
+    }
+
+    /**
+     * Returns next token from input.
+     *
+     * @return array
+     */
+    protected function getNextToken()
+    {
+        return $this->getDeferredToken()
+            ?: $this->scanEOS()
+            ?: $this->scanLanguage()
+            ?: $this->scanComment()
+            ?: $this->scanPyStringOp()
+            ?: $this->scanPyStringContent()
+            ?: $this->scanStep()
+            ?: $this->scanScenario()
+            ?: $this->scanBackground()
+            ?: $this->scanOutline()
+            ?: $this->scanExamples()
+            ?: $this->scanFeature()
+            ?: $this->scanTags()
+            ?: $this->scanTableRow()
+            ?: $this->scanNewline()
+            ?: $this->scanText();
+    }
+
+    /**
+     * Scans for token with specified regex.
+     *
+     * @param string $regex Regular expression
+     * @param string $type  Expected token type
+     *
+     * @return null|array
+     */
+    protected function scanInput($regex, $type)
+    {
+        if (!preg_match($regex, $this->line, $matches)) {
+            return null;
+        }
+
+        $token = $this->takeToken($type, $matches[1]);
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans for token with specified keywords.
+     *
+     * @param string $keywords Keywords (splitted with |)
+     * @param string $type     Expected token type
+     *
+     * @return null|array
+     */
+    protected function scanInputForKeywords($keywords, $type)
+    {
+        if (!preg_match('/^(\s*)(' . $keywords . '):\s*(.*)/u', $this->line, $matches)) {
+            return null;
+        }
+
+        $token = $this->takeToken($type, $matches[3]);
+        $token['keyword'] = $matches[2];
+        $token['indent'] = mb_strlen($matches[1], 'utf8');
+
+        $this->consumeLine();
+
+        // turn off language searching
+        if ('Feature' === $type) {
+            $this->featureStarted = true;
+        }
+
+        // turn off PyString and Table searching
+        if ('Feature' === $type || 'Scenario' === $type || 'Outline' === $type) {
+            $this->allowMultilineArguments = false;
+        } elseif ('Examples' === $type) {
+            $this->allowMultilineArguments = true;
+        }
+
+        // turn on steps searching
+        if ('Scenario' === $type || 'Background' === $type || 'Outline' === $type) {
+            $this->allowSteps = true;
+        }
+
+        return $token;
+    }
+
+    /**
+     * Scans EOS from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanEOS()
+    {
+        if (!$this->eos) {
+            return null;
+        }
+
+        return $this->takeToken('EOS');
+    }
+
+    /**
+     * Returns keywords for provided type.
+     *
+     * @param string $type Keyword type
+     *
+     * @return string
+     */
+    protected function getKeywords($type)
+    {
+        if (!isset($this->keywordsCache[$type])) {
+            $getter = 'get' . $type . 'Keywords';
+            $keywords = $this->keywords->$getter();
+
+            if ('Step' === $type) {
+                $padded = array();
+                foreach (explode('|', $keywords) as $keyword) {
+                    $padded[] = false !== mb_strpos($keyword, '<', 0, 'utf8')
+                        ? preg_quote(mb_substr($keyword, 0, -1, 'utf8'), '/') . '\s*'
+                        : preg_quote($keyword, '/') . '\s+';
+                }
+
+                $keywords = implode('|', $padded);
+            }
+
+            $this->keywordsCache[$type] = $keywords;
+        }
+
+        return $this->keywordsCache[$type];
+    }
+
+    /**
+     * Scans Feature from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanFeature()
+    {
+        return $this->scanInputForKeywords($this->getKeywords('Feature'), 'Feature');
+    }
+
+    /**
+     * Scans Background from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanBackground()
+    {
+        return $this->scanInputForKeywords($this->getKeywords('Background'), 'Background');
+    }
+
+    /**
+     * Scans Scenario from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanScenario()
+    {
+        return $this->scanInputForKeywords($this->getKeywords('Scenario'), 'Scenario');
+    }
+
+    /**
+     * Scans Scenario Outline from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanOutline()
+    {
+        return $this->scanInputForKeywords($this->getKeywords('Outline'), 'Outline');
+    }
+
+    /**
+     * Scans Scenario Outline Examples from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanExamples()
+    {
+        return $this->scanInputForKeywords($this->getKeywords('Examples'), 'Examples');
+    }
+
+    /**
+     * Scans Step from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanStep()
+    {
+        if (!$this->allowSteps) {
+            return null;
+        }
+
+        $keywords = $this->getKeywords('Step');
+        if (!preg_match('/^\s*(' . $keywords . ')([^\s].+)/u', $this->line, $matches)) {
+            return null;
+        }
+
+        $keyword = trim($matches[1]);
+        $token = $this->takeToken('Step', $keyword);
+        $token['keyword_type'] = $this->getStepKeywordType($keyword);
+        $token['text'] = $matches[2];
+
+        $this->consumeLine();
+        $this->allowMultilineArguments = true;
+
+        return $token;
+    }
+
+    /**
+     * Scans PyString from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanPyStringOp()
+    {
+        if (!$this->allowMultilineArguments) {
+            return null;
+        }
+
+        if (false === ($pos = mb_strpos($this->line, '"""', 0, 'utf8'))) {
+            return null;
+        }
+
+        $this->inPyString = !$this->inPyString;
+        $token = $this->takeToken('PyStringOp');
+        $this->pyStringSwallow = $pos;
+
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans PyString content.
+     *
+     * @return null|array
+     */
+    protected function scanPyStringContent()
+    {
+        if (!$this->inPyString) {
+            return null;
+        }
+
+        $token = $this->scanText();
+        // swallow trailing spaces
+        $token['value'] = preg_replace('/^\s{0,' . $this->pyStringSwallow . '}/u', '', $token['value']);
+
+        return $token;
+    }
+
+    /**
+     * Scans Table Row from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanTableRow()
+    {
+        if (!$this->allowMultilineArguments) {
+            return null;
+        }
+
+        $line = $this->getTrimmedLine();
+        if (!isset($line[0]) || '|' !== $line[0] || '|' !== substr($line, -1)) {
+            return null;
+        }
+
+        $token = $this->takeToken('TableRow');
+        $line = mb_substr($line, 1, mb_strlen($line, 'utf8') - 2, 'utf8');
+        $columns = array_map(function ($column) {
+            return trim(str_replace('\\|', '|', $column));
+        }, preg_split('/(?<!\\\)\|/u', $line));
+        $token['columns'] = $columns;
+
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans Tags from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanTags()
+    {
+        $line = $this->getTrimmedLine();
+        if (!isset($line[0]) || '@' !== $line[0]) {
+            return null;
+        }
+
+        $token = $this->takeToken('Tag');
+        $tags = explode('@', mb_substr($line, 1, mb_strlen($line, 'utf8') - 1, 'utf8'));
+        $tags = array_map('trim', $tags);
+        $token['tags'] = $tags;
+
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans Language specifier from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanLanguage()
+    {
+        if ($this->featureStarted) {
+            return null;
+        }
+
+        if ($this->inPyString) {
+            return null;
+        }
+
+        if (0 !== mb_strpos(ltrim($this->line), '#', 0, 'utf8')) {
+            return null;
+        }
+
+        return $this->scanInput('/^\s*\#\s*language:\s*([\w_\-]+)\s*$/', 'Language');
+    }
+
+    /**
+     * Scans Comment from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanComment()
+    {
+        if ($this->inPyString) {
+            return null;
+        }
+
+        $line = $this->getTrimmedLine();
+        if (0 !== mb_strpos($line, '#', 0, 'utf8')) {
+            return null;
+        }
+
+        $token = $this->takeToken('Comment', $line);
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans Newline from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanNewline()
+    {
+        if ('' !== $this->getTrimmedLine()) {
+            return null;
+        }
+
+        $token = $this->takeToken('Newline', mb_strlen($this->line, 'utf8'));
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Scans text from input & returns it if found.
+     *
+     * @return null|array
+     */
+    protected function scanText()
+    {
+        $token = $this->takeToken('Text', $this->line);
+        $this->consumeLine();
+
+        return $token;
+    }
+
+    /**
+     * Returns step type keyword (Given, When, Then, etc.).
+     *
+     * @param string $native Step keyword in provided language
+     * @return string
+     */
+    private function getStepKeywordType($native)
+    {
+        // Consider "*" as a AND keyword so that it is normalized to the previous step type
+        if ('*' === $native) {
+            return 'And';
+        }
+
+        if (empty($this->stepKeywordTypesCache)) {
+            $this->stepKeywordTypesCache = array(
+                'Given' => explode('|', $this->keywords->getGivenKeywords()),
+                'When' => explode('|', $this->keywords->getWhenKeywords()),
+                'Then' => explode('|', $this->keywords->getThenKeywords()),
+                'And' => explode('|', $this->keywords->getAndKeywords()),
+                'But' => explode('|', $this->keywords->getButKeywords())
+            );
+        }
+
+        foreach ($this->stepKeywordTypesCache as $type => $keywords) {
+            if (in_array($native, $keywords) || in_array($native . '<', $keywords)) {
+                return $type;
+            }
+        }
+
+        return 'Given';
+    }
+}

+ 72 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/AbstractFileLoader.php

@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+/**
+ * Abstract filesystem loader.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+abstract class AbstractFileLoader implements FileLoaderInterface
+{
+    protected $basePath;
+
+    /**
+     * Sets base features path.
+     *
+     * @param string $path Base loader path
+     */
+    public function setBasePath($path)
+    {
+        $this->basePath = realpath($path);
+    }
+
+    /**
+     * Finds relative path for provided absolute (relative to base features path).
+     *
+     * @param string $path Absolute path
+     *
+     * @return string
+     */
+    protected function findRelativePath($path)
+    {
+        if (null !== $this->basePath) {
+            return strtr($path, array($this->basePath . DIRECTORY_SEPARATOR => ''));
+        }
+
+        return $path;
+    }
+
+    /**
+     * Finds absolute path for provided relative (relative to base features path).
+     *
+     * @param string $path Relative path
+     *
+     * @return string
+     */
+    protected function findAbsolutePath($path)
+    {
+        if (is_file($path) || is_dir($path)) {
+            return realpath($path);
+        }
+
+        if (null === $this->basePath) {
+            return false;
+        }
+
+        if (is_file($this->basePath . DIRECTORY_SEPARATOR . $path)
+               || is_dir($this->basePath . DIRECTORY_SEPARATOR . $path)) {
+            return realpath($this->basePath . DIRECTORY_SEPARATOR . $path);
+        }
+
+        return false;
+    }
+}

+ 269 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/ArrayLoader.php

@@ -0,0 +1,269 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+use Behat\Gherkin\Node\BackgroundNode;
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\PyStringNode;
+use Behat\Gherkin\Node\ScenarioNode;
+use Behat\Gherkin\Node\StepNode;
+use Behat\Gherkin\Node\TableNode;
+
+/**
+ * From-array loader.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class ArrayLoader implements LoaderInterface
+{
+    /**
+     * Checks if current loader supports provided resource.
+     *
+     * @param mixed $resource Resource to load
+     *
+     * @return Boolean
+     */
+    public function supports($resource)
+    {
+        return is_array($resource) && (isset($resource['features']) || isset($resource['feature']));
+    }
+
+    /**
+     * Loads features from provided resource.
+     *
+     * @param mixed $resource Resource to load
+     *
+     * @return FeatureNode[]
+     */
+    public function load($resource)
+    {
+        $features = array();
+
+        if (isset($resource['features'])) {
+            foreach ($resource['features'] as $iterator => $hash) {
+                $feature = $this->loadFeatureHash($hash, $iterator);
+                $features[] = $feature;
+            }
+        } elseif (isset($resource['feature'])) {
+            $feature = $this->loadFeatureHash($resource['feature']);
+            $features[] = $feature;
+        }
+
+        return $features;
+    }
+
+    /**
+     * Loads feature from provided feature hash.
+     *
+     * @param array   $hash Feature hash
+     * @param integer $line
+     *
+     * @return FeatureNode
+     */
+    protected function loadFeatureHash(array $hash, $line = 0)
+    {
+        $hash = array_merge(
+            array(
+                'title' => null,
+                'description' => null,
+                'tags' => array(),
+                'keyword' => 'Feature',
+                'language' => 'en',
+                'line' => $line,
+                'scenarios' => array(),
+            ),
+            $hash
+        );
+        $background = isset($hash['background']) ? $this->loadBackgroundHash($hash['background']) : null;
+
+        $scenarios = array();
+        foreach ((array) $hash['scenarios'] as $scenarioIterator => $scenarioHash) {
+            if (isset($scenarioHash['type']) && 'outline' === $scenarioHash['type']) {
+                $scenarios[] = $this->loadOutlineHash($scenarioHash, $scenarioIterator);
+            } else {
+                $scenarios[] = $this->loadScenarioHash($scenarioHash, $scenarioIterator);
+            }
+        }
+
+        return new FeatureNode($hash['title'], $hash['description'], $hash['tags'], $background, $scenarios, $hash['keyword'], $hash['language'], null, $hash['line']);
+    }
+
+    /**
+     * Loads background from provided hash.
+     *
+     * @param array $hash Background hash
+     *
+     * @return BackgroundNode
+     */
+    protected function loadBackgroundHash(array $hash)
+    {
+        $hash = array_merge(
+            array(
+                'title' => null,
+                'keyword' => 'Background',
+                'line' => 0,
+                'steps' => array(),
+            ),
+            $hash
+        );
+
+        $steps = $this->loadStepsHash($hash['steps']);
+
+        return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line']);
+    }
+
+    /**
+     * Loads scenario from provided scenario hash.
+     *
+     * @param array   $hash Scenario hash
+     * @param integer $line Scenario definition line
+     *
+     * @return ScenarioNode
+     */
+    protected function loadScenarioHash(array $hash, $line = 0)
+    {
+        $hash = array_merge(
+            array(
+                'title' => null,
+                'tags' => array(),
+                'keyword' => 'Scenario',
+                'line' => $line,
+                'steps' => array(),
+            ),
+            $hash
+        );
+
+        $steps = $this->loadStepsHash($hash['steps']);
+
+        return new ScenarioNode($hash['title'], $hash['tags'], $steps, $hash['keyword'], $hash['line']);
+    }
+
+    /**
+     * Loads outline from provided outline hash.
+     *
+     * @param array   $hash Outline hash
+     * @param integer $line Outline definition line
+     *
+     * @return OutlineNode
+     */
+    protected function loadOutlineHash(array $hash, $line = 0)
+    {
+        $hash = array_merge(
+            array(
+                'title' => null,
+                'tags' => array(),
+                'keyword' => 'Scenario Outline',
+                'line' => $line,
+                'steps' => array(),
+                'examples' => array(),
+            ),
+            $hash
+        );
+
+        $steps = $this->loadStepsHash($hash['steps']);
+
+        if (isset($hash['examples']['keyword'])) {
+            $examplesKeyword = $hash['examples']['keyword'];
+            unset($hash['examples']['keyword']);
+        } else {
+            $examplesKeyword = 'Examples';
+        }
+
+        $examples = new ExampleTableNode($hash['examples'], $examplesKeyword);
+
+        return new OutlineNode($hash['title'], $hash['tags'], $steps, $examples, $hash['keyword'], $hash['line']);
+    }
+
+    /**
+     * Loads steps from provided hash.
+     *
+     * @param array $hash
+     *
+     * @return StepNode[]
+     */
+    private function loadStepsHash(array $hash)
+    {
+        $steps = array();
+        foreach ($hash as $stepIterator => $stepHash) {
+            $steps[] = $this->loadStepHash($stepHash, $stepIterator);
+        }
+
+        return $steps;
+    }
+
+    /**
+     * Loads step from provided hash.
+     *
+     * @param array   $hash Step hash
+     * @param integer $line Step definition line
+     *
+     * @return StepNode
+     */
+    protected function loadStepHash(array $hash, $line = 0)
+    {
+        $hash = array_merge(
+            array(
+                'keyword_type' => 'Given',
+                'type' => 'Given',
+                'text' => null,
+                'keyword' => 'Scenario',
+                'line' => $line,
+                'arguments' => array(),
+            ),
+            $hash
+        );
+
+        $arguments = array();
+        foreach ($hash['arguments'] as $argumentHash) {
+            if ('table' === $argumentHash['type']) {
+                $arguments[] = $this->loadTableHash($argumentHash['rows']);
+            } elseif ('pystring' === $argumentHash['type']) {
+                $arguments[] = $this->loadPyStringHash($argumentHash, $hash['line'] + 1);
+            }
+        }
+
+        return new StepNode($hash['type'], $hash['text'], $arguments, $hash['line'], $hash['keyword_type']);
+    }
+
+    /**
+     * Loads table from provided hash.
+     *
+     * @param array $hash Table hash
+     *
+     * @return TableNode
+     */
+    protected function loadTableHash(array $hash)
+    {
+        return new TableNode($hash);
+    }
+
+    /**
+     * Loads PyString from provided hash.
+     *
+     * @param array   $hash PyString hash
+     * @param integer $line
+     *
+     * @return PyStringNode
+     */
+    protected function loadPyStringHash(array $hash, $line = 0)
+    {
+        $line = isset($hash['line']) ? $hash['line'] : $line;
+
+        $strings = array();
+        foreach (explode("\n", $hash['text']) as $string) {
+            $strings[] = $string;
+        }
+
+        return new PyStringNode($strings, $line);
+    }
+}

+ 80 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/DirectoryLoader.php

@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+use Behat\Gherkin\Gherkin;
+use Behat\Gherkin\Node\FeatureNode;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+/**
+ * Directory contents loader.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class DirectoryLoader extends AbstractFileLoader
+{
+    protected $gherkin;
+
+    /**
+     * Initializes loader.
+     *
+     * @param Gherkin $gherkin Gherkin manager
+     */
+    public function __construct(Gherkin $gherkin)
+    {
+        $this->gherkin = $gherkin;
+    }
+
+    /**
+     * Checks if current loader supports provided resource.
+     *
+     * @param mixed $path Resource to load
+     *
+     * @return Boolean
+     */
+    public function supports($path)
+    {
+        return is_string($path)
+        && is_dir($this->findAbsolutePath($path));
+    }
+
+    /**
+     * Loads features from provided resource.
+     *
+     * @param string $path Resource to load
+     *
+     * @return FeatureNode[]
+     */
+    public function load($path)
+    {
+        $path = $this->findAbsolutePath($path);
+
+        $iterator = new RecursiveIteratorIterator(
+            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
+        );
+        $paths = array_map('strval', iterator_to_array($iterator));
+        uasort($paths, 'strnatcasecmp');
+
+        $features = array();
+
+        foreach ($paths as $path) {
+            $path = (string) $path;
+            $loader = $this->gherkin->resolveLoader($path);
+
+            if (null !== $loader) {
+                $features = array_merge($features, $loader->load($path));
+            }
+        }
+
+        return $features;
+    }
+}

+ 26 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/FileLoaderInterface.php

@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+/**
+ * File Loader interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface FileLoaderInterface extends LoaderInterface
+{
+    /**
+     * Sets base features path.
+     *
+     * @param string $path Base loader path
+     */
+    public function setBasePath($path);
+}

+ 102 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/GherkinFileLoader.php

@@ -0,0 +1,102 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+use Behat\Gherkin\Cache\CacheInterface;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Parser;
+
+/**
+ * Gherkin *.feature files loader.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class GherkinFileLoader extends AbstractFileLoader
+{
+    protected $parser;
+    protected $cache;
+
+    /**
+     * Initializes loader.
+     *
+     * @param Parser         $parser Parser
+     * @param CacheInterface $cache  Cache layer
+     */
+    public function __construct(Parser $parser, CacheInterface $cache = null)
+    {
+        $this->parser = $parser;
+        $this->cache = $cache;
+    }
+
+    /**
+     * Sets cache layer.
+     *
+     * @param CacheInterface $cache Cache layer
+     */
+    public function setCache(CacheInterface $cache)
+    {
+        $this->cache = $cache;
+    }
+
+    /**
+     * Checks if current loader supports provided resource.
+     *
+     * @param mixed $path Resource to load
+     *
+     * @return Boolean
+     */
+    public function supports($path)
+    {
+        return is_string($path)
+        && is_file($absolute = $this->findAbsolutePath($path))
+        && 'feature' === pathinfo($absolute, PATHINFO_EXTENSION);
+    }
+
+    /**
+     * Loads features from provided resource.
+     *
+     * @param string $path Resource to load
+     *
+     * @return FeatureNode[]
+     */
+    public function load($path)
+    {
+        $path = $this->findAbsolutePath($path);
+
+        if ($this->cache) {
+            if ($this->cache->isFresh($path, filemtime($path))) {
+                $feature = $this->cache->read($path);
+            } elseif (null !== $feature = $this->parseFeature($path)) {
+                $this->cache->write($path, $feature);
+            }
+        } else {
+            $feature = $this->parseFeature($path);
+        }
+
+        return null !== $feature ? array($feature) : array();
+    }
+
+    /**
+     * Parses feature at provided absolute path.
+     *
+     * @param string $path Feature path
+     *
+     * @return FeatureNode
+     */
+    protected function parseFeature($path)
+    {
+        $filename = $this->findRelativePath($path);
+        $content = file_get_contents($path);
+        $feature = $this->parser->parse($content, $filename);
+
+        return $feature;
+    }
+}

+ 39 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/LoaderInterface.php

@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+use Behat\Gherkin\Node\FeatureNode;
+
+/**
+ * Loader interface.
+ *
+ * @author      Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface LoaderInterface
+{
+    /**
+     * Checks if current loader supports provided resource.
+     *
+     * @param mixed $resource Resource to load
+     *
+     * @return Boolean
+     */
+    public function supports($resource);
+
+    /**
+     * Loads features from provided resource.
+     *
+     * @param mixed $resource Resource to load
+     *
+     * @return FeatureNode[]
+     */
+    public function load($resource);
+}

+ 73 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Loader/YamlFileLoader.php

@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Loader;
+
+use Behat\Gherkin\Node\FeatureNode;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Yaml files loader.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class YamlFileLoader extends AbstractFileLoader
+{
+    private $loader;
+
+    public function __construct()
+    {
+        $this->loader = new ArrayLoader();
+    }
+
+    /**
+     * Checks if current loader supports provided resource.
+     *
+     * @param mixed $path Resource to load
+     *
+     * @return Boolean
+     */
+    public function supports($path)
+    {
+        return is_string($path)
+            && is_file($absolute = $this->findAbsolutePath($path))
+            && 'yml' === pathinfo($absolute, PATHINFO_EXTENSION);
+    }
+
+    /**
+     * Loads features from provided resource.
+     *
+     * @param string $path Resource to load
+     *
+     * @return FeatureNode[]
+     */
+    public function load($path)
+    {
+        $path = $this->findAbsolutePath($path);
+        $hash = Yaml::parse(file_get_contents($path));
+
+        $features = $this->loader->load($hash);
+        $filename = $this->findRelativePath($path);
+
+        return array_map(function (FeatureNode $feature) use ($filename) {
+            return new FeatureNode(
+                $feature->getTitle(),
+                $feature->getDescription(),
+                $feature->getTags(),
+                $feature->getBackground(),
+                $feature->getScenarios(),
+                $feature->getKeyword(),
+                $feature->getLanguage(),
+                $filename,
+                $feature->getLine()
+            );
+        }, $features);
+    }
+}

+ 20 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ArgumentInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin arguments interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface ArgumentInterface extends NodeInterface
+{
+}

+ 112 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/BackgroundNode.php

@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Background.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class BackgroundNode implements ScenarioLikeInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+    /**
+     * @var StepNode[]
+     */
+    private $steps = array();
+    /**
+     * @var string
+     */
+    private $keyword;
+    /**
+     * @var integer
+     */
+    private $line;
+
+    /**
+     * Initializes background.
+     *
+     * @param null|string $title
+     * @param StepNode[]  $steps
+     * @param string      $keyword
+     * @param integer     $line
+     */
+    public function __construct($title, array $steps, $keyword, $line)
+    {
+        $this->title = $title;
+        $this->steps = $steps;
+        $this->keyword = $keyword;
+        $this->line = $line;
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Background';
+    }
+
+    /**
+     * Returns background title.
+     *
+     * @return null|string
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Checks if background has steps.
+     *
+     * @return Boolean
+     */
+    public function hasSteps()
+    {
+        return 0 < count($this->steps);
+    }
+
+    /**
+     * Returns background steps.
+     *
+     * @return StepNode[]
+     */
+    public function getSteps()
+    {
+        return $this->steps;
+    }
+
+    /**
+     * Returns background keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+
+    /**
+     * Returns background declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+}

+ 274 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleNode.php

@@ -0,0 +1,274 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Outline Example.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class ExampleNode implements ScenarioInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+    /**
+     * @var string[]
+     */
+    private $tags;
+    /**
+     * @var StepNode[]
+     */
+    private $outlineSteps;
+    /**
+     * @var string[]
+     */
+    private $tokens;
+    /**
+     * @var integer
+     */
+    private $line;
+    /**
+     * @var null|StepNode[]
+     */
+    private $steps;
+    /**
+     * @var string
+     */
+    private $outlineTitle;
+
+    /**
+     * Initializes outline.
+     *
+     * @param string      $title
+     * @param string[]    $tags
+     * @param StepNode[]  $outlineSteps
+     * @param string[]    $tokens
+     * @param integer     $line
+     * @param string|null $outlineTitle
+     */
+    public function __construct($title, array $tags, $outlineSteps, array $tokens, $line, $outlineTitle = null)
+    {
+        $this->title = $title;
+        $this->tags = $tags;
+        $this->outlineSteps = $outlineSteps;
+        $this->tokens = $tokens;
+        $this->line = $line;
+        $this->outlineTitle = $outlineTitle;
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Example';
+    }
+
+    /**
+     * Returns node keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->getNodeType();
+    }
+
+    /**
+     * Returns example title.
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Checks if outline is tagged with tag.
+     *
+     * @param string $tag
+     *
+     * @return Boolean
+     */
+    public function hasTag($tag)
+    {
+        return in_array($tag, $this->getTags());
+    }
+
+    /**
+     * Checks if outline has tags (both inherited from feature and own).
+     *
+     * @return Boolean
+     */
+    public function hasTags()
+    {
+        return 0 < count($this->getTags());
+    }
+
+    /**
+     * Returns outline tags (including inherited from feature).
+     *
+     * @return string[]
+     */
+    public function getTags()
+    {
+        return $this->tags;
+    }
+
+    /**
+     * Checks if outline has steps.
+     *
+     * @return Boolean
+     */
+    public function hasSteps()
+    {
+        return 0 < count($this->outlineSteps);
+    }
+
+    /**
+     * Returns outline steps.
+     *
+     * @return StepNode[]
+     */
+    public function getSteps()
+    {
+        return $this->steps = $this->steps ? : $this->createExampleSteps();
+    }
+
+    /**
+     * Returns example tokens.
+     *
+     * @return string[]
+     */
+    public function getTokens()
+    {
+        return $this->tokens;
+    }
+
+    /**
+     * Returns outline declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+
+    /**
+     * Returns outline title.
+     *
+     * @return string
+     */
+    public function getOutlineTitle()
+    {
+        return $this->outlineTitle;
+    }
+
+    /**
+     * Creates steps for this example from abstract outline steps.
+     *
+     * @return StepNode[]
+     */
+    protected function createExampleSteps()
+    {
+        $steps = array();
+        foreach ($this->outlineSteps as $outlineStep) {
+            $keyword = $outlineStep->getKeyword();
+            $keywordType = $outlineStep->getKeywordType();
+            $text = $this->replaceTextTokens($outlineStep->getText());
+            $args = $this->replaceArgumentsTokens($outlineStep->getArguments());
+            $line = $outlineStep->getLine();
+
+            $steps[] = new StepNode($keyword, $text, $args, $line, $keywordType);
+        }
+
+        return $steps;
+    }
+
+    /**
+     * Replaces tokens in arguments with row values.
+     *
+     * @param ArgumentInterface[] $arguments
+     *
+     * @return ArgumentInterface[]
+     */
+    protected function replaceArgumentsTokens(array $arguments)
+    {
+        foreach ($arguments as $num => $argument) {
+            if ($argument instanceof TableNode) {
+                $arguments[$num] = $this->replaceTableArgumentTokens($argument);
+            }
+            if ($argument instanceof PyStringNode) {
+                $arguments[$num] = $this->replacePyStringArgumentTokens($argument);
+            }
+        }
+
+        return $arguments;
+    }
+
+    /**
+     * Replaces tokens in table with row values.
+     *
+     * @param TableNode $argument
+     *
+     * @return TableNode
+     */
+    protected function replaceTableArgumentTokens(TableNode $argument)
+    {
+        $table = $argument->getTable();
+        foreach ($table as $line => $row) {
+            foreach (array_keys($row) as $col) {
+                $table[$line][$col] = $this->replaceTextTokens($table[$line][$col]);
+            }
+        }
+
+        return new TableNode($table);
+    }
+
+    /**
+     * Replaces tokens in PyString with row values.
+     *
+     * @param PyStringNode $argument
+     *
+     * @return PyStringNode
+     */
+    protected function replacePyStringArgumentTokens(PyStringNode $argument)
+    {
+        $strings = $argument->getStrings();
+        foreach ($strings as $line => $string) {
+            $strings[$line] = $this->replaceTextTokens($strings[$line]);
+        }
+
+        return new PyStringNode($strings, $argument->getLine());
+    }
+
+    /**
+     * Replaces tokens in text with row values.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    protected function replaceTextTokens($text)
+    {
+        foreach ($this->tokens as $key => $val) {
+            $text = str_replace('<' . $key . '>', $val, $text);
+        }
+
+        return $text;
+    }
+}

+ 57 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ExampleTableNode.php

@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Outline Example Table.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class ExampleTableNode extends TableNode
+{
+    /**
+     * @var string
+     */
+    private $keyword;
+
+    /**
+     * Initializes example table.
+     *
+     * @param array  $table   Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
+     * @param string $keyword
+     */
+    public function __construct(array $table, $keyword)
+    {
+        $this->keyword = $keyword;
+
+        parent::__construct($table);
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'ExampleTable';
+    }
+
+    /**
+     * Returns example table keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+}

+ 243 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/FeatureNode.php

@@ -0,0 +1,243 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Feature.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class FeatureNode implements KeywordNodeInterface, TaggedNodeInterface
+{
+    /**
+     * @var null|string
+     */
+    private $title;
+    /**
+     * @var null|string
+     */
+    private $description;
+    /**
+     * @var string[]
+     */
+    private $tags = array();
+    /**
+     * @var null|BackgroundNode
+     */
+    private $background;
+    /**
+     * @var ScenarioInterface[]
+     */
+    private $scenarios = array();
+    /**
+     * @var string
+     */
+    private $keyword;
+    /**
+     * @var string
+     */
+    private $language;
+    /**
+     * @var null|string
+     */
+    private $file;
+    /**
+     * @var integer
+     */
+    private $line;
+
+    /**
+     * Initializes feature.
+     *
+     * @param null|string         $title
+     * @param null|string         $description
+     * @param string[]            $tags
+     * @param null|BackgroundNode $background
+     * @param ScenarioInterface[] $scenarios
+     * @param string              $keyword
+     * @param string              $language
+     * @param null|string         $file
+     * @param integer             $line
+     */
+    public function __construct(
+        $title,
+        $description,
+        array $tags,
+        BackgroundNode $background = null,
+        array $scenarios,
+        $keyword,
+        $language,
+        $file,
+        $line
+    ) {
+        $this->title = $title;
+        $this->description = $description;
+        $this->tags = $tags;
+        $this->background = $background;
+        $this->scenarios = $scenarios;
+        $this->keyword = $keyword;
+        $this->language = $language;
+        $this->file = $file;
+        $this->line = $line;
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Feature';
+    }
+
+    /**
+     * Returns feature title.
+     *
+     * @return null|string
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Checks if feature has a description.
+     *
+     * @return Boolean
+     */
+    public function hasDescription()
+    {
+        return !empty($this->description);
+    }
+
+    /**
+     * Returns feature description.
+     *
+     * @return null|string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Checks if feature is tagged with tag.
+     *
+     * @param string $tag
+     *
+     * @return Boolean
+     */
+    public function hasTag($tag)
+    {
+        return in_array($tag, $this->tags);
+    }
+
+    /**
+     * Checks if feature has tags.
+     *
+     * @return Boolean
+     */
+    public function hasTags()
+    {
+        return 0 < count($this->tags);
+    }
+
+    /**
+     * Returns feature tags.
+     *
+     * @return string[]
+     */
+    public function getTags()
+    {
+        return $this->tags;
+    }
+
+    /**
+     * Checks if feature has background.
+     *
+     * @return Boolean
+     */
+    public function hasBackground()
+    {
+        return null !== $this->background;
+    }
+
+    /**
+     * Returns feature background.
+     *
+     * @return null|BackgroundNode
+     */
+    public function getBackground()
+    {
+        return $this->background;
+    }
+
+    /**
+     * Checks if feature has scenarios.
+     *
+     * @return Boolean
+     */
+    public function hasScenarios()
+    {
+        return 0 < count($this->scenarios);
+    }
+
+    /**
+     * Returns feature scenarios.
+     *
+     * @return ScenarioInterface[]
+     */
+    public function getScenarios()
+    {
+        return $this->scenarios;
+    }
+
+    /**
+     * Returns feature keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+
+    /**
+     * Returns feature language.
+     *
+     * @return string
+     */
+    public function getLanguage()
+    {
+        return $this->language;
+    }
+
+    /**
+     * Returns feature file.
+     *
+     * @return null|string
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+
+    /**
+     * Returns feature declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+}

+ 33 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/KeywordNodeInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin keyword node interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface KeywordNodeInterface extends NodeInterface
+{
+    /**
+     * Returns node keyword.
+     *
+     * @return string
+     */
+    public function getKeyword();
+
+    /**
+     * Returns node title.
+     *
+     * @return null|string
+     */
+    public function getTitle();
+}

+ 33 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/NodeInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin node interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface NodeInterface
+{
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType();
+
+    /**
+     * Returns feature declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine();
+}

+ 218 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/OutlineNode.php

@@ -0,0 +1,218 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Outline.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class OutlineNode implements ScenarioInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+    /**
+     * @var string[]
+     */
+    private $tags;
+    /**
+     * @var StepNode[]
+     */
+    private $steps;
+    /**
+     * @var ExampleTableNode
+     */
+    private $table;
+    /**
+     * @var string
+     */
+    private $keyword;
+    /**
+     * @var integer
+     */
+    private $line;
+    /**
+     * @var null|ExampleNode[]
+     */
+    private $examples;
+
+    /**
+     * Initializes outline.
+     *
+     * @param null|string      $title
+     * @param string[]         $tags
+     * @param StepNode[]       $steps
+     * @param ExampleTableNode $table
+     * @param string           $keyword
+     * @param integer          $line
+     */
+    public function __construct(
+        $title,
+        array $tags,
+        array $steps,
+        ExampleTableNode $table,
+        $keyword,
+        $line
+    ) {
+        $this->title = $title;
+        $this->tags = $tags;
+        $this->steps = $steps;
+        $this->table = $table;
+        $this->keyword = $keyword;
+        $this->line = $line;
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Outline';
+    }
+
+    /**
+     * Returns outline title.
+     *
+     * @return null|string
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Checks if outline is tagged with tag.
+     *
+     * @param string $tag
+     *
+     * @return Boolean
+     */
+    public function hasTag($tag)
+    {
+        return in_array($tag, $this->getTags());
+    }
+
+    /**
+     * Checks if outline has tags (both inherited from feature and own).
+     *
+     * @return Boolean
+     */
+    public function hasTags()
+    {
+        return 0 < count($this->getTags());
+    }
+
+    /**
+     * Returns outline tags (including inherited from feature).
+     *
+     * @return string[]
+     */
+    public function getTags()
+    {
+        return $this->tags;
+    }
+
+    /**
+     * Checks if outline has steps.
+     *
+     * @return Boolean
+     */
+    public function hasSteps()
+    {
+        return 0 < count($this->steps);
+    }
+
+    /**
+     * Returns outline steps.
+     *
+     * @return StepNode[]
+     */
+    public function getSteps()
+    {
+        return $this->steps;
+    }
+
+    /**
+     * Checks if outline has examples.
+     *
+     * @return Boolean
+     */
+    public function hasExamples()
+    {
+        return 0 < count($this->table->getColumnsHash());
+    }
+
+    /**
+     * Returns examples table.
+     *
+     * @return ExampleTableNode
+     */
+    public function getExampleTable()
+    {
+        return $this->table;
+    }
+
+    /**
+     * Returns list of examples for the outline.
+     *
+     * @return ExampleNode[]
+     */
+    public function getExamples()
+    {
+        return $this->examples = $this->examples ? : $this->createExamples();
+    }
+
+    /**
+     * Returns outline keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+
+    /**
+     * Returns outline declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+
+    /**
+     * Creates examples for this outline using examples table.
+     *
+     * @return ExampleNode[]
+     */
+    protected function createExamples()
+    {
+        $examples = array();
+        foreach ($this->table->getColumnsHash() as $rowNum => $row) {
+            $examples[] = new ExampleNode(
+                $this->table->getRowAsString($rowNum + 1),
+                $this->tags,
+                $this->getSteps(),
+                $row,
+                $this->table->getRowLine($rowNum + 1),
+                $this->getTitle()
+            );
+        }
+
+        return $examples;
+    }
+}

+ 90 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/PyStringNode.php

@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin PyString argument.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class PyStringNode implements ArgumentInterface
+{
+    /**
+     * @var array
+     */
+    private $strings = array();
+    /**
+     * @var integer
+     */
+    private $line;
+
+    /**
+     * Initializes PyString.
+     *
+     * @param array   $strings String in form of [$stringLine]
+     * @param integer $line    Line number where string been started
+     */
+    public function __construct(array $strings, $line)
+    {
+        $this->strings = $strings;
+        $this->line = $line;
+    }
+
+    /**
+     * Returns node type.
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'PyString';
+    }
+
+    /**
+     * Returns entire PyString lines set.
+     *
+     * @return array
+     */
+    public function getStrings()
+    {
+        return $this->strings;
+    }
+
+    /**
+     * Returns raw string.
+     *
+     * @return string
+     */
+    public function getRaw()
+    {
+        return implode("\n", $this->strings);
+    }
+
+    /**
+     * Converts PyString into string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getRaw();
+    }
+
+    /**
+     * Returns line number at which PyString was started.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+}

+ 20 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin scenario interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface ScenarioInterface extends ScenarioLikeInterface, TaggedNodeInterface
+{
+}

+ 20 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioLikeInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin scenario-like interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface ScenarioLikeInterface extends KeywordNodeInterface, StepContainerInterface
+{
+}

+ 150 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/ScenarioNode.php

@@ -0,0 +1,150 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Represents Gherkin Scenario.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class ScenarioNode implements ScenarioInterface
+{
+    /**
+     * @var string
+     */
+    private $title;
+    /**
+     * @var array
+     */
+    private $tags = array();
+    /**
+     * @var StepNode[]
+     */
+    private $steps = array();
+    /**
+     * @var string
+     */
+    private $keyword;
+    /**
+     * @var integer
+     */
+    private $line;
+
+    /**
+     * Initializes scenario.
+     *
+     * @param null|string $title
+     * @param array       $tags
+     * @param StepNode[]  $steps
+     * @param string      $keyword
+     * @param integer     $line
+     */
+    public function __construct($title, array $tags, array $steps, $keyword, $line)
+    {
+        $this->title = $title;
+        $this->tags = $tags;
+        $this->steps = $steps;
+        $this->keyword = $keyword;
+        $this->line = $line;
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Scenario';
+    }
+
+    /**
+     * Returns scenario title.
+     *
+     * @return null|string
+     */
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    /**
+     * Checks if scenario is tagged with tag.
+     *
+     * @param string $tag
+     *
+     * @return Boolean
+     */
+    public function hasTag($tag)
+    {
+        return in_array($tag, $this->getTags());
+    }
+
+    /**
+     * Checks if scenario has tags (both inherited from feature and own).
+     *
+     * @return Boolean
+     */
+    public function hasTags()
+    {
+        return 0 < count($this->getTags());
+    }
+
+    /**
+     * Returns scenario tags (including inherited from feature).
+     *
+     * @return array
+     */
+    public function getTags()
+    {
+        return $this->tags;
+    }
+
+    /**
+     * Checks if scenario has steps.
+     *
+     * @return Boolean
+     */
+    public function hasSteps()
+    {
+        return 0 < count($this->steps);
+    }
+
+    /**
+     * Returns scenario steps.
+     *
+     * @return StepNode[]
+     */
+    public function getSteps()
+    {
+        return $this->steps;
+    }
+
+    /**
+     * Returns scenario keyword.
+     *
+     * @return string
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+
+    /**
+     * Returns scenario declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+}

+ 33 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/StepContainerInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin step container interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface StepContainerInterface extends NodeInterface
+{
+    /**
+     * Checks if container has steps.
+     *
+     * @return Boolean
+     */
+    public function hasSteps();
+
+    /**
+     * Returns container steps.
+     *
+     * @return StepNode[]
+     */
+    public function getSteps();
+}

+ 152 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/StepNode.php

@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+use Behat\Gherkin\Exception\NodeException;
+
+/**
+ * Represents Gherkin Step.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class StepNode implements NodeInterface
+{
+    /**
+     * @var string
+     */
+    private $keyword;
+    /**
+     * @var string
+     */
+    private $keywordType;
+    /**
+     * @var string
+     */
+    private $text;
+    /**
+     * @var ArgumentInterface[]
+     */
+    private $arguments = array();
+    /**
+     * @var integer
+     */
+    private $line;
+
+    /**
+     * Initializes step.
+     *
+     * @param string              $keyword
+     * @param string              $text
+     * @param ArgumentInterface[] $arguments
+     * @param integer             $line
+     * @param string              $keywordType
+     */
+    public function __construct($keyword, $text, array $arguments, $line, $keywordType = null)
+    {
+        if (count($arguments) > 1) {
+            throw new NodeException(sprintf(
+                'Steps could have only one argument, but `%s %s` have %d.',
+                $keyword,
+                $text,
+                count($arguments)
+            ));
+        }
+
+        $this->keyword = $keyword;
+        $this->text = $text;
+        $this->arguments = $arguments;
+        $this->line = $line;
+        $this->keywordType = $keywordType ?: 'Given';
+    }
+
+    /**
+     * Returns node type string
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Step';
+    }
+
+    /**
+     * Returns step keyword in provided language (Given, When, Then, etc.).
+     *
+     * @return string
+     *
+     * @deprecated use getKeyword() instead
+     */
+    public function getType()
+    {
+        return $this->getKeyword();
+    }
+
+    /**
+     * Returns step keyword in provided language (Given, When, Then, etc.).
+     *
+     * @return string
+     *
+     */
+    public function getKeyword()
+    {
+        return $this->keyword;
+    }
+
+    /**
+     * Returns step type keyword (Given, When, Then, etc.).
+     *
+     * @return string
+     */
+    public function getKeywordType()
+    {
+        return $this->keywordType;
+    }
+
+    /**
+     * Returns step text.
+     *
+     * @return string
+     */
+    public function getText()
+    {
+        return $this->text;
+    }
+
+    /**
+     * Checks if step has arguments.
+     *
+     * @return Boolean
+     */
+    public function hasArguments()
+    {
+        return 0 < count($this->arguments);
+    }
+
+    /**
+     * Returns step arguments.
+     *
+     * @return ArgumentInterface[]
+     */
+    public function getArguments()
+    {
+        return $this->arguments;
+    }
+
+    /**
+     * Returns step declaration line number.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->line;
+    }
+}

+ 347 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/TableNode.php

@@ -0,0 +1,347 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+use ArrayIterator;
+use Behat\Gherkin\Exception\NodeException;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * Represents Gherkin Table argument.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class TableNode implements ArgumentInterface, IteratorAggregate
+{
+    /**
+     * @var array
+     */
+    private $table;
+    /**
+     * @var integer
+     */
+    private $maxLineLength = array();
+
+    /**
+     * Initializes table.
+     *
+     * @param array $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]]
+     *
+     * @throws NodeException If the given table is invalid
+     */
+    public function __construct(array $table)
+    {
+        $this->table = $table;
+        $columnCount = null;
+
+        foreach ($this->getRows() as $row) {
+
+            if (!is_array($row)) {
+                throw new NodeException('Table is not two-dimensional.');
+            }
+
+            if ($columnCount === null) {
+                $columnCount = count($row);
+            }
+
+            if (count($row) !== $columnCount) {
+                throw new NodeException('Table does not have same number of columns in every row.');
+            }
+
+            if (!is_array($row)) {
+                throw new NodeException('Table is not two-dimensional.');
+            }
+
+            foreach ($row as $column => $string) {
+                if (!isset($this->maxLineLength[$column])) {
+                    $this->maxLineLength[$column] = 0;
+                }
+
+                if (!is_scalar($string)) {
+                    throw new NodeException('Table is not two-dimensional.');
+                }
+
+                $this->maxLineLength[$column] = max($this->maxLineLength[$column], mb_strlen($string, 'utf8'));
+            }
+        }
+    }
+
+    /**
+     * Creates a table from a given list.
+     *
+     * @param array $list One-dimensional array
+     *
+     * @return TableNode
+     *
+     * @throws NodeException If the given list is not a one-dimensional array
+     */
+    public static function fromList(array $list)
+    {
+        if (count($list) !== count($list, COUNT_RECURSIVE)) {
+            throw new NodeException('List is not a one-dimensional array.');
+        }
+
+        array_walk($list, function (&$item) {
+            $item = array($item);
+        });
+        return new self($list);
+    }
+
+    /**
+     * Returns node type.
+     *
+     * @return string
+     */
+    public function getNodeType()
+    {
+        return 'Table';
+    }
+
+    /**
+     * Returns table hash, formed by columns (ColumnsHash).
+     *
+     * @return array
+     */
+    public function getHash()
+    {
+        return $this->getColumnsHash();
+    }
+
+    /**
+     * Returns table hash, formed by columns.
+     *
+     * @return array
+     */
+    public function getColumnsHash()
+    {
+        $rows = $this->getRows();
+        $keys = array_shift($rows);
+
+        $hash = array();
+        foreach ($rows as $row) {
+            $hash[] = array_combine($keys, $row);
+        }
+
+        return $hash;
+    }
+
+    /**
+     * Returns table hash, formed by rows.
+     *
+     * @return array
+     */
+    public function getRowsHash()
+    {
+        $hash = array();
+
+        foreach ($this->getRows() as $row) {
+            $hash[array_shift($row)] = (1 == count($row)) ? $row[0] : $row;
+        }
+
+        return $hash;
+    }
+
+    /**
+     * Returns numerated table lines.
+     * Line numbers are keys, lines are values.
+     *
+     * @return array
+     */
+    public function getTable()
+    {
+        return $this->table;
+    }
+
+    /**
+     * Returns table rows.
+     *
+     * @return array
+     */
+    public function getRows()
+    {
+        return array_values($this->table);
+    }
+
+    /**
+     * Returns table definition lines.
+     *
+     * @return array
+     */
+    public function getLines()
+    {
+        return array_keys($this->table);
+    }
+
+    /**
+     * Returns specific row in a table.
+     *
+     * @param integer $index Row number
+     *
+     * @return array
+     *
+     * @throws NodeException If row with specified index does not exist
+     */
+    public function getRow($index)
+    {
+        $rows = $this->getRows();
+
+        if (!isset($rows[$index])) {
+            throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
+        }
+
+        return $rows[$index];
+    }
+
+    /**
+     * Returns specific column in a table.
+     *
+     * @param integer $index Column number
+     *
+     * @return array
+     *
+     * @throws NodeException If column with specified index does not exist
+     */
+    public function getColumn($index)
+    {
+        if ($index >= count($this->getRow(0))) {
+            throw new NodeException(sprintf('Column #%d does not exist in table.', $index));
+        }
+
+        $rows = $this->getRows();
+        $column = array();
+
+        foreach ($rows as $row) {
+            $column[] = $row[$index];
+        }
+
+        return $column;
+    }
+
+    /**
+     * Returns line number at which specific row was defined.
+     *
+     * @param integer $index
+     *
+     * @return integer
+     *
+     * @throws NodeException If row with specified index does not exist
+     */
+    public function getRowLine($index)
+    {
+        $lines = array_keys($this->table);
+
+        if (!isset($lines[$index])) {
+            throw new NodeException(sprintf('Rows #%d does not exist in table.', $index));
+        }
+
+        return $lines[$index];
+    }
+
+    /**
+     * Converts row into delimited string.
+     *
+     * @param integer $rowNum Row number
+     *
+     * @return string
+     */
+    public function getRowAsString($rowNum)
+    {
+        $values = array();
+        foreach ($this->getRow($rowNum) as $column => $value) {
+            $values[] = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
+        }
+
+        return sprintf('|%s|', implode('|', $values));
+    }
+
+    /**
+     * Converts row into delimited string.
+     *
+     * @param integer  $rowNum  Row number
+     * @param callable $wrapper Wrapper function
+     *
+     * @return string
+     */
+    public function getRowAsStringWithWrappedValues($rowNum, $wrapper)
+    {
+        $values = array();
+        foreach ($this->getRow($rowNum) as $column => $value) {
+            $value = $this->padRight(' ' . $value . ' ', $this->maxLineLength[$column] + 2);
+
+            $values[] = call_user_func($wrapper, $value, $column);
+        }
+
+        return sprintf('|%s|', implode('|', $values));
+    }
+
+    /**
+     * Converts entire table into string
+     *
+     * @return string
+     */
+    public function getTableAsString()
+    {
+        $lines = array();
+        for ($i = 0; $i < count($this->getRows()); $i++) {
+            $lines[] = $this->getRowAsString($i);
+        }
+
+        return implode("\n", $lines);
+    }
+
+    /**
+     * Returns line number at which table was started.
+     *
+     * @return integer
+     */
+    public function getLine()
+    {
+        return $this->getRowLine(0);
+    }
+
+    /**
+     * Converts table into string
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getTableAsString();
+    }
+
+    /**
+     * Retrieves a hash iterator.
+     *
+     * @return Iterator
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->getHash());
+    }
+
+    /**
+     * Pads string right.
+     *
+     * @param string  $text   Text to pad
+     * @param integer $length Length
+     *
+     * @return string
+     */
+    protected function padRight($text, $length)
+    {
+        while ($length > mb_strlen($text, 'utf8')) {
+            $text = $text . ' ';
+        }
+
+        return $text;
+    }
+}

+ 42 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Node/TaggedNodeInterface.php

@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin\Node;
+
+/**
+ * Gherkin tagged node interface.
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+interface TaggedNodeInterface extends NodeInterface
+{
+    /**
+     * Checks if node is tagged with tag.
+     *
+     * @param string $tag
+     *
+     * @return Boolean
+     */
+    public function hasTag($tag);
+
+    /**
+     * Checks if node has tags (both inherited from feature and own).
+     *
+     * @return Boolean
+     */
+    public function hasTags();
+
+    /**
+     * Returns node tags (including inherited from feature).
+     *
+     * @return string[]
+     */
+    public function getTags();
+}

+ 699 - 0
vendor/behat/gherkin/src/Behat/Gherkin/Parser.php

@@ -0,0 +1,699 @@
+<?php
+
+/*
+ * This file is part of the Behat Gherkin.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Behat\Gherkin;
+
+use Behat\Gherkin\Exception\LexerException;
+use Behat\Gherkin\Exception\ParserException;
+use Behat\Gherkin\Node\BackgroundNode;
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\PyStringNode;
+use Behat\Gherkin\Node\ScenarioInterface;
+use Behat\Gherkin\Node\ScenarioNode;
+use Behat\Gherkin\Node\StepNode;
+use Behat\Gherkin\Node\TableNode;
+
+/**
+ * Gherkin parser.
+ *
+ * $lexer  = new Behat\Gherkin\Lexer($keywords);
+ * $parser = new Behat\Gherkin\Parser($lexer);
+ * $featuresArray = $parser->parse('/path/to/feature.feature');
+ *
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class Parser
+{
+    private $lexer;
+    private $input;
+    private $file;
+    private $tags = array();
+    private $languageSpecifierLine;
+
+    /**
+     * Initializes parser.
+     *
+     * @param Lexer $lexer Lexer instance
+     */
+    public function __construct(Lexer $lexer)
+    {
+        $this->lexer = $lexer;
+    }
+
+    /**
+     * Parses input & returns features array.
+     *
+     * @param string $input Gherkin string document
+     * @param string $file  File name
+     *
+     * @return FeatureNode|null
+     *
+     * @throws ParserException
+     */
+    public function parse($input, $file = null)
+    {
+        $this->languageSpecifierLine = null;
+        $this->input = $input;
+        $this->file = $file;
+        $this->tags = array();
+
+        try {
+            $this->lexer->analyse($this->input, 'en');
+        } catch (LexerException $e) {
+            throw new ParserException(
+                sprintf('Lexer exception "%s" thrown for file %s', $e->getMessage(), $file),
+                0,
+                $e
+            );
+        }
+
+        $feature = null;
+        while ('EOS' !== ($predicted = $this->predictTokenType())) {
+            $node = $this->parseExpression();
+
+            if (null === $node || "\n" === $node) {
+                continue;
+            }
+
+            if (!$feature && $node instanceof FeatureNode) {
+                $feature = $node;
+                continue;
+            }
+
+            if ($feature && $node instanceof FeatureNode) {
+                throw new ParserException(sprintf(
+                    'Only one feature is allowed per feature file. But %s got multiple.',
+                    $this->file
+                ));
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Feature, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof FeatureNode) {
+                throw new ParserException(sprintf(
+                    'Expected Feature, but got %s on line: %d%s',
+                    $node->getKeyword(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return $feature;
+    }
+
+    /**
+     * Returns next token if it's type equals to expected.
+     *
+     * @param string $type Token type
+     *
+     * @return array
+     *
+     * @throws Exception\ParserException
+     */
+    protected function expectTokenType($type)
+    {
+        $types = (array) $type;
+        if (in_array($this->predictTokenType(), $types)) {
+            return $this->lexer->getAdvancedToken();
+        }
+
+        $token = $this->lexer->predictToken();
+
+        throw new ParserException(sprintf(
+            'Expected %s token, but got %s on line: %d%s',
+            implode(' or ', $types),
+            $this->predictTokenType(),
+            $token['line'],
+            $this->file ? ' in file: ' . $this->file : ''
+        ));
+    }
+
+    /**
+     * Returns next token if it's type equals to expected.
+     *
+     * @param string $type Token type
+     *
+     * @return null|array
+     */
+    protected function acceptTokenType($type)
+    {
+        if ($type !== $this->predictTokenType()) {
+            return null;
+        }
+
+        return $this->lexer->getAdvancedToken();
+    }
+
+    /**
+     * Returns next token type without real input reading (prediction).
+     *
+     * @return string
+     */
+    protected function predictTokenType()
+    {
+        $token = $this->lexer->predictToken();
+
+        return $token['type'];
+    }
+
+    /**
+     * Parses current expression & returns Node.
+     *
+     * @return string|FeatureNode|BackgroundNode|ScenarioNode|OutlineNode|TableNode|StepNode
+     *
+     * @throws ParserException
+     */
+    protected function parseExpression()
+    {
+        switch ($type = $this->predictTokenType()) {
+            case 'Feature':
+                return $this->parseFeature();
+            case 'Background':
+                return $this->parseBackground();
+            case 'Scenario':
+                return $this->parseScenario();
+            case 'Outline':
+                return $this->parseOutline();
+            case 'Examples':
+                return $this->parseExamples();
+            case 'TableRow':
+                return $this->parseTable();
+            case 'PyStringOp':
+                return $this->parsePyString();
+            case 'Step':
+                return $this->parseStep();
+            case 'Text':
+                return $this->parseText();
+            case 'Newline':
+                return $this->parseNewline();
+            case 'Tag':
+                return $this->parseTags();
+            case 'Comment':
+                return $this->parseComment();
+            case 'Language':
+                return $this->parseLanguage();
+            case 'EOS':
+                return '';
+        }
+
+        throw new ParserException(sprintf('Unknown token type: %s', $type));
+    }
+
+    /**
+     * Parses feature token & returns it's node.
+     *
+     * @return FeatureNode
+     *
+     * @throws ParserException
+     */
+    protected function parseFeature()
+    {
+        $token = $this->expectTokenType('Feature');
+
+        $title = trim($token['value']) ?: null;
+        $description = null;
+        $tags = $this->popTags();
+        $background = null;
+        $scenarios = array();
+        $keyword = $token['keyword'];
+        $language = $this->lexer->getLanguage();
+        $file = $this->file;
+        $line = $token['line'];
+
+        // Parse description, background, scenarios & outlines
+        while ('EOS' !== $this->predictTokenType()) {
+            $node = $this->parseExpression();
+
+            if (is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $description .= (null !== $description ? "\n" : '') . $text;
+                continue;
+            }
+
+            if (!$background && $node instanceof BackgroundNode) {
+                $background = $node;
+                continue;
+            }
+
+            if ($node instanceof ScenarioInterface) {
+                $scenarios[] = $node;
+                continue;
+            }
+
+            if ($background instanceof BackgroundNode && $node instanceof BackgroundNode) {
+                throw new ParserException(sprintf(
+                    'Each Feature could have only one Background, but found multiple on lines %d and %d%s',
+                    $background->getLine(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof ScenarioNode) {
+                throw new ParserException(sprintf(
+                    'Expected Scenario, Outline or Background, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new FeatureNode(
+            rtrim($title) ?: null,
+            rtrim($description) ?: null,
+            $tags,
+            $background,
+            $scenarios,
+            $keyword,
+            $language,
+            $file,
+            $line
+        );
+    }
+
+    /**
+     * Parses background token & returns it's node.
+     *
+     * @return BackgroundNode
+     *
+     * @throws ParserException
+     */
+    protected function parseBackground()
+    {
+        $token = $this->expectTokenType('Background');
+
+        $title = trim($token['value']);
+        $keyword = $token['keyword'];
+        $line = $token['line'];
+
+        if (count($this->popTags())) {
+            throw new ParserException(sprintf(
+                'Background can not be tagged, but it is on line: %d%s',
+                $line,
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        // Parse description and steps
+        $steps = array();
+        $allowedTokenTypes = array('Step', 'Newline', 'Text', 'Comment');
+        while (in_array($this->predictTokenType(), $allowedTokenTypes)) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new BackgroundNode(rtrim($title) ?: null, $steps, $keyword, $line);
+    }
+
+    /**
+     * Parses scenario token & returns it's node.
+     *
+     * @return ScenarioNode
+     *
+     * @throws ParserException
+     */
+    protected function parseScenario()
+    {
+        $token = $this->expectTokenType('Scenario');
+
+        $title = trim($token['value']);
+        $tags = $this->popTags();
+        $keyword = $token['keyword'];
+        $line = $token['line'];
+
+        // Parse description and steps
+        $steps = array();
+        while (in_array($this->predictTokenType(), array('Step', 'Newline', 'Text', 'Comment'))) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        return new ScenarioNode(rtrim($title) ?: null, $tags, $steps, $keyword, $line);
+    }
+
+    /**
+     * Parses scenario outline token & returns it's node.
+     *
+     * @return OutlineNode
+     *
+     * @throws ParserException
+     */
+    protected function parseOutline()
+    {
+        $token = $this->expectTokenType('Outline');
+
+        $title = trim($token['value']);
+        $tags = $this->popTags();
+        $keyword = $token['keyword'];
+        $examples = null;
+        $line = $token['line'];
+
+        // Parse description, steps and examples
+        $steps = array();
+        while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment'))) {
+            $node = $this->parseExpression();
+
+            if ($node instanceof StepNode) {
+                $steps[] = $this->normalizeStepNodeKeywordType($node, $steps);
+                continue;
+            }
+
+            if ($node instanceof ExampleTableNode) {
+                $examples = $node;
+                continue;
+            }
+
+            if (!count($steps) && is_string($node)) {
+                $text = preg_replace('/^\s{0,' . ($token['indent'] + 2) . '}|\s*$/', '', $node);
+                $title .= "\n" . $text;
+                continue;
+            }
+
+            if ("\n" === $node) {
+                continue;
+            }
+
+            if (is_string($node)) {
+                throw new ParserException(sprintf(
+                    'Expected Step or Examples table, but got text: "%s"%s',
+                    $node,
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+
+            if (!$node instanceof StepNode) {
+                throw new ParserException(sprintf(
+                    'Expected Step or Examples table, but got %s on line: %d%s',
+                    $node->getNodeType(),
+                    $node->getLine(),
+                    $this->file ? ' in file: ' . $this->file : ''
+                ));
+            }
+        }
+
+        if (null === $examples) {
+            throw new ParserException(sprintf(
+                'Outline should have examples table, but got none for outline "%s" on line: %d%s',
+                rtrim($title),
+                $line,
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        return new OutlineNode(rtrim($title) ?: null, $tags, $steps, $examples, $keyword, $line);
+    }
+
+    /**
+     * Parses step token & returns it's node.
+     *
+     * @return StepNode
+     */
+    protected function parseStep()
+    {
+        $token = $this->expectTokenType('Step');
+
+        $keyword = $token['value'];
+        $keywordType = $token['keyword_type'];
+        $text = trim($token['text']);
+        $line = $token['line'];
+
+        $arguments = array();
+        while (in_array($predicted = $this->predictTokenType(), array('PyStringOp', 'TableRow', 'Newline', 'Comment'))) {
+            if ('Comment' === $predicted || 'Newline' === $predicted) {
+                $this->acceptTokenType($predicted);
+                continue;
+            }
+
+            $node = $this->parseExpression();
+
+            if ($node instanceof PyStringNode || $node instanceof TableNode) {
+                $arguments[] = $node;
+            }
+        }
+
+        return new StepNode($keyword, $text, $arguments, $line, $keywordType);
+    }
+
+    /**
+     * Parses examples table node.
+     *
+     * @return ExampleTableNode
+     */
+    protected function parseExamples()
+    {
+        $token = $this->expectTokenType('Examples');
+
+        $keyword = $token['keyword'];
+
+        return new ExampleTableNode($this->parseTableRows(), $keyword);
+    }
+
+    /**
+     * Parses table token & returns it's node.
+     *
+     * @return TableNode
+     */
+    protected function parseTable()
+    {
+        return new TableNode($this->parseTableRows());
+    }
+
+    /**
+     * Parses PyString token & returns it's node.
+     *
+     * @return PyStringNode
+     */
+    protected function parsePyString()
+    {
+        $token = $this->expectTokenType('PyStringOp');
+
+        $line = $token['line'];
+
+        $strings = array();
+        while ('PyStringOp' !== ($predicted = $this->predictTokenType()) && 'Text' === $predicted) {
+            $token = $this->expectTokenType('Text');
+
+            $strings[] = $token['value'];
+        }
+
+        $this->expectTokenType('PyStringOp');
+
+        return new PyStringNode($strings, $line);
+    }
+
+    /**
+     * Parses tags.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     */
+    protected function parseTags()
+    {
+        $token = $this->expectTokenType('Tag');
+        $this->tags = array_merge($this->tags, $token['tags']);
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Returns current set of tags and clears tag buffer.
+     *
+     * @return array
+     */
+    protected function popTags()
+    {
+        $tags = $this->tags;
+        $this->tags = array();
+
+        return $tags;
+    }
+
+    /**
+     * Parses next text line & returns it.
+     *
+     * @return string
+     */
+    protected function parseText()
+    {
+        $token = $this->expectTokenType('Text');
+
+        return $token['value'];
+    }
+
+    /**
+     * Parses next newline & returns \n.
+     *
+     * @return string
+     */
+    protected function parseNewline()
+    {
+        $this->expectTokenType('Newline');
+
+        return "\n";
+    }
+
+    /**
+     * Parses next comment token & returns it's string content.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     */
+    protected function parseComment()
+    {
+        $this->expectTokenType('Comment');
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Parses language block and updates lexer configuration based on it.
+     *
+     * @return BackgroundNode|FeatureNode|OutlineNode|ScenarioNode|StepNode|TableNode|string
+     *
+     * @throws ParserException
+     */
+    protected function parseLanguage()
+    {
+        $token = $this->expectTokenType('Language');
+
+        if (null === $this->languageSpecifierLine) {
+            $this->lexer->analyse($this->input, $token['value']);
+            $this->languageSpecifierLine = $token['line'];
+        } elseif ($token['line'] !== $this->languageSpecifierLine) {
+            throw new ParserException(sprintf(
+                'Ambiguous language specifiers on lines: %d and %d%s',
+                $this->languageSpecifierLine,
+                $token['line'],
+                $this->file ? ' in file: ' . $this->file : ''
+            ));
+        }
+
+        return $this->parseExpression();
+    }
+
+    /**
+     * Parses the rows of a table
+     *
+     * @return string[][]
+     */
+    private function parseTableRows()
+    {
+        $table = array();
+        while (in_array($predicted = $this->predictTokenType(), array('TableRow', 'Newline', 'Comment'))) {
+            if ('Comment' === $predicted || 'Newline' === $predicted) {
+                $this->acceptTokenType($predicted);
+                continue;
+            }
+
+            $token = $this->expectTokenType('TableRow');
+
+            $table[$token['line']] = $token['columns'];
+        }
+
+        return $table;
+    }
+
+    /**
+     * Changes step node type for types But, And to type of previous step if it exists else sets to Given
+     *
+     * @param StepNode   $node
+     * @param StepNode[] $steps
+     * @return StepNode
+     */
+    private function normalizeStepNodeKeywordType(StepNode $node, array $steps = array())
+    {
+        if (in_array($node->getKeywordType(), array('And', 'But'))) {
+            if (($prev = end($steps))) {
+                $keywordType = $prev->getKeywordType();
+            } else {
+                $keywordType = 'Given';
+            }
+
+            $node = new StepNode(
+                $node->getKeyword(),
+                $node->getText(),
+                $node->getArguments(),
+                $node->getLine(),
+                $keywordType
+            );
+        }
+        return $node;
+    }
+}

+ 75 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Cache/FileCacheTest.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Cache;
+
+use Behat\Gherkin\Cache\FileCache;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioNode;
+use Behat\Gherkin\Gherkin;
+
+class FileCacheTest extends \PHPUnit_Framework_TestCase
+{
+    private $path;
+    private $cache;
+
+    public function testIsFreshWhenThereIsNoFile()
+    {
+        $this->assertFalse($this->cache->isFresh('unexisting', time() + 100));
+    }
+
+    public function testIsFreshOnFreshFile()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, null);
+
+        $this->cache->write('some_path', $feature);
+
+        $this->assertFalse($this->cache->isFresh('some_path', time() + 100));
+    }
+
+    public function testIsFreshOnOutdated()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, null);
+
+        $this->cache->write('some_path', $feature);
+
+        $this->assertTrue($this->cache->isFresh('some_path', time() - 100));
+    }
+
+    public function testCacheAndRead()
+    {
+        $scenarios = array(new ScenarioNode('Some scenario', array(), array(), null, null));
+        $feature = new FeatureNode('Some feature', 'some description', array(), null, $scenarios, null, null, null, null);
+
+        $this->cache->write('some_feature', $feature);
+        $featureRead = $this->cache->read('some_feature');
+
+        $this->assertEquals($feature, $featureRead);
+    }
+
+    public function testBrokenCacheRead()
+    {
+        $this->setExpectedException('Behat\Gherkin\Exception\CacheException');
+
+        touch($this->path . '/v' . Gherkin::VERSION . '/' . md5('broken_feature') . '.feature.cache');
+        $this->cache->read('broken_feature');
+    }
+
+    public function testUnwriteableCacheDir()
+    {
+        $this->setExpectedException('Behat\Gherkin\Exception\CacheException');
+
+        new FileCache('/dev/null/gherkin-test');
+    }
+
+    protected function setUp()
+    {
+        $this->cache = new FileCache($this->path = sys_get_temp_dir() . '/gherkin-test');
+    }
+
+    protected function tearDown()
+    {
+        foreach (glob($this->path . '/*.feature.cache') as $file) {
+            unlink((string) $file);
+        }
+    }
+}

+ 51 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Cache/MemoryCacheTest.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Cache;
+
+use Behat\Gherkin\Cache\MemoryCache;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioNode;
+
+class MemoryCacheTest extends \PHPUnit_Framework_TestCase
+{
+    private $cache;
+
+    public function testIsFreshWhenThereIsNoFile()
+    {
+        $this->assertFalse($this->cache->isFresh('unexisting', time() + 100));
+    }
+
+    public function testIsFreshOnFreshFile()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, null);
+
+        $this->cache->write('some_path', $feature);
+
+        $this->assertFalse($this->cache->isFresh('some_path', time() + 100));
+    }
+
+    public function testIsFreshOnOutdated()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, null);
+
+        $this->cache->write('some_path', $feature);
+
+        $this->assertTrue($this->cache->isFresh('some_path', time() - 100));
+    }
+
+    public function testCacheAndRead()
+    {
+        $scenarios = array(new ScenarioNode('Some scenario', array(), array(), null, null));
+        $feature = new FeatureNode('Some feature', 'some description', array(), null, $scenarios, null, null, null, null);
+
+        $this->cache->write('some_feature', $feature);
+        $featureRead = $this->cache->read('some_feature');
+
+        $this->assertEquals($feature, $featureRead);
+    }
+
+    protected function setUp()
+    {
+        $this->cache = new MemoryCache();
+    }
+}

+ 64 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/FilterTest.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Keywords\ArrayKeywords;
+use Behat\Gherkin\Lexer;
+use Behat\Gherkin\Parser;
+
+abstract class FilterTest extends \PHPUnit_Framework_TestCase
+{
+    protected function getParser()
+    {
+        return new Parser(
+            new Lexer(
+                new ArrayKeywords(array(
+                    'en' => array(
+                        'feature'          => 'Feature',
+                        'background'       => 'Background',
+                        'scenario'         => 'Scenario',
+                        'scenario_outline' => 'Scenario Outline|Scenario Template',
+                        'examples'         => 'Examples|Scenarios',
+                        'given'            => 'Given',
+                        'when'             => 'When',
+                        'then'             => 'Then',
+                        'and'              => 'And',
+                        'but'              => 'But'
+                    )
+                ))
+            )
+        );
+    }
+
+    protected function getGherkinFeature()
+    {
+        return <<<GHERKIN
+Feature: Long feature with outline
+  Scenario: Scenario#1
+    Given initial step
+    When action occurs
+    Then outcomes should be visible
+
+  Scenario: Scenario#2
+    Given initial step
+    And another initial step
+    When action occurs
+    Then outcomes should be visible
+
+  Scenario Outline: Scenario#3
+    When <action> occurs
+    Then <outcome> should be visible
+
+    Examples:
+      | action | outcome |
+      | act#1  | out#1   |
+      | act#2  | out#2   |
+      | act#3  | out#3   |
+GHERKIN;
+    }
+
+    protected function getParsedFeature()
+    {
+        return $this->getParser()->parse($this->getGherkinFeature());
+    }
+}

+ 0 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full/file1


+ 0 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full/file2


+ 0 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/Fixtures/full_path/file1


+ 103 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/LineFilterTest.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\LineFilter;
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\ScenarioNode;
+
+class LineFilterTest extends FilterTest
+{
+    public function testIsFeatureMatchFilter()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, 1);
+
+        $filter = new LineFilter(1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new LineFilter(2);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new LineFilter(3);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+
+    public function testIsScenarioMatchFilter()
+    {
+        $scenario = new ScenarioNode(null, array(), array(), null, 2);
+
+        $filter = new LineFilter(2);
+        $this->assertTrue($filter->isScenarioMatch($scenario));
+
+        $filter = new LineFilter(1);
+        $this->assertFalse($filter->isScenarioMatch($scenario));
+
+        $filter = new LineFilter(5);
+        $this->assertFalse($filter->isScenarioMatch($scenario));
+
+        $outline = new OutlineNode(null, array(), array(), new ExampleTableNode(array(), null), null, 20);
+
+        $filter = new LineFilter(5);
+        $this->assertFalse($filter->isScenarioMatch($outline));
+
+        $filter = new LineFilter(20);
+        $this->assertTrue($filter->isScenarioMatch($outline));
+    }
+
+    public function testFilterFeatureScenario()
+    {
+        $filter = new LineFilter(2);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#1', $scenarios[0]->getTitle());
+
+        $filter = new LineFilter(7);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#2', $scenarios[0]->getTitle());
+
+        $filter = new LineFilter(5);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(0, $scenarios = $feature->getScenarios());
+    }
+
+    public function testFilterFeatureOutline()
+    {
+        $filter = new LineFilter(13);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(4, $scenarios[0]->getExampleTable()->getRows());
+
+        $filter = new LineFilter(19);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(2, $scenarios[0]->getExampleTable()->getRows());
+        $this->assertSame(array(
+            array('action', 'outcome'),
+            array('act#1', 'out#1'),
+        ), $scenarios[0]->getExampleTable()->getRows());
+
+        $filter = new LineFilter(21);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(2, $scenarios[0]->getExampleTable()->getRows());
+        $this->assertSame(array(
+            array('action', 'outcome'),
+            array('act#3', 'out#3'),
+        ), $scenarios[0]->getExampleTable()->getRows());
+
+        $filter = new LineFilter(18);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(1, $scenarios[0]->getExampleTable()->getRows());
+        $this->assertSame(array(
+            array('action', 'outcome'),
+        ), $scenarios[0]->getExampleTable()->getRows());
+    }
+}

+ 101 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\LineRangeFilter;
+use Behat\Gherkin\Node\ExampleTableNode;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\OutlineNode;
+use Behat\Gherkin\Node\ScenarioNode;
+
+class LineRangeFilterTest extends FilterTest
+{
+    public function featureLineRangeProvider()
+    {
+        return array(
+            array('1', '1', true),
+            array('1', '2', true),
+            array('1', '*', true),
+            array('2', '2', false),
+            array('2', '*', false)
+        );
+    }
+
+    /**
+     * @dataProvider featureLineRangeProvider
+     */
+    public function testIsFeatureMatchFilter($filterMinLine, $filterMaxLine, $expected)
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, 1);
+
+        $filter = new LineRangeFilter($filterMinLine, $filterMaxLine);
+        $this->assertSame($expected, $filter->isFeatureMatch($feature));
+    }
+
+    public function scenarioLineRangeProvider()
+    {
+        return array(
+            array('1', '2', 1),
+            array('1', '*', 2),
+            array('2', '2', 1),
+            array('2', '*', 2),
+            array('3', '3', 1),
+            array('3', '*', 1),
+            array('1', '1', 0),
+            array('4', '4', 0),
+            array('4', '*', 0)
+        );
+    }
+
+    /**
+     * @dataProvider scenarioLineRangeProvider
+     */
+    public function testIsScenarioMatchFilter($filterMinLine, $filterMaxLine, $expectedNumberOfMatches)
+    {
+        $scenario = new ScenarioNode(null, array(), array(), null, 2);
+        $outline = new OutlineNode(null, array(), array(), new ExampleTableNode(array(), null), null, 3);
+
+        $filter = new LineRangeFilter($filterMinLine, $filterMaxLine);
+        $this->assertEquals(
+            $expectedNumberOfMatches,
+            intval($filter->isScenarioMatch($scenario)) + intval($filter->isScenarioMatch($outline))
+        );
+    }
+
+    public function testFilterFeatureScenario()
+    {
+        $filter = new LineRangeFilter(1, 3);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#1', $scenarios[0]->getTitle());
+
+        $filter = new LineRangeFilter(5, 9);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#2', $scenarios[0]->getTitle());
+
+        $filter = new LineRangeFilter(5, 6);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(0, $scenarios = $feature->getScenarios());
+    }
+
+    public function testFilterFeatureOutline()
+    {
+        $filter = new LineRangeFilter(12, 14);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(1, $scenarios[0]->getExampleTable()->getRows());
+
+        $filter = new LineRangeFilter(15, 20);
+        $feature = $filter->filterFeature($this->getParsedFeature());
+        $this->assertCount(1, $scenarios = $feature->getScenarios());
+        $this->assertSame('Scenario#3', $scenarios[0]->getTitle());
+        $this->assertCount(3, $scenarios[0]->getExampleTable()->getRows());
+        $this->assertSame(array(
+            array('action', 'outcome'),
+            array('act#1', 'out#1'),
+            array('act#2', 'out#2'),
+        ), $scenarios[0]->getExampleTable()->getRows());
+    }
+}

+ 79 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/NameFilterTest.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\NameFilter;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioNode;
+
+class NameFilterTest extends \PHPUnit_Framework_TestCase
+{
+    public function testFilterFeature()
+    {
+        $feature = new FeatureNode('feature1', null, array(), null, array(), null, null, null, 1);
+        $filter = new NameFilter('feature1');
+        $this->assertSame($feature, $filter->filterFeature($feature));
+
+        $scenarios = array(
+            new ScenarioNode('scenario1', array(), array(), null, 2),
+            $matchedScenario = new ScenarioNode('scenario2', array(), array(), null, 4)
+        );
+        $feature = new FeatureNode('feature1', null, array(), null, $scenarios, null, null, null, 1);
+        $filter = new NameFilter('scenario2');
+        $filteredFeature = $filter->filterFeature($feature);
+
+        $this->assertSame(array($matchedScenario), $filteredFeature->getScenarios());
+    }
+
+    public function testIsFeatureMatchFilter()
+    {
+        $feature = new FeatureNode('random feature title', null, array(), null, array(), null, null, null, 1);
+
+        $filter = new NameFilter('feature1');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('feature1', null, array(), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('feature1 title', null, array(), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('some feature1 title', null, array(), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('some feature title', null, array(), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new NameFilter('/fea.ure/');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('some feaSure title', null, array(), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode('some feture title', null, array(), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+
+    public function testIsScenarioMatchFilter()
+    {
+        $filter = new NameFilter('scenario1');
+
+        $scenario = new ScenarioNode('UNKNOWN', array(), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($scenario));
+
+        $scenario = new ScenarioNode('scenario1', array(), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($scenario));
+
+        $scenario = new ScenarioNode('scenario1 title', array(), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($scenario));
+
+        $scenario = new ScenarioNode('some scenario title', array(), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($scenario));
+
+        $filter = new NameFilter('/sce.ario/');
+        $this->assertTrue($filter->isScenarioMatch($scenario));
+
+        $filter = new NameFilter('/scen.rio/');
+        $this->assertTrue($filter->isScenarioMatch($scenario));
+    }
+}

+ 34 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/NarrativeFilterTest.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\NarrativeFilter;
+use Behat\Gherkin\Node\FeatureNode;
+
+class NarrativeFilterTest extends FilterTest
+{
+    public function testIsFeatureMatchFilter()
+    {
+        $description = <<<NAR
+In order to be able to read news in my own language
+As a french user
+I need to be able to switch website language to french
+NAR;
+        $feature = new FeatureNode(null, $description, array(), null, array(), null, null, null, 1);
+
+        $filter = new NarrativeFilter('/as (?:a|an) french user/');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new NarrativeFilter('/as (?:a|an) french user/i');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new NarrativeFilter('/french .*/');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new NarrativeFilter('/^french/');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new NarrativeFilter('/user$/');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+}

+ 61 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/PathsFilterTest.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\PathsFilter;
+use Behat\Gherkin\Node\FeatureNode;
+
+class PathsFilterTest extends FilterTest
+{
+    public function testIsFeatureMatchFilter()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, __FILE__, 1);
+
+        $filter = new PathsFilter(array(__DIR__));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array('/abc', '/def', dirname(__DIR__)));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array('/abc', '/def', __DIR__));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array('/abc', __DIR__, '/def'));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array('/abc', '/def', '/wrong/path'));
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+
+    public function testItDoesNotMatchPartialPaths()
+    {
+        $fixtures = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR;
+
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, $fixtures . 'full_path' . DIRECTORY_SEPARATOR . 'file1', 1);
+
+        $filter = new PathsFilter(array($fixtures . 'full'));
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array($fixtures . 'full' . DIRECTORY_SEPARATOR));
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array($fixtures . 'full_path' . DIRECTORY_SEPARATOR));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new PathsFilter(array($fixtures . 'full_path'));
+        $this->assertTrue($filter->isFeatureMatch($feature));
+        
+        $filter = new PathsFilter(array($fixtures . 'ful._path')); // Don't accept regexp
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+
+    public function testItDoesNotMatchIfFileWithSameNameButNotPathExistsInFolder()
+    {
+        $fixtures = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR;
+
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, $fixtures . 'full_path' . DIRECTORY_SEPARATOR . 'file1', 1);
+
+        $filter = new PathsFilter(array($fixtures . 'full'));
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+}

+ 76 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/RoleFilterTest.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\RoleFilter;
+use Behat\Gherkin\Node\FeatureNode;
+
+class RoleFilterTest extends FilterTest
+{
+    public function testIsFeatureMatchFilter()
+    {
+        $description = <<<NAR
+In order to be able to read news in my own language
+As a french user
+I need to be able to switch website language to french
+NAR;
+        $feature = new FeatureNode(null, $description, array(), null, array(), null, null, null, 1);
+
+        $filter = new RoleFilter('french user');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('french *');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('french');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('user');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('*user');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('French User');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, 1);
+        $filter = new RoleFilter('French User');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+
+    public function testFeatureRolePrefixedWithAn()
+    {
+        $description = <<<NAR
+In order to be able to read news in my own language
+As an american user
+I need to be able to switch website language to french
+NAR;
+        $feature = new FeatureNode(null, $description, array(), null, array(), null, null, null, 1);
+
+        $filter = new RoleFilter('american user');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('american *');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('american');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('user');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('*user');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+        
+        $filter = new RoleFilter('[\w\s]+user');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $filter = new RoleFilter('American User');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, 1);
+        $filter = new RoleFilter('American User');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+    }
+}

+ 144 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Filter/TagFilterTest.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace Tests\Behat\Gherkin\Filter;
+
+use Behat\Gherkin\Filter\TagFilter;
+use Behat\Gherkin\Node\FeatureNode;
+use Behat\Gherkin\Node\ScenarioNode;
+
+class TagFilterTest extends \PHPUnit_Framework_TestCase
+{
+    public function testFilterFeature()
+    {
+        $feature = new FeatureNode(null, null, array('wip'), null, array(), null, null, null, 1);
+        $filter = new TagFilter('@wip');
+        $this->assertEquals($feature, $filter->filterFeature($feature));
+
+        $scenarios = array(
+            new ScenarioNode(null, array(), array(), null, 2),
+            $matchedScenario = new ScenarioNode(null, array('wip'), array(), null, 4)
+        );
+        $feature = new FeatureNode(null, null, array(), null, $scenarios, null, null, null, 1);
+        $filteredFeature = $filter->filterFeature($feature);
+
+        $this->assertSame(array($matchedScenario), $filteredFeature->getScenarios());
+
+        $filter = new TagFilter('~@wip');
+        $scenarios = array(
+            $matchedScenario = new ScenarioNode(null, array(), array(), null, 2),
+            new ScenarioNode(null, array('wip'), array(), null, 4)
+        );
+        $feature = new FeatureNode(null, null, array(), null, $scenarios, null, null, null, 1);
+        $filteredFeature = $filter->filterFeature($feature);
+
+        $this->assertSame(array($matchedScenario), $filteredFeature->getScenarios());
+    }
+
+    public function testIsFeatureMatchFilter()
+    {
+        $feature = new FeatureNode(null, null, array(), null, array(), null, null, null, 1);
+
+        $filter = new TagFilter('@wip');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('wip'), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new TagFilter('~@done');
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('wip', 'done'), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('tag1', 'tag2', 'tag3'), null, array(), null, null, null, 1);
+        $filter = new TagFilter('@tag5,@tag4,@tag6');
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array(
+            'tag1',
+            'tag2',
+            'tag3',
+            'tag5'
+        ), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new TagFilter('@wip&&@vip');
+        $feature = new FeatureNode(null, null, array('wip', 'done'), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('wip', 'done', 'vip'), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $filter = new TagFilter('@wip,@vip&&@user');
+        $feature = new FeatureNode(null, null, array('wip'), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('vip'), null, array(), null, null, null, 1);
+        $this->assertFalse($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('wip', 'user'), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+
+        $feature = new FeatureNode(null, null, array('vip', 'user'), null, array(), null, null, null, 1);
+        $this->assertTrue($filter->isFeatureMatch($feature));
+    }
+
+    public function testIsScenarioMatchFilter()
+    {
+        $feature = new FeatureNode(null, null, array('feature-tag'), null, array(), null, null, null, 1);
+        $scenario = new ScenarioNode(null, array(), array(), null, 2);
+
+        $filter = new TagFilter('@wip');
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+
+        $filter = new TagFilter('~@done');
+        $this->assertTrue($filter->isScenarioMatch($feature, $scenario));
+
+        $scenario = new ScenarioNode(null, array(
+            'tag1',
+            'tag2',
+            'tag3'
+        ), array(), null, 2);
+        $filter = new TagFilter('@tag5,@tag4,@tag6');
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+
+        $scenario = new ScenarioNode(null, array(
+            'tag1',
+            'tag2',
+            'tag3',
+            'tag5'
+        ), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($feature, $scenario));
+
+        $filter = new TagFilter('@wip&&@vip');
+        $scenario = new ScenarioNode(null, array('wip', 'not-done'), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+
+        $scenario = new ScenarioNode(null, array(
+            'wip',
+            'not-done',
+            'vip'
+        ), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($feature, $scenario));
+
+        $filter = new TagFilter('@wip,@vip&&@user');
+        $scenario = new ScenarioNode(null, array(
+            'wip'
+        ), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+
+        $scenario = new ScenarioNode(null, array('vip'), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+
+        $scenario = new ScenarioNode(null, array('wip', 'user'), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($feature, $scenario));
+
+        $filter = new TagFilter('@feature-tag&&@user');
+        $scenario = new ScenarioNode(null, array('wip', 'user'), array(), null, 2);
+        $this->assertTrue($filter->isScenarioMatch($feature, $scenario));
+
+        $filter = new TagFilter('@feature-tag&&@user');
+        $scenario = new ScenarioNode(null, array('wip'), array(), null, 2);
+        $this->assertFalse($filter->isScenarioMatch($feature, $scenario));
+    }
+}

+ 0 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/directories/phps/some_file.php


+ 29 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/addition.yml

@@ -0,0 +1,29 @@
+feature:
+  title:        Addition
+  language:     en
+  line:         2
+  description:  |-
+    In order to avoid silly mistakes
+    As a math idiot
+    I want to be told the sum of two numbers
+
+  scenarios:
+    -
+      type:     scenario
+      title:    Add two numbers
+      line:     7
+      steps:
+        - { keyword_type: 'Given', type: 'Given',  text: 'I have entered 11 into the calculator',  line: 8 }
+        - { keyword_type: 'Given', type: 'And',    text: 'I have entered 12 into the calculator',  line: 9 }
+        - { keyword_type: 'When',  type: 'When',   text: 'I press add',                            line: 10 }
+        - { keyword_type: 'Then',  type: 'Then',   text: 'the result should be 23 on the screen',  line: 11 }
+
+    -
+      type:     scenario
+      title:    Div two numbers
+      line:     13
+      steps:
+        - { keyword_type: 'Given', type: 'Given',  text: 'I have entered 10 into the calculator',  line: 14 }
+        - { keyword_type: 'Given', type: 'And',    text: 'I have entered 2 into the calculator',   line: 15 }
+        - { keyword_type: 'When',  type: 'When',   text: 'I press div',                            line: 16 }
+        - { keyword_type: 'Then',  type: 'Then',   text: 'the result should be 5 on the screen',   line: 17 }

+ 18 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/background.yml

@@ -0,0 +1,18 @@
+feature:
+  title:        Feature with background
+  language:     en
+  line:         1
+  description:  ~
+
+  background:
+    line:       3
+    steps:
+      - { keyword_type: Given, type: Given, text: a passing step, line: 4 }
+
+  scenarios:
+    -
+      type:     scenario
+      title:    ~
+      line:     6
+      steps:
+        - { keyword_type: Given, type: Given, text: a failing step, line: 7 }

+ 26 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/background_title.yml

@@ -0,0 +1,26 @@
+feature:
+  title:        Feature with titled background
+  language:     en
+  line:         1
+  description:  ~
+
+  background:
+    line:       3
+    title:      |-
+      Some Background
+      title with
+       couple
+        of
+         | continuous  |
+       """
+       strings
+    steps:
+      - { keyword_type: Given, type: Given, text: a passing step, line: 10 }
+
+  scenarios:
+    -
+      type:     scenario
+      title:    ~
+      line:     12
+      steps:
+        - { keyword_type: Given, type: Given, text: a failing step, line: 13 }

+ 0 - 0
vendor/behat/gherkin/tests/Behat/Gherkin/Fixtures/etalons/big_pystring.yml


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio