├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── melody ├── box.json ├── build └── .gitkeep ├── composer.json ├── composer.lock ├── doc ├── development.md ├── features.md └── install.md ├── phpunit.xml.dist └── src └── SensioLabs └── Melody ├── Composer └── Composer.php ├── Configuration ├── RunConfiguration.php ├── RunConfigurationParser.php ├── ScriptConfiguration.php ├── UserConfiguration.php └── UserConfigurationRepository.php ├── Console ├── Application.php └── Command │ ├── RunCommand.php │ └── SelfUpdateCommand.php ├── Exception ├── ConfigException.php ├── ParseException.php └── TrustException.php ├── Handler ├── FileHandler.php ├── GistHandler.php ├── Github │ └── Gist.php ├── ResourceHandlerInterface.php └── StreamHandler.php ├── Melody.php ├── Resource ├── LocalResource.php ├── Metadata.php ├── Resource.php └── ResourceParser.php ├── Runner └── Runner.php ├── Script ├── Script.php └── ScriptBuilder.php ├── Tests ├── Composer │ └── ComposerTest.php ├── Configuration │ ├── RunConfigurationParserTest.php │ └── UserConfigurationRepositoryTest.php ├── Fixtures │ ├── config-empty.yml │ ├── config.yml │ ├── fork-repositories.php │ ├── hello-world-with-constraints.php │ ├── hello-world.phar │ ├── hello-world.php │ ├── hello-world.php.gz │ ├── php-options.php │ ├── pimple.php │ └── shebang.php ├── Handler │ ├── FileHandlerTest.php │ ├── GistHandlerTest.php │ ├── Github │ │ └── GistTest.php │ └── StreamHandlerTest.php ├── IntegrationTest.php └── WorkingDirectory │ └── WorkingDirectoryFactoryTest.php └── WorkingDirectory ├── GarbageCollector.php ├── WorkingDirectory.php └── WorkingDirectoryFactory.php /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /build/* 3 | /vendor/ 4 | !/bin/melody 5 | !/build/.gitkeep 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache/files 8 | 9 | php: 10 | - 5.5 11 | - 5.6 12 | - 7.0 13 | - 7.1 14 | - 7.2 15 | - 7.3 16 | 17 | install: 18 | - composer --prefer-dist install 19 | 20 | before_script: 21 | - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini 22 | 23 | script: 24 | - phpunit 25 | 26 | branches: 27 | only: 28 | - master 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribution guide 2 | ================== 3 | 4 | Melody is an open source project driven by [SensioLabs](http://sensiolabs.com). 5 | 6 | Reporting a bug 7 | --------------- 8 | 9 | Whenever you find a bug in Melody, we kindly ask you to report it. Before 10 | reporting a bug, please verify that it has not already been reported by someone 11 | else in the 12 | [existing bugs](https://github.com/sensiolabs/melody/issues?q=is%3Aopen+is%3Aissue). 13 | 14 | If you're the first person to hit this problem, 15 | [create an issue](https://github.com/sensiolabs/melody/issues/new) and provide 16 | us maximum information about your system: OS, PHP version, composer version. 17 | 18 | Release managers 19 | ---------------- 20 | 21 | Release managers are allowed to manage the Github repository and do the 22 | following: 23 | 24 | * They close or re-open issues; 25 | * They merge pull-requests into master; 26 | * They manage labels and milestones on issues; 27 | 28 | Github labels 29 | ------------- 30 | 31 | * **bug** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Abug)): 32 | an issue or a PR for a feature that doesn't work 33 | 34 | If the issue about a usage problem, it will probably be flagged as **bug**. 35 | 36 | * **wip** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Awip)): 37 | long-running PR 38 | 39 | This label allows other people to review the code before merging the code. 40 | 41 | It's very useful for long-running PR, and to avoid merging an unfinished 42 | feature. 43 | 44 | If you make a pull-request and plan to make it long-running, please add 45 | [WIP] also in the title, so a release manager can flag it as wip and remove 46 | it from your title. 47 | 48 | * **discuss** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Adiscuss)): 49 | an issue about a feature suggestion or changing an existing feature 50 | 51 | An issue suggesting a new feature will be flagged as **discuss**. 52 | 53 | * **refactor** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Arefactor)): 54 | an issue or a PR that is about changing the implementation of an existing 55 | feature; 56 | 57 | A refactor usually have no functional benefit, but can be required by a 58 | **new feature**, or any other issue; 59 | 60 | * **new feature** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3A"new+feature")): 61 | a feature that is decided to be done 62 | 63 | If there is no milestone attached, it's an unplanned new feature. 64 | 65 | * **duplicate** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Aduplicate)): 66 | an issue or PR that is a duplicate of another. 67 | 68 | The original issue should be referenced inside duplicated issue with a comment or in the description: 69 | 70 | > Duplicate of #32 71 | 72 | * **wontfix** ([see all](https://github.com/sensiolabs/melody/issues?q=label%3Awontfix)): 73 | an issue that cannot be fixed because of a limitation or a decision; 74 | 75 | The limitation or reasons of the decision should be exposed in the issue. 76 | 77 | Labels on the repository are managed by release managers. Users can suggest tags 78 | by adding pseudo-labels in title: 79 | 80 | > [bug][new-feature] It's not a bug, it's a new feature! 81 | 82 | Release managers are allowed to edit the user title to remove those labels and 83 | add them as labels. 84 | 85 | Acceptance criteria 86 | ------------------- 87 | 88 | * Every new feature should be documented; 89 | * Every new feature should be tested; 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 SensioLabs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Melody - One-file composer scripts 2 | ================================== 3 | 4 | Create a file named `test.php`: 5 | 6 | ```php 7 | in(__DIR__) 15 | ->files() 16 | ->name('*.php') 17 | ; 18 | 19 | foreach ($finder as $file) { 20 | echo $file, "\n"; 21 | } 22 | ``` 23 | 24 | And simply run it: 25 | 26 | ```bash 27 | $ melody run test.php 28 | ``` 29 | 30 | ![demo](http://melody.sensiolabs.org/img/melody.gif) 31 | 32 | More Information 33 | ---------------- 34 | 35 | Read the [documentation](http://melody.sensiolabs.org) for more information. 36 | -------------------------------------------------------------------------------- /bin/melody: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": "build/melody.phar", 3 | "chmod": "0755", 4 | "compactors": [ 5 | "Herrera\\Box\\Compactor\\Php" 6 | ], 7 | "compression": "GZ", 8 | "directories": ["src"], 9 | "files": [ 10 | "LICENSE" 11 | ], 12 | "finder": [ 13 | { 14 | "name": ["*.php"], 15 | "exclude": [ "Tests", "tests" ], 16 | "in": ["src", "vendor"] 17 | } 18 | ], 19 | "git-commit": "git-commit", 20 | "main": "bin/melody", 21 | "stub": true 22 | } 23 | -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensiolabs/melody/0bf7fc3347ba68a55b2a2e1b27368e2b9d3dbe41/build/.gitkeep -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sensiolabs/melody", 3 | "description": "One file composer scripts", 4 | "license": "MIT", 5 | "autoload": { 6 | "psr-4": { 7 | "SensioLabs\\Melody\\": "src/SensioLabs/Melody/" 8 | } 9 | }, 10 | "require": { 11 | "php": ">=5.5", 12 | "symfony/console": "^3.1", 13 | "symfony/filesystem": "^2.5|^3.0", 14 | "symfony/process": "^2.5|^3.0", 15 | "symfony/yaml": "^2.5|^3.0", 16 | "symfony/finder": "^2.5|^3.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "4.3.*", 20 | "mikey179/vfsStream": " ~1.4" 21 | }, 22 | "config": { 23 | "bin-dir": "bin" 24 | }, 25 | "bin": ["bin/melody"] 26 | } 27 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "c041fbda8cc15b90b63772cfc043b840", 8 | "packages": [ 9 | { 10 | "name": "psr/log", 11 | "version": "1.0.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/php-fig/log.git", 15 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 20 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3.0" 25 | }, 26 | "type": "library", 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "1.0.x-dev" 30 | } 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Psr\\Log\\": "Psr/Log/" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "PHP-FIG", 44 | "homepage": "http://www.php-fig.org/" 45 | } 46 | ], 47 | "description": "Common interface for logging libraries", 48 | "homepage": "https://github.com/php-fig/log", 49 | "keywords": [ 50 | "log", 51 | "psr", 52 | "psr-3" 53 | ], 54 | "time": "2016-10-10T12:19:37+00:00" 55 | }, 56 | { 57 | "name": "symfony/console", 58 | "version": "v3.2.8", 59 | "source": { 60 | "type": "git", 61 | "url": "https://github.com/symfony/console.git", 62 | "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38" 63 | }, 64 | "dist": { 65 | "type": "zip", 66 | "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", 67 | "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", 68 | "shasum": "" 69 | }, 70 | "require": { 71 | "php": ">=5.5.9", 72 | "symfony/debug": "~2.8|~3.0", 73 | "symfony/polyfill-mbstring": "~1.0" 74 | }, 75 | "require-dev": { 76 | "psr/log": "~1.0", 77 | "symfony/event-dispatcher": "~2.8|~3.0", 78 | "symfony/filesystem": "~2.8|~3.0", 79 | "symfony/process": "~2.8|~3.0" 80 | }, 81 | "suggest": { 82 | "psr/log": "For using the console logger", 83 | "symfony/event-dispatcher": "", 84 | "symfony/filesystem": "", 85 | "symfony/process": "" 86 | }, 87 | "type": "library", 88 | "extra": { 89 | "branch-alias": { 90 | "dev-master": "3.2-dev" 91 | } 92 | }, 93 | "autoload": { 94 | "psr-4": { 95 | "Symfony\\Component\\Console\\": "" 96 | }, 97 | "exclude-from-classmap": [ 98 | "/Tests/" 99 | ] 100 | }, 101 | "notification-url": "https://packagist.org/downloads/", 102 | "license": [ 103 | "MIT" 104 | ], 105 | "authors": [ 106 | { 107 | "name": "Fabien Potencier", 108 | "email": "fabien@symfony.com" 109 | }, 110 | { 111 | "name": "Symfony Community", 112 | "homepage": "https://symfony.com/contributors" 113 | } 114 | ], 115 | "description": "Symfony Console Component", 116 | "homepage": "https://symfony.com", 117 | "time": "2017-04-26T01:39:17+00:00" 118 | }, 119 | { 120 | "name": "symfony/debug", 121 | "version": "v3.2.8", 122 | "source": { 123 | "type": "git", 124 | "url": "https://github.com/symfony/debug.git", 125 | "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686" 126 | }, 127 | "dist": { 128 | "type": "zip", 129 | "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686", 130 | "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686", 131 | "shasum": "" 132 | }, 133 | "require": { 134 | "php": ">=5.5.9", 135 | "psr/log": "~1.0" 136 | }, 137 | "conflict": { 138 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 139 | }, 140 | "require-dev": { 141 | "symfony/class-loader": "~2.8|~3.0", 142 | "symfony/http-kernel": "~2.8|~3.0" 143 | }, 144 | "type": "library", 145 | "extra": { 146 | "branch-alias": { 147 | "dev-master": "3.2-dev" 148 | } 149 | }, 150 | "autoload": { 151 | "psr-4": { 152 | "Symfony\\Component\\Debug\\": "" 153 | }, 154 | "exclude-from-classmap": [ 155 | "/Tests/" 156 | ] 157 | }, 158 | "notification-url": "https://packagist.org/downloads/", 159 | "license": [ 160 | "MIT" 161 | ], 162 | "authors": [ 163 | { 164 | "name": "Fabien Potencier", 165 | "email": "fabien@symfony.com" 166 | }, 167 | { 168 | "name": "Symfony Community", 169 | "homepage": "https://symfony.com/contributors" 170 | } 171 | ], 172 | "description": "Symfony Debug Component", 173 | "homepage": "https://symfony.com", 174 | "time": "2017-04-19T20:17:50+00:00" 175 | }, 176 | { 177 | "name": "symfony/filesystem", 178 | "version": "v3.2.8", 179 | "source": { 180 | "type": "git", 181 | "url": "https://github.com/symfony/filesystem.git", 182 | "reference": "040651db13cf061827a460cc10f6e36a445c45b4" 183 | }, 184 | "dist": { 185 | "type": "zip", 186 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4", 187 | "reference": "040651db13cf061827a460cc10f6e36a445c45b4", 188 | "shasum": "" 189 | }, 190 | "require": { 191 | "php": ">=5.5.9" 192 | }, 193 | "type": "library", 194 | "extra": { 195 | "branch-alias": { 196 | "dev-master": "3.2-dev" 197 | } 198 | }, 199 | "autoload": { 200 | "psr-4": { 201 | "Symfony\\Component\\Filesystem\\": "" 202 | }, 203 | "exclude-from-classmap": [ 204 | "/Tests/" 205 | ] 206 | }, 207 | "notification-url": "https://packagist.org/downloads/", 208 | "license": [ 209 | "MIT" 210 | ], 211 | "authors": [ 212 | { 213 | "name": "Fabien Potencier", 214 | "email": "fabien@symfony.com" 215 | }, 216 | { 217 | "name": "Symfony Community", 218 | "homepage": "https://symfony.com/contributors" 219 | } 220 | ], 221 | "description": "Symfony Filesystem Component", 222 | "homepage": "https://symfony.com", 223 | "time": "2017-04-12T14:13:17+00:00" 224 | }, 225 | { 226 | "name": "symfony/finder", 227 | "version": "v3.2.8", 228 | "source": { 229 | "type": "git", 230 | "url": "https://github.com/symfony/finder.git", 231 | "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" 232 | }, 233 | "dist": { 234 | "type": "zip", 235 | "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", 236 | "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", 237 | "shasum": "" 238 | }, 239 | "require": { 240 | "php": ">=5.5.9" 241 | }, 242 | "type": "library", 243 | "extra": { 244 | "branch-alias": { 245 | "dev-master": "3.2-dev" 246 | } 247 | }, 248 | "autoload": { 249 | "psr-4": { 250 | "Symfony\\Component\\Finder\\": "" 251 | }, 252 | "exclude-from-classmap": [ 253 | "/Tests/" 254 | ] 255 | }, 256 | "notification-url": "https://packagist.org/downloads/", 257 | "license": [ 258 | "MIT" 259 | ], 260 | "authors": [ 261 | { 262 | "name": "Fabien Potencier", 263 | "email": "fabien@symfony.com" 264 | }, 265 | { 266 | "name": "Symfony Community", 267 | "homepage": "https://symfony.com/contributors" 268 | } 269 | ], 270 | "description": "Symfony Finder Component", 271 | "homepage": "https://symfony.com", 272 | "time": "2017-04-12T14:13:17+00:00" 273 | }, 274 | { 275 | "name": "symfony/polyfill-mbstring", 276 | "version": "v1.3.0", 277 | "source": { 278 | "type": "git", 279 | "url": "https://github.com/symfony/polyfill-mbstring.git", 280 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" 281 | }, 282 | "dist": { 283 | "type": "zip", 284 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", 285 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", 286 | "shasum": "" 287 | }, 288 | "require": { 289 | "php": ">=5.3.3" 290 | }, 291 | "suggest": { 292 | "ext-mbstring": "For best performance" 293 | }, 294 | "type": "library", 295 | "extra": { 296 | "branch-alias": { 297 | "dev-master": "1.3-dev" 298 | } 299 | }, 300 | "autoload": { 301 | "psr-4": { 302 | "Symfony\\Polyfill\\Mbstring\\": "" 303 | }, 304 | "files": [ 305 | "bootstrap.php" 306 | ] 307 | }, 308 | "notification-url": "https://packagist.org/downloads/", 309 | "license": [ 310 | "MIT" 311 | ], 312 | "authors": [ 313 | { 314 | "name": "Nicolas Grekas", 315 | "email": "p@tchwork.com" 316 | }, 317 | { 318 | "name": "Symfony Community", 319 | "homepage": "https://symfony.com/contributors" 320 | } 321 | ], 322 | "description": "Symfony polyfill for the Mbstring extension", 323 | "homepage": "https://symfony.com", 324 | "keywords": [ 325 | "compatibility", 326 | "mbstring", 327 | "polyfill", 328 | "portable", 329 | "shim" 330 | ], 331 | "time": "2016-11-14T01:06:16+00:00" 332 | }, 333 | { 334 | "name": "symfony/process", 335 | "version": "v3.2.8", 336 | "source": { 337 | "type": "git", 338 | "url": "https://github.com/symfony/process.git", 339 | "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0" 340 | }, 341 | "dist": { 342 | "type": "zip", 343 | "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", 344 | "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", 345 | "shasum": "" 346 | }, 347 | "require": { 348 | "php": ">=5.5.9" 349 | }, 350 | "type": "library", 351 | "extra": { 352 | "branch-alias": { 353 | "dev-master": "3.2-dev" 354 | } 355 | }, 356 | "autoload": { 357 | "psr-4": { 358 | "Symfony\\Component\\Process\\": "" 359 | }, 360 | "exclude-from-classmap": [ 361 | "/Tests/" 362 | ] 363 | }, 364 | "notification-url": "https://packagist.org/downloads/", 365 | "license": [ 366 | "MIT" 367 | ], 368 | "authors": [ 369 | { 370 | "name": "Fabien Potencier", 371 | "email": "fabien@symfony.com" 372 | }, 373 | { 374 | "name": "Symfony Community", 375 | "homepage": "https://symfony.com/contributors" 376 | } 377 | ], 378 | "description": "Symfony Process Component", 379 | "homepage": "https://symfony.com", 380 | "time": "2017-04-12T14:13:17+00:00" 381 | }, 382 | { 383 | "name": "symfony/yaml", 384 | "version": "v2.8.20", 385 | "source": { 386 | "type": "git", 387 | "url": "https://github.com/symfony/yaml.git", 388 | "reference": "93ccdde79f4b079c7558da4656a3cb1c50c68e02" 389 | }, 390 | "dist": { 391 | "type": "zip", 392 | "url": "https://api.github.com/repos/symfony/yaml/zipball/93ccdde79f4b079c7558da4656a3cb1c50c68e02", 393 | "reference": "93ccdde79f4b079c7558da4656a3cb1c50c68e02", 394 | "shasum": "" 395 | }, 396 | "require": { 397 | "php": ">=5.3.9" 398 | }, 399 | "type": "library", 400 | "extra": { 401 | "branch-alias": { 402 | "dev-master": "2.8-dev" 403 | } 404 | }, 405 | "autoload": { 406 | "psr-4": { 407 | "Symfony\\Component\\Yaml\\": "" 408 | }, 409 | "exclude-from-classmap": [ 410 | "/Tests/" 411 | ] 412 | }, 413 | "notification-url": "https://packagist.org/downloads/", 414 | "license": [ 415 | "MIT" 416 | ], 417 | "authors": [ 418 | { 419 | "name": "Fabien Potencier", 420 | "email": "fabien@symfony.com" 421 | }, 422 | { 423 | "name": "Symfony Community", 424 | "homepage": "https://symfony.com/contributors" 425 | } 426 | ], 427 | "description": "Symfony Yaml Component", 428 | "homepage": "https://symfony.com", 429 | "time": "2017-05-01T14:31:55+00:00" 430 | } 431 | ], 432 | "packages-dev": [ 433 | { 434 | "name": "doctrine/instantiator", 435 | "version": "1.0.5", 436 | "source": { 437 | "type": "git", 438 | "url": "https://github.com/doctrine/instantiator.git", 439 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 440 | }, 441 | "dist": { 442 | "type": "zip", 443 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 444 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 445 | "shasum": "" 446 | }, 447 | "require": { 448 | "php": ">=5.3,<8.0-DEV" 449 | }, 450 | "require-dev": { 451 | "athletic/athletic": "~0.1.8", 452 | "ext-pdo": "*", 453 | "ext-phar": "*", 454 | "phpunit/phpunit": "~4.0", 455 | "squizlabs/php_codesniffer": "~2.0" 456 | }, 457 | "type": "library", 458 | "extra": { 459 | "branch-alias": { 460 | "dev-master": "1.0.x-dev" 461 | } 462 | }, 463 | "autoload": { 464 | "psr-4": { 465 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 466 | } 467 | }, 468 | "notification-url": "https://packagist.org/downloads/", 469 | "license": [ 470 | "MIT" 471 | ], 472 | "authors": [ 473 | { 474 | "name": "Marco Pivetta", 475 | "email": "ocramius@gmail.com", 476 | "homepage": "http://ocramius.github.com/" 477 | } 478 | ], 479 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 480 | "homepage": "https://github.com/doctrine/instantiator", 481 | "keywords": [ 482 | "constructor", 483 | "instantiate" 484 | ], 485 | "time": "2015-06-14T21:17:01+00:00" 486 | }, 487 | { 488 | "name": "mikey179/vfsStream", 489 | "version": "v1.6.4", 490 | "source": { 491 | "type": "git", 492 | "url": "https://github.com/mikey179/vfsStream.git", 493 | "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592" 494 | }, 495 | "dist": { 496 | "type": "zip", 497 | "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592", 498 | "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592", 499 | "shasum": "" 500 | }, 501 | "require": { 502 | "php": ">=5.3.0" 503 | }, 504 | "require-dev": { 505 | "phpunit/phpunit": "~4.5" 506 | }, 507 | "type": "library", 508 | "extra": { 509 | "branch-alias": { 510 | "dev-master": "1.6.x-dev" 511 | } 512 | }, 513 | "autoload": { 514 | "psr-0": { 515 | "org\\bovigo\\vfs\\": "src/main/php" 516 | } 517 | }, 518 | "notification-url": "https://packagist.org/downloads/", 519 | "license": [ 520 | "BSD-3-Clause" 521 | ], 522 | "authors": [ 523 | { 524 | "name": "Frank Kleine", 525 | "homepage": "http://frankkleine.de/", 526 | "role": "Developer" 527 | } 528 | ], 529 | "description": "Virtual file system to mock the real file system in unit tests.", 530 | "homepage": "http://vfs.bovigo.org/", 531 | "time": "2016-07-18T14:02:57+00:00" 532 | }, 533 | { 534 | "name": "phpunit/php-code-coverage", 535 | "version": "2.2.4", 536 | "source": { 537 | "type": "git", 538 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 539 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" 540 | }, 541 | "dist": { 542 | "type": "zip", 543 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", 544 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", 545 | "shasum": "" 546 | }, 547 | "require": { 548 | "php": ">=5.3.3", 549 | "phpunit/php-file-iterator": "~1.3", 550 | "phpunit/php-text-template": "~1.2", 551 | "phpunit/php-token-stream": "~1.3", 552 | "sebastian/environment": "^1.3.2", 553 | "sebastian/version": "~1.0" 554 | }, 555 | "require-dev": { 556 | "ext-xdebug": ">=2.1.4", 557 | "phpunit/phpunit": "~4" 558 | }, 559 | "suggest": { 560 | "ext-dom": "*", 561 | "ext-xdebug": ">=2.2.1", 562 | "ext-xmlwriter": "*" 563 | }, 564 | "type": "library", 565 | "extra": { 566 | "branch-alias": { 567 | "dev-master": "2.2.x-dev" 568 | } 569 | }, 570 | "autoload": { 571 | "classmap": [ 572 | "src/" 573 | ] 574 | }, 575 | "notification-url": "https://packagist.org/downloads/", 576 | "license": [ 577 | "BSD-3-Clause" 578 | ], 579 | "authors": [ 580 | { 581 | "name": "Sebastian Bergmann", 582 | "email": "sb@sebastian-bergmann.de", 583 | "role": "lead" 584 | } 585 | ], 586 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 587 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 588 | "keywords": [ 589 | "coverage", 590 | "testing", 591 | "xunit" 592 | ], 593 | "time": "2015-10-06T15:47:00+00:00" 594 | }, 595 | { 596 | "name": "phpunit/php-file-iterator", 597 | "version": "1.3.4", 598 | "source": { 599 | "type": "git", 600 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 601 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 602 | }, 603 | "dist": { 604 | "type": "zip", 605 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 606 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 607 | "shasum": "" 608 | }, 609 | "require": { 610 | "php": ">=5.3.3" 611 | }, 612 | "type": "library", 613 | "autoload": { 614 | "classmap": [ 615 | "File/" 616 | ] 617 | }, 618 | "notification-url": "https://packagist.org/downloads/", 619 | "include-path": [ 620 | "" 621 | ], 622 | "license": [ 623 | "BSD-3-Clause" 624 | ], 625 | "authors": [ 626 | { 627 | "name": "Sebastian Bergmann", 628 | "email": "sb@sebastian-bergmann.de", 629 | "role": "lead" 630 | } 631 | ], 632 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 633 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 634 | "keywords": [ 635 | "filesystem", 636 | "iterator" 637 | ], 638 | "time": "2013-10-10T15:34:57+00:00" 639 | }, 640 | { 641 | "name": "phpunit/php-text-template", 642 | "version": "1.2.1", 643 | "source": { 644 | "type": "git", 645 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 646 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 647 | }, 648 | "dist": { 649 | "type": "zip", 650 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 651 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 652 | "shasum": "" 653 | }, 654 | "require": { 655 | "php": ">=5.3.3" 656 | }, 657 | "type": "library", 658 | "autoload": { 659 | "classmap": [ 660 | "src/" 661 | ] 662 | }, 663 | "notification-url": "https://packagist.org/downloads/", 664 | "license": [ 665 | "BSD-3-Clause" 666 | ], 667 | "authors": [ 668 | { 669 | "name": "Sebastian Bergmann", 670 | "email": "sebastian@phpunit.de", 671 | "role": "lead" 672 | } 673 | ], 674 | "description": "Simple template engine.", 675 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 676 | "keywords": [ 677 | "template" 678 | ], 679 | "time": "2015-06-21T13:50:34+00:00" 680 | }, 681 | { 682 | "name": "phpunit/php-timer", 683 | "version": "1.0.9", 684 | "source": { 685 | "type": "git", 686 | "url": "https://github.com/sebastianbergmann/php-timer.git", 687 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 688 | }, 689 | "dist": { 690 | "type": "zip", 691 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 692 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 693 | "shasum": "" 694 | }, 695 | "require": { 696 | "php": "^5.3.3 || ^7.0" 697 | }, 698 | "require-dev": { 699 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 700 | }, 701 | "type": "library", 702 | "extra": { 703 | "branch-alias": { 704 | "dev-master": "1.0-dev" 705 | } 706 | }, 707 | "autoload": { 708 | "classmap": [ 709 | "src/" 710 | ] 711 | }, 712 | "notification-url": "https://packagist.org/downloads/", 713 | "license": [ 714 | "BSD-3-Clause" 715 | ], 716 | "authors": [ 717 | { 718 | "name": "Sebastian Bergmann", 719 | "email": "sb@sebastian-bergmann.de", 720 | "role": "lead" 721 | } 722 | ], 723 | "description": "Utility class for timing", 724 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 725 | "keywords": [ 726 | "timer" 727 | ], 728 | "time": "2017-02-26T11:10:40+00:00" 729 | }, 730 | { 731 | "name": "phpunit/php-token-stream", 732 | "version": "1.4.11", 733 | "source": { 734 | "type": "git", 735 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 736 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" 737 | }, 738 | "dist": { 739 | "type": "zip", 740 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", 741 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", 742 | "shasum": "" 743 | }, 744 | "require": { 745 | "ext-tokenizer": "*", 746 | "php": ">=5.3.3" 747 | }, 748 | "require-dev": { 749 | "phpunit/phpunit": "~4.2" 750 | }, 751 | "type": "library", 752 | "extra": { 753 | "branch-alias": { 754 | "dev-master": "1.4-dev" 755 | } 756 | }, 757 | "autoload": { 758 | "classmap": [ 759 | "src/" 760 | ] 761 | }, 762 | "notification-url": "https://packagist.org/downloads/", 763 | "license": [ 764 | "BSD-3-Clause" 765 | ], 766 | "authors": [ 767 | { 768 | "name": "Sebastian Bergmann", 769 | "email": "sebastian@phpunit.de" 770 | } 771 | ], 772 | "description": "Wrapper around PHP's tokenizer extension.", 773 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 774 | "keywords": [ 775 | "tokenizer" 776 | ], 777 | "time": "2017-02-27T10:12:30+00:00" 778 | }, 779 | { 780 | "name": "phpunit/phpunit", 781 | "version": "4.3.5", 782 | "source": { 783 | "type": "git", 784 | "url": "https://github.com/sebastianbergmann/phpunit.git", 785 | "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1" 786 | }, 787 | "dist": { 788 | "type": "zip", 789 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2dab9d593997db4abcf58d0daf798eb4e9cecfe1", 790 | "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1", 791 | "shasum": "" 792 | }, 793 | "require": { 794 | "ext-dom": "*", 795 | "ext-json": "*", 796 | "ext-pcre": "*", 797 | "ext-reflection": "*", 798 | "ext-spl": "*", 799 | "php": ">=5.3.3", 800 | "phpunit/php-code-coverage": "~2.0", 801 | "phpunit/php-file-iterator": "~1.3.2", 802 | "phpunit/php-text-template": "~1.2", 803 | "phpunit/php-timer": "~1.0.2", 804 | "phpunit/phpunit-mock-objects": "~2.3", 805 | "sebastian/comparator": "~1.0", 806 | "sebastian/diff": "~1.1", 807 | "sebastian/environment": "~1.0", 808 | "sebastian/exporter": "~1.0", 809 | "sebastian/version": "~1.0", 810 | "symfony/yaml": "~2.0" 811 | }, 812 | "suggest": { 813 | "phpunit/php-invoker": "~1.1" 814 | }, 815 | "bin": [ 816 | "phpunit" 817 | ], 818 | "type": "library", 819 | "extra": { 820 | "branch-alias": { 821 | "dev-master": "4.3.x-dev" 822 | } 823 | }, 824 | "autoload": { 825 | "classmap": [ 826 | "src/" 827 | ] 828 | }, 829 | "notification-url": "https://packagist.org/downloads/", 830 | "include-path": [ 831 | "", 832 | "../../symfony/yaml/" 833 | ], 834 | "license": [ 835 | "BSD-3-Clause" 836 | ], 837 | "authors": [ 838 | { 839 | "name": "Sebastian Bergmann", 840 | "email": "sebastian@phpunit.de", 841 | "role": "lead" 842 | } 843 | ], 844 | "description": "The PHP Unit Testing framework.", 845 | "homepage": "http://www.phpunit.de/", 846 | "keywords": [ 847 | "phpunit", 848 | "testing", 849 | "xunit" 850 | ], 851 | "time": "2014-11-11T10:11:09+00:00" 852 | }, 853 | { 854 | "name": "phpunit/phpunit-mock-objects", 855 | "version": "2.3.8", 856 | "source": { 857 | "type": "git", 858 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 859 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" 860 | }, 861 | "dist": { 862 | "type": "zip", 863 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", 864 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", 865 | "shasum": "" 866 | }, 867 | "require": { 868 | "doctrine/instantiator": "^1.0.2", 869 | "php": ">=5.3.3", 870 | "phpunit/php-text-template": "~1.2", 871 | "sebastian/exporter": "~1.2" 872 | }, 873 | "require-dev": { 874 | "phpunit/phpunit": "~4.4" 875 | }, 876 | "suggest": { 877 | "ext-soap": "*" 878 | }, 879 | "type": "library", 880 | "extra": { 881 | "branch-alias": { 882 | "dev-master": "2.3.x-dev" 883 | } 884 | }, 885 | "autoload": { 886 | "classmap": [ 887 | "src/" 888 | ] 889 | }, 890 | "notification-url": "https://packagist.org/downloads/", 891 | "license": [ 892 | "BSD-3-Clause" 893 | ], 894 | "authors": [ 895 | { 896 | "name": "Sebastian Bergmann", 897 | "email": "sb@sebastian-bergmann.de", 898 | "role": "lead" 899 | } 900 | ], 901 | "description": "Mock Object library for PHPUnit", 902 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 903 | "keywords": [ 904 | "mock", 905 | "xunit" 906 | ], 907 | "time": "2015-10-02T06:51:40+00:00" 908 | }, 909 | { 910 | "name": "sebastian/comparator", 911 | "version": "1.2.4", 912 | "source": { 913 | "type": "git", 914 | "url": "https://github.com/sebastianbergmann/comparator.git", 915 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 916 | }, 917 | "dist": { 918 | "type": "zip", 919 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 920 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 921 | "shasum": "" 922 | }, 923 | "require": { 924 | "php": ">=5.3.3", 925 | "sebastian/diff": "~1.2", 926 | "sebastian/exporter": "~1.2 || ~2.0" 927 | }, 928 | "require-dev": { 929 | "phpunit/phpunit": "~4.4" 930 | }, 931 | "type": "library", 932 | "extra": { 933 | "branch-alias": { 934 | "dev-master": "1.2.x-dev" 935 | } 936 | }, 937 | "autoload": { 938 | "classmap": [ 939 | "src/" 940 | ] 941 | }, 942 | "notification-url": "https://packagist.org/downloads/", 943 | "license": [ 944 | "BSD-3-Clause" 945 | ], 946 | "authors": [ 947 | { 948 | "name": "Jeff Welch", 949 | "email": "whatthejeff@gmail.com" 950 | }, 951 | { 952 | "name": "Volker Dusch", 953 | "email": "github@wallbash.com" 954 | }, 955 | { 956 | "name": "Bernhard Schussek", 957 | "email": "bschussek@2bepublished.at" 958 | }, 959 | { 960 | "name": "Sebastian Bergmann", 961 | "email": "sebastian@phpunit.de" 962 | } 963 | ], 964 | "description": "Provides the functionality to compare PHP values for equality", 965 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 966 | "keywords": [ 967 | "comparator", 968 | "compare", 969 | "equality" 970 | ], 971 | "time": "2017-01-29T09:50:25+00:00" 972 | }, 973 | { 974 | "name": "sebastian/diff", 975 | "version": "1.4.1", 976 | "source": { 977 | "type": "git", 978 | "url": "https://github.com/sebastianbergmann/diff.git", 979 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" 980 | }, 981 | "dist": { 982 | "type": "zip", 983 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", 984 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", 985 | "shasum": "" 986 | }, 987 | "require": { 988 | "php": ">=5.3.3" 989 | }, 990 | "require-dev": { 991 | "phpunit/phpunit": "~4.8" 992 | }, 993 | "type": "library", 994 | "extra": { 995 | "branch-alias": { 996 | "dev-master": "1.4-dev" 997 | } 998 | }, 999 | "autoload": { 1000 | "classmap": [ 1001 | "src/" 1002 | ] 1003 | }, 1004 | "notification-url": "https://packagist.org/downloads/", 1005 | "license": [ 1006 | "BSD-3-Clause" 1007 | ], 1008 | "authors": [ 1009 | { 1010 | "name": "Kore Nordmann", 1011 | "email": "mail@kore-nordmann.de" 1012 | }, 1013 | { 1014 | "name": "Sebastian Bergmann", 1015 | "email": "sebastian@phpunit.de" 1016 | } 1017 | ], 1018 | "description": "Diff implementation", 1019 | "homepage": "https://github.com/sebastianbergmann/diff", 1020 | "keywords": [ 1021 | "diff" 1022 | ], 1023 | "time": "2015-12-08T07:14:41+00:00" 1024 | }, 1025 | { 1026 | "name": "sebastian/environment", 1027 | "version": "1.3.8", 1028 | "source": { 1029 | "type": "git", 1030 | "url": "https://github.com/sebastianbergmann/environment.git", 1031 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" 1032 | }, 1033 | "dist": { 1034 | "type": "zip", 1035 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1036 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1037 | "shasum": "" 1038 | }, 1039 | "require": { 1040 | "php": "^5.3.3 || ^7.0" 1041 | }, 1042 | "require-dev": { 1043 | "phpunit/phpunit": "^4.8 || ^5.0" 1044 | }, 1045 | "type": "library", 1046 | "extra": { 1047 | "branch-alias": { 1048 | "dev-master": "1.3.x-dev" 1049 | } 1050 | }, 1051 | "autoload": { 1052 | "classmap": [ 1053 | "src/" 1054 | ] 1055 | }, 1056 | "notification-url": "https://packagist.org/downloads/", 1057 | "license": [ 1058 | "BSD-3-Clause" 1059 | ], 1060 | "authors": [ 1061 | { 1062 | "name": "Sebastian Bergmann", 1063 | "email": "sebastian@phpunit.de" 1064 | } 1065 | ], 1066 | "description": "Provides functionality to handle HHVM/PHP environments", 1067 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1068 | "keywords": [ 1069 | "Xdebug", 1070 | "environment", 1071 | "hhvm" 1072 | ], 1073 | "time": "2016-08-18T05:49:44+00:00" 1074 | }, 1075 | { 1076 | "name": "sebastian/exporter", 1077 | "version": "1.2.2", 1078 | "source": { 1079 | "type": "git", 1080 | "url": "https://github.com/sebastianbergmann/exporter.git", 1081 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" 1082 | }, 1083 | "dist": { 1084 | "type": "zip", 1085 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", 1086 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", 1087 | "shasum": "" 1088 | }, 1089 | "require": { 1090 | "php": ">=5.3.3", 1091 | "sebastian/recursion-context": "~1.0" 1092 | }, 1093 | "require-dev": { 1094 | "ext-mbstring": "*", 1095 | "phpunit/phpunit": "~4.4" 1096 | }, 1097 | "type": "library", 1098 | "extra": { 1099 | "branch-alias": { 1100 | "dev-master": "1.3.x-dev" 1101 | } 1102 | }, 1103 | "autoload": { 1104 | "classmap": [ 1105 | "src/" 1106 | ] 1107 | }, 1108 | "notification-url": "https://packagist.org/downloads/", 1109 | "license": [ 1110 | "BSD-3-Clause" 1111 | ], 1112 | "authors": [ 1113 | { 1114 | "name": "Jeff Welch", 1115 | "email": "whatthejeff@gmail.com" 1116 | }, 1117 | { 1118 | "name": "Volker Dusch", 1119 | "email": "github@wallbash.com" 1120 | }, 1121 | { 1122 | "name": "Bernhard Schussek", 1123 | "email": "bschussek@2bepublished.at" 1124 | }, 1125 | { 1126 | "name": "Sebastian Bergmann", 1127 | "email": "sebastian@phpunit.de" 1128 | }, 1129 | { 1130 | "name": "Adam Harvey", 1131 | "email": "aharvey@php.net" 1132 | } 1133 | ], 1134 | "description": "Provides the functionality to export PHP variables for visualization", 1135 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1136 | "keywords": [ 1137 | "export", 1138 | "exporter" 1139 | ], 1140 | "time": "2016-06-17T09:04:28+00:00" 1141 | }, 1142 | { 1143 | "name": "sebastian/recursion-context", 1144 | "version": "1.0.5", 1145 | "source": { 1146 | "type": "git", 1147 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1148 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" 1149 | }, 1150 | "dist": { 1151 | "type": "zip", 1152 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1153 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1154 | "shasum": "" 1155 | }, 1156 | "require": { 1157 | "php": ">=5.3.3" 1158 | }, 1159 | "require-dev": { 1160 | "phpunit/phpunit": "~4.4" 1161 | }, 1162 | "type": "library", 1163 | "extra": { 1164 | "branch-alias": { 1165 | "dev-master": "1.0.x-dev" 1166 | } 1167 | }, 1168 | "autoload": { 1169 | "classmap": [ 1170 | "src/" 1171 | ] 1172 | }, 1173 | "notification-url": "https://packagist.org/downloads/", 1174 | "license": [ 1175 | "BSD-3-Clause" 1176 | ], 1177 | "authors": [ 1178 | { 1179 | "name": "Jeff Welch", 1180 | "email": "whatthejeff@gmail.com" 1181 | }, 1182 | { 1183 | "name": "Sebastian Bergmann", 1184 | "email": "sebastian@phpunit.de" 1185 | }, 1186 | { 1187 | "name": "Adam Harvey", 1188 | "email": "aharvey@php.net" 1189 | } 1190 | ], 1191 | "description": "Provides functionality to recursively process PHP variables", 1192 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1193 | "time": "2016-10-03T07:41:43+00:00" 1194 | }, 1195 | { 1196 | "name": "sebastian/version", 1197 | "version": "1.0.6", 1198 | "source": { 1199 | "type": "git", 1200 | "url": "https://github.com/sebastianbergmann/version.git", 1201 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 1202 | }, 1203 | "dist": { 1204 | "type": "zip", 1205 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1206 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1207 | "shasum": "" 1208 | }, 1209 | "type": "library", 1210 | "autoload": { 1211 | "classmap": [ 1212 | "src/" 1213 | ] 1214 | }, 1215 | "notification-url": "https://packagist.org/downloads/", 1216 | "license": [ 1217 | "BSD-3-Clause" 1218 | ], 1219 | "authors": [ 1220 | { 1221 | "name": "Sebastian Bergmann", 1222 | "email": "sebastian@phpunit.de", 1223 | "role": "lead" 1224 | } 1225 | ], 1226 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1227 | "homepage": "https://github.com/sebastianbergmann/version", 1228 | "time": "2015-06-21T13:59:46+00:00" 1229 | } 1230 | ], 1231 | "aliases": [], 1232 | "minimum-stability": "stable", 1233 | "stability-flags": [], 1234 | "prefer-stable": false, 1235 | "prefer-lowest": false, 1236 | "platform": { 1237 | "php": ">=5.5" 1238 | }, 1239 | "platform-dev": [] 1240 | } 1241 | -------------------------------------------------------------------------------- /doc/development.md: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | Please read the [Contributing guide](../CONTRIBUTING.md). 5 | 6 | Installation 7 | ------------ 8 | 9 | Clone repository and install dependencies: 10 | 11 | ```bash 12 | $ git clone git@github.com:sensiolabs/melody.git 13 | $ cd melody 14 | $ composer install 15 | ``` 16 | 17 | Running tests 18 | ------------- 19 | 20 | A script is available to execute all projects tests. It should work after a 21 | fresh `git clone`: 22 | 23 | ```bash 24 | $ phpunit 25 | ``` 26 | 27 | Generating the PHAR 28 | ------------------- 29 | 30 | You need [box](http://box-project.org/) to build the phar, then 31 | 32 | ```bash 33 | $ box build 34 | ``` -------------------------------------------------------------------------------- /doc/features.md: -------------------------------------------------------------------------------- 1 | Available features 2 | ================== 3 | 4 | Run gists scripts 5 | ----------------- 6 | 7 | You can easily [create a gist](https://gist.github.com) to share a snippet and 8 | execute it using `melody`. Instead of downloading the file to your computer, 9 | simply pass the URL to `melody`: 10 | 11 | ```bash 12 | $ melody run https://gist.github.com/lyrixx/565752f13499a3fa17d9 13 | ``` 14 | 15 | Supported formats: 16 | 17 | * Gist Id: `565752f13499a3fa17d9` 18 | * Username/Id: `lyrixx/565752f13499a3fa17d9` 19 | * Gist URI: `https://gist.github.com/lyrixx/565752f13499a3fa17d9` 20 | 21 | Please note that `melody` can only handle gists which contain a single PHP 22 | file. It will report an error otherwise. 23 | 24 | For those users behind a proxy server, `melody` now uses the `HTTPS_PROXY` 25 | environment variable. 26 | 27 | Run streamed script 28 | ------------------- 29 | 30 | You can run scripts from every supported streams (list streams with 31 | `stream_get_wrappers`): 32 | 33 | * `http`: `http://my.private/snippets/test.php` 34 | * `ftp`: `ftp://user:password@server/public/test.php` 35 | * `php`: `php://stdin` 36 | * `data`: `data://text/plain;base64,SSBsb3ZlIFBIUAo[...]==` 37 | * `phar`: `phar:///opt/resource.phar/test.php` 38 | * `zlib`: `compress.zlib:///opt/resource.gz` 39 | * `bzip2`: `compress.bzip2:///opt/resource.bz2` 40 | 41 | Caching 42 | ------- 43 | 44 | If you ran twice or more a script with the same dependencies, theses 45 | dependencies will be cached. 46 | 47 | If you don't want this cache, you can disable the cache from the command line: 48 | 49 | ```bash 50 | $ melody run --no-cache test.php 51 | ``` 52 | 53 | Debug scripts 54 | ------------- 55 | 56 | In case you want to have a look whats going on behind the scenes, use the verbose 57 | flag make melody print output produced by Composer: 58 | 59 | ```bash 60 | $ melody run --vvv test.php 61 | ``` 62 | 63 | Download Mode 64 | ------------- 65 | 66 | There are two ways of downloading a package: `source` and `dist`. By default 67 | Melody will use the `dist` mode. 68 | 69 | If `--prefer-source` is enabled, Melody will use the `source` mode and install 70 | from source if there is one. The `--prefer-source` can be used if you don't 71 | want Composer to download release archives but do `git clone` instead. It's 72 | very useful if you suffer from API throttling. 73 | 74 | Only use this method if you know what you're doing, because `--prefer-source` 75 | is not efficient at all. 76 | 77 | ```bash 78 | $ melody run --prefer-source test.php 79 | ``` 80 | 81 | Arguments 82 | --------- 83 | 84 | Melody allows you to pass arguments to your script. 85 | 86 | The simplest way, is to add your arguments after the name of the script. 87 | 88 | ```bash 89 | $ melody run test.php arg1 arg2 90 | ``` 91 | 92 | But this method does not works with options starting by `-` or `--`, because 93 | melody will catch them. To use options, you must prepend your options by 94 | ` -- `. 95 | 96 | ```bash 97 | $ melody run test.php -- -a --arg1 arg2 98 | ``` 99 | 100 | Trust 101 | ----- 102 | 103 | By default, when you run an external resource (ie: a gist) Melody will display 104 | a warning message to let you choose if you want or not run the script. 105 | When you trust the resource, Melody will remember your answer and never 106 | again ask you for confirmation. 107 | 108 | ```bash 109 | You are running an untrusted resource 110 | URL: https://gist.github.com/565752f13499a3fa17d9 111 | Revision: #1 112 | Owner: lyrixx 113 | Created at: Fri, 05 Dec 2014 22:22:28 +0000 114 | Last update: Tue, 09 Dec 2014 13:45:02 +0000 115 | 116 | What do you want to do (show-code, continue, abort)? 117 | ``` 118 | 119 | But if you trust the resource and don't want to interact with Melody, you 120 | can pass the parameter `--trust` to the command 121 | 122 | ```bash 123 | $ melody run 565752f13499a3fa17d9 --trust 124 | ``` 125 | 126 | User Configuration 127 | ------------------ 128 | 129 | Melody stores your configuration in a file located in `~/.sensiolabs/melody.yml`. 130 | This file contains: 131 | 132 | * a list of trusted resources signatures (see section Trust). 133 | * a list of trusted users. 134 | 135 | This file is stored with a YAML syntax. You can manually edit it to complete 136 | the list of trusted user for instance. 137 | 138 | ```yaml 139 | trust: 140 | signatures: [] 141 | users: 142 | - jeremy-derusse 143 | - lyrixx 144 | ``` 145 | 146 | Front matter 147 | ------------ 148 | 149 | The script you want to run with melody **must** start with a YAML configuration 150 | embedded in a `heredoc` string named `CONFIG`. This config must contain at 151 | least one package to install. 152 | 153 | Optionally you can provide a list of options to pass to php command. It could 154 | be useful to e.g. start a php web server or define php.ini settings. 155 | 156 | ```php 157 | get('/hello/{name}', function ($name) use ($app) { 169 | return 'Hello '.$app->escape($name); 170 | }); 171 | $app->run(); 172 | ``` 173 | 174 | Beware that `CONFIG` section contents must comply with YAML syntax restrictions: 175 | 176 | * `- "silex/silex: *"` without quotes is an invalid YAML. 177 | * `- "silex/silex: ~1.2"` without quotes is a YAML object and refused by melody. 178 | * `- "-S"` without quotes is an array of arrays. 179 | 180 | 181 | Using fork and private repositories 182 | ----------------------------------- 183 | 184 | If you need to use packages not registered in Packagist repository, you can 185 | specify repositories in the YAML configuration. 186 | See [composer documentation](https://getcomposer.org/doc/05-repositories.md). 187 | 188 | ```php 189 | 2 | 3 | 9 | 10 | 11 | src/SensioLabs/Melody/Tests/ 12 | 13 | 14 | 15 | 16 | 17 | src/SensioLabs/Melody/ 18 | 19 | src/SensioLabs/Melody/Tests 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Composer/Composer.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Jérémy Derussé 14 | */ 15 | class Composer 16 | { 17 | public function __construct(array $composerCommand = null) 18 | { 19 | if (!$composerCommand) { 20 | $composerCommand = $this->guessComposerCommand(); 21 | } 22 | 23 | if (!$composerCommand) { 24 | throw new \RuntimeException('Impossible to find composer executable.'); 25 | } 26 | 27 | $this->composerCommand = $composerCommand; 28 | } 29 | 30 | public function buildProcess(array $packages, array $repositories, $dir, $preferSource = false) 31 | { 32 | $this->generateJsonFile($packages, $repositories, $dir); 33 | 34 | return $this->updateProcess($dir, $preferSource); 35 | } 36 | 37 | private function generateJsonFile(array $packages, array $repositories, $dir) 38 | { 39 | $config = array( 40 | 'require' => $packages, 41 | ); 42 | if (!empty($repositories)) { 43 | $config['repositories'] = $repositories; 44 | } 45 | file_put_contents($dir.'/composer.json', json_encode($config)); 46 | } 47 | 48 | private function updateProcess($dir, $preferSource = false) 49 | { 50 | $args = array_merge( 51 | $this->composerCommand, 52 | array('update') 53 | ); 54 | 55 | if ($preferSource) { 56 | $args[] = '--prefer-source'; 57 | } else { 58 | $args[] = '--prefer-dist'; 59 | } 60 | 61 | $process = ProcessBuilder::create($args) 62 | ->setWorkingDirectory($dir) 63 | ->setTimeout(240) 64 | // forward the PATH variable from the user running the webserver, to the subprocess 65 | // so it can find binaries like e.g. composer 66 | ->setEnv('PATH', $_SERVER['PATH']) 67 | ->getProcess() 68 | ; 69 | 70 | return $process; 71 | } 72 | 73 | public function getVendorDir() 74 | { 75 | $args = array_merge( 76 | $this->composerCommand, 77 | array( 78 | 'config', 79 | '--global', 80 | 'vendor-dir', 81 | ) 82 | ); 83 | 84 | $process = ProcessBuilder::create($args) 85 | ->getProcess() 86 | ->mustRun() 87 | ; 88 | 89 | $output = trim($process->getOutput()); 90 | 91 | // Composer could add a message telling the version is outdated like: 92 | // "This development build of composer is over 30 days old" 93 | // We should take the last line. 94 | $outputByLines = explode(PHP_EOL, $output); 95 | 96 | return end($outputByLines); 97 | } 98 | 99 | /** 100 | * Guess and build the command to call composer. 101 | * 102 | * Search: 103 | * - a executable composer (or composer.phar) 104 | * - a file composer (or composer.phar) prefixed by php 105 | */ 106 | private function guessComposerCommand() 107 | { 108 | $candidateNames = array('composer', 'composer.phar'); 109 | 110 | $executableFinder = new ExecutableFinder(); 111 | foreach ($candidateNames as $candidateName) { 112 | if ($composerPath = $executableFinder->find($candidateName, null, array(getcwd()))) { 113 | return array(realpath($composerPath)); 114 | } 115 | } 116 | 117 | foreach ($candidateNames as $candidateName) { 118 | $composerPath = sprintf('%s/%s', getcwd(), $candidateName); 119 | if (file_exists($composerPath)) { 120 | $phpFinder = new PhpExecutableFinder(); 121 | 122 | return array_merge( 123 | array( 124 | $phpFinder->find(false), 125 | $composerPath, 126 | ), 127 | $phpFinder->findArguments() 128 | ); 129 | } 130 | } 131 | 132 | return; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Configuration/RunConfiguration.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RunConfiguration 11 | { 12 | private $noCache; 13 | private $preferSource; 14 | private $trusted; 15 | 16 | public function __construct($noCache = false, $preferSource = false, $trusted = false) 17 | { 18 | $this->noCache = $noCache; 19 | $this->preferSource = $preferSource; 20 | $this->trusted = $trusted; 21 | } 22 | 23 | public function noCache() 24 | { 25 | return $this->noCache; 26 | } 27 | 28 | public function preferSource() 29 | { 30 | return $this->preferSource; 31 | } 32 | 33 | public function isTrusted() 34 | { 35 | return $this->trusted; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Configuration/RunConfigurationParser.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RunConfigurationParser 13 | { 14 | const PACKAGE_DELIMITER = ':'; 15 | const PACKAGE_REGEX = '/^((|[a-zA-Z0-9]([_.-]?[a-zA-Z0-9]+)*\\/[a-zA-Z0-9]([_.-]?[a-zA-Z0-9]+)*)|php|ext-[a-zA-Z0-9_.-]+)$/'; 16 | 17 | public function parseConfiguration($config) 18 | { 19 | if (!is_array($config)) { 20 | throw new ParseException('The configuration should be an array.'); 21 | } 22 | 23 | $packages = $this->parsePackages($config); 24 | $phpOptions = $this->parsePhpOptions($config); 25 | $repositories = $this->parseRepositories($config); 26 | 27 | return new ScriptConfiguration($packages, $phpOptions, $repositories); 28 | } 29 | 30 | private function parsePackages($config) 31 | { 32 | if (!array_key_exists('packages', $config)) { 33 | throw new ParseException('The configuration should define a "packages" key.'); 34 | } 35 | 36 | if (!is_array($config['packages'])) { 37 | throw new ParseException('The packages configuration should be an array.'); 38 | } 39 | 40 | $packages = array(); 41 | 42 | foreach ($config['packages'] as $i => $package) { 43 | if (!is_string($package)) { 44 | throw new ParseException(sprintf('The package at key "%s" should be a string, "%s" given.', $i, gettype($package))); 45 | } 46 | 47 | $packages[] = $this->extractPackage($package); 48 | } 49 | 50 | // allow empty list of config packages 51 | if ($packages) { 52 | $packages = call_user_func_array('array_merge', $packages); 53 | } 54 | 55 | return $packages; 56 | } 57 | 58 | private function extractPackage($package) 59 | { 60 | if (false === strpos($package, self::PACKAGE_DELIMITER)) { 61 | $packageName = $this->validatePackage($package); 62 | 63 | return array($packageName => '*'); 64 | } 65 | 66 | $explode = explode(self::PACKAGE_DELIMITER, $package); 67 | 68 | if (2 !== count($explode)) { 69 | throw new ParseException(sprintf('The package named "%s" is not valid. It should contain only one ":".', $explode[0])); 70 | } 71 | 72 | $packageName = $this->validatePackage($explode[0]); 73 | 74 | $version = trim($explode[1]); 75 | 76 | if (!$version) { 77 | throw new ParseException(sprintf('The package version named "%s" is not valid.', $explode[0])); 78 | } 79 | 80 | return array($packageName => $version); 81 | } 82 | 83 | private function validatePackage($package) 84 | { 85 | if (!preg_match(self::PACKAGE_REGEX, $package)) { 86 | throw new ParseException(sprintf('The package named "%s" is not valid.', $package)); 87 | } 88 | 89 | return trim($package); 90 | } 91 | 92 | private function parsePhpOptions($config) 93 | { 94 | if (!array_key_exists('php-options', $config)) { 95 | return array(); 96 | } 97 | 98 | if (!is_array($config['php-options'])) { 99 | throw new ParseException('The php-options configuration should be an array.'); 100 | } 101 | 102 | $phpOptions = array(); 103 | 104 | foreach ($config['php-options'] as $i => $phpOption) { 105 | if (!is_string($phpOption)) { 106 | throw new ParseException(sprintf('The php-option at key "%s" should be a string.', $i)); 107 | } 108 | 109 | $phpOptions[] = $phpOption; 110 | } 111 | 112 | return $phpOptions; 113 | } 114 | 115 | private function parseRepositories($config) 116 | { 117 | if (!array_key_exists('repositories', $config)) { 118 | return array(); 119 | } 120 | 121 | if (!is_array($config['repositories'])) { 122 | throw new ParseException('The repositories configuration should be an array.'); 123 | } 124 | 125 | return $config['repositories']; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Configuration/ScriptConfiguration.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ScriptConfiguration 11 | { 12 | private $packages; 13 | private $phpOptions; 14 | private $repositories; 15 | 16 | public function __construct(array $packages, array $phpOptions, array $repositories) 17 | { 18 | $this->packages = $packages; 19 | $this->phpOptions = $phpOptions; 20 | $this->repositories = $repositories; 21 | } 22 | 23 | public function getPackages() 24 | { 25 | return $this->packages; 26 | } 27 | 28 | public function getPhpOptions() 29 | { 30 | return $this->phpOptions; 31 | } 32 | 33 | public function getRepositories() 34 | { 35 | return $this->repositories; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Configuration/UserConfiguration.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class UserConfiguration 11 | { 12 | private $trustedSignatures = array(); 13 | private $trustedUsers = array(); 14 | 15 | public function toArray() 16 | { 17 | return array( 18 | 'trust' => array( 19 | 'signatures' => $this->getTrustedSignatures(), 20 | 'users' => $this->getTrustedUsers(), 21 | ), 22 | ); 23 | } 24 | 25 | public function load(array $data) 26 | { 27 | if (array_key_exists('trust', $data) && is_array($data['trust'])) { 28 | if (array_key_exists('signatures', $data['trust'])) { 29 | $this->trustedSignatures = (array) $data['trust']['signatures']; 30 | } 31 | if (array_key_exists('users', $data['trust'])) { 32 | $this->trustedUsers = (array) $data['trust']['users']; 33 | } 34 | } 35 | } 36 | 37 | public function getTrustedSignatures() 38 | { 39 | return $this->trustedSignatures; 40 | } 41 | 42 | public function addTrustedSignature($signature) 43 | { 44 | return $this->addTrustedSignatures(array($signature)); 45 | } 46 | 47 | public function addTrustedSignatures(array $signatures) 48 | { 49 | $this->trustedSignatures = array_unique( 50 | array_merge( 51 | $this->trustedSignatures, 52 | $signatures 53 | ) 54 | ); 55 | 56 | return $this; 57 | } 58 | 59 | public function getTrustedUsers() 60 | { 61 | return $this->trustedUsers; 62 | } 63 | 64 | public function addTrustedUser($user) 65 | { 66 | return $this->addTrustedUsers(array($user)); 67 | } 68 | 69 | public function addTrustedUsers(array $users) 70 | { 71 | $this->trustedUsers = array_unique( 72 | array_merge( 73 | $this->trustedUsers, 74 | $users 75 | ) 76 | ); 77 | 78 | return $this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Configuration/UserConfigurationRepository.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class UserConfigurationRepository 15 | { 16 | public function __construct($storagePath = null) 17 | { 18 | $this->storagePath = $storagePath ?: $this->getStoragePath(); 19 | } 20 | 21 | /** 22 | * Load stored configuration. Returns an empty UserConfiguration if no previous configuration was stored. 23 | * 24 | * @return UserConfiguration 25 | */ 26 | public function load() 27 | { 28 | $config = new UserConfiguration(); 29 | if (!file_exists($this->storagePath)) { 30 | return $config; 31 | } 32 | 33 | try { 34 | $data = Yaml::parse(file_get_contents($this->storagePath)); 35 | } catch (ParseException $e) { 36 | throw new ConfigException(sprintf('The config file "%s" is not a valid YAML.', $this->storagePath), 0, $e); 37 | } 38 | 39 | if (is_array($data)) { 40 | $config->load($data); 41 | } 42 | 43 | return $config; 44 | } 45 | 46 | /** 47 | * Save the given configuration. 48 | * 49 | * @param UserConfiguration $config 50 | * 51 | * @return $this 52 | */ 53 | public function save(UserConfiguration $config) 54 | { 55 | file_put_contents($this->storagePath, Yaml::dump($config->toArray(), 3, 2)); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Retrieves path to the user's HOME directory. 62 | * 63 | * @return string 64 | */ 65 | private function getStoragePath() 66 | { 67 | $storagePath = getenv('MELODY_HOME'); 68 | if (!$storagePath) { 69 | if (defined('PHP_WINDOWS_VERSION_MAJOR')) { 70 | if (!getenv('APPDATA')) { 71 | throw new \RuntimeException('The APPDATA or MELODY_HOME environment variable must be set for melody to run correctly'); 72 | } 73 | $storagePath = strtr(getenv('APPDATA'), '\\', '/').'/Sensiolabs'; 74 | } else { 75 | if (!getenv('HOME')) { 76 | throw new \RuntimeException('The HOME or MELODY_HOME environment variable must be set for melody to run correctly'); 77 | } 78 | $storagePath = rtrim(getenv('HOME'), '/').'/.sensiolabs'; 79 | } 80 | } 81 | if (!is_dir($storagePath) && !@mkdir($storagePath, 0755, true)) { 82 | throw new \RuntimeException(sprintf('The directory "%s" does not exist and could not be created.', $storagePath)); 83 | } 84 | if (!is_writable($storagePath)) { 85 | throw new \RuntimeException(sprintf('The directory "%s" is not writable.', $storagePath)); 86 | } 87 | 88 | return $storagePath.'/melody.yml'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Console/Application.php: -------------------------------------------------------------------------------- 1 | 15 | * @author Grégoire Pineau 16 | */ 17 | class Application extends BaseApplication 18 | { 19 | const LOGO = ' 20 | ::: ::: :::::::::: ::: :::::::: ::::::::: ::: ::: 21 | :+:+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+: 22 | +:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ 23 | +#+ +:+ +#+ +#++:++# +#+ +#+ +:+ +#+ +:+ +#++: 24 | +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ 25 | #+# #+# #+# #+# #+# #+# #+# #+# #+# 26 | ### ### ########## ########## ######## ######### ### 27 | '; 28 | 29 | public function __construct() 30 | { 31 | parent::__construct('Melody', Melody::VERSION); 32 | } 33 | 34 | public function getHelp() 35 | { 36 | return self::LOGO.parent::getHelp(); 37 | } 38 | 39 | public function getLongVersion() 40 | { 41 | $version = parent::getLongVersion().' by SensioLabs'; 42 | $commit = '@git-commit@'; 43 | 44 | if ('@'.'git-commit@' !== $commit) { 45 | $version .= ' ('.substr($commit, 0, 7).')'; 46 | } 47 | 48 | return $version; 49 | } 50 | 51 | protected function getDefaultCommands() 52 | { 53 | $commands = parent::getDefaultCommands(); 54 | 55 | $commands[] = new RunCommand(); 56 | if (0 === stripos(__FILE__, 'phar://')) { 57 | $commands[] = new SelfUpdateCommand(); 58 | } 59 | 60 | return $commands; 61 | } 62 | 63 | protected function getDefaultHelperSet() 64 | { 65 | $helperSet = parent::getDefaultHelperSet(); 66 | 67 | $helperSet->set(new ProcessHelper()); 68 | 69 | return $helperSet; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Console/Command/RunCommand.php: -------------------------------------------------------------------------------- 1 | 24 | * @author Grégoire Pineau 25 | */ 26 | class RunCommand extends Command 27 | { 28 | protected function configure() 29 | { 30 | $this 31 | ->setName('run') 32 | ->setDescription('execute a script') 33 | ->addArgument('script', InputArgument::REQUIRED, 'Which script do you want to run?') 34 | ->addArgument('arguments', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Which arguments do you want to pass to the script?') 35 | ->addOption('no-cache', null, InputOption::VALUE_NONE, 'If set, do not rely on previous cache.') 36 | ->addOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.') 37 | ->addOption('trust', 't', InputOption::VALUE_NONE, 'Trust the resource.') 38 | ->setHelp( 39 | <<run command executes single-file scripts using Composer 41 | 42 | php melody.phar run test.php 43 | 44 | You may also run a gist file: 45 | 46 | php melody.phar run https://gist.github.com/lyrixx/23bb3980daf65154c3d4 47 | 48 | Or a stream resource 49 | 50 | php melody.phar run http://my.private/snippets/test.php 51 | curl http://my.private/snippets/demo.php | php melody.phar run php://stdin -- --arg1 52 | 53 | If you want to debug things a little bit, it might be useful to use: 54 | 55 | php melody.phar run -vvv --no-cache test.php 56 | 57 | If you want to pass arguments to your script, use: 58 | 59 | php melody.phar run test.php -- -a --arg1 arg2 60 | 61 | If you trust a remote resource, use: 62 | 63 | php melody.phar run https://gist.github.com/lyrixx/23bb3980daf65154c3d4 --trust 64 | 65 | EOT 66 | ) 67 | ; 68 | } 69 | 70 | protected function execute(InputInterface $input, OutputInterface $output) 71 | { 72 | $melody = new Melody(); 73 | $configRepository = new UserConfigurationRepository(); 74 | $userConfig = $configRepository->load(); 75 | 76 | $processHelper = $this->getHelperSet()->get('process'); 77 | 78 | $cliExecutor = function (Process $process, $verbose) use ($output, $processHelper) { 79 | if ($verbose) { 80 | // print debugging output for the build process 81 | $processHelper->mustRun($output, $process); 82 | } else { 83 | $callback = function ($type, $text) use ($output) { 84 | if ($type == 'out' && $output instanceof ConsoleOutputInterface) { 85 | $output = $output->getErrorOutput(); 86 | } 87 | $output->write($text); 88 | }; 89 | 90 | return $process->run($callback); 91 | } 92 | }; 93 | 94 | $runConfiguration = new RunConfiguration( 95 | $input->getOption('no-cache'), 96 | $input->getOption('prefer-source'), 97 | $input->getOption('trust') 98 | ); 99 | 100 | $script = $input->getArgument('script'); 101 | $arguments = $input->getArgument('arguments'); 102 | 103 | while (true) { 104 | try { 105 | $melody->run($script, $arguments, $runConfiguration, $userConfig, $cliExecutor); 106 | 107 | return 0; 108 | } catch (TrustException $e) { 109 | if (false === $this->confirmTrust($e->getResource(), $input, $output)) { 110 | $output->writeln('Operation aborted by the user.'); 111 | 112 | return 1; 113 | } 114 | 115 | $userConfig->addTrustedSignature($e->getResource()->getSignature()); 116 | $configRepository->save($userConfig); 117 | } 118 | } 119 | } 120 | 121 | private function confirmTrust(Resource $resource, InputInterface $input, OutputInterface $output) 122 | { 123 | $message = <<You are running an untrusted resource 125 | URL: %s 126 | Revision: #%d 127 | Owner: %s 128 | Created at: %s 129 | Last update: %s 130 | 131 | EOT; 132 | $output->writeln(sprintf( 133 | $message, 134 | $resource->getMetadata()->getUri(), 135 | $resource->getMetadata()->getRevision(), 136 | $resource->getMetadata()->getOwner(), 137 | $resource->getMetadata()->getCreatedAt()->format(\DateTime::RSS), 138 | $resource->getMetadata()->getUpdatedAt()->format(\DateTime::RSS) 139 | )); 140 | 141 | $actions = array('abort', 'continue', 'show-code'); 142 | $defaultAction = 'abort'; 143 | $actionLabels = array_map(function ($action) use ($defaultAction) { 144 | if ($action === $defaultAction) { 145 | return sprintf('<%1$s>%2$s', 'default', $action); 146 | } 147 | 148 | return $action; 149 | }, $actions); 150 | 151 | $defaultStyle = clone $output->getFormatter()->getStyle('question'); 152 | $defaultStyle->setOptions(array('reverse')); 153 | $output->getFormatter()->setStyle('default', $defaultStyle); 154 | 155 | $question = new Question( 156 | sprintf('What do you want to do (%s)? ', implode(', ', $actionLabels)), 157 | $defaultAction 158 | ); 159 | $question->setAutocompleterValues($actions); 160 | $action = $this->getHelper('question')->ask($input, $output, $question); 161 | 162 | if ($action === 'show-code') { 163 | $output->writeln(PHP_EOL.$resource->getContent().PHP_EOL); 164 | 165 | $question = new ConfirmationQuestion('Do you want to continue [y/N]? ', false); 166 | 167 | return $this->getHelper('question')->ask($input, $output, $question); 168 | } 169 | 170 | return $action === 'continue'; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Console/Command/SelfUpdateCommand.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Stephane PY 12 | * @author Grégoire Pineau 13 | */ 14 | class SelfUpdateCommand extends Command 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | protected function configure() 20 | { 21 | $this 22 | ->setName('self-update') 23 | ->setDescription('Update melody.phar to the latest version.') 24 | ->setHelp(<<%command.name% command replace your melody by the 26 | latest version from melody.sensiolabs.org. 27 | 28 | php melody %command.name% 29 | 30 | EOT 31 | ) 32 | ; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | protected function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $remoteFilename = 'http://get.sensiolabs.org/melody.phar'; 41 | $localFilename = $_SERVER['argv'][0]; 42 | 43 | if (is_writable(dirname($localFilename))) { 44 | $moveCallback = function ($tempFilename, $localFilename) { 45 | rename($tempFilename, $localFilename); 46 | }; 47 | } elseif (is_writable($localFilename)) { 48 | $moveCallback = function ($tempFilename, $localFilename) { 49 | file_put_contents($localFilename, file_get_contents($tempFilename)); 50 | unlink($tempFilename); 51 | }; 52 | } else { 53 | $output->writeln(sprintf('The file %s could not be written.', $localFilename)); 54 | $output->writeln('Please run the self-update command with higher privileges.'); 55 | 56 | return 1; 57 | } 58 | 59 | $tempDirectory = sys_get_temp_dir(); 60 | if (!is_writable($tempDirectory)) { 61 | $output->writeln(sprintf('The temporary directory %s could not be written.', $tempDirectory)); 62 | $output->writeln('Please run the self-update command with higher privileges.'); 63 | 64 | return 1; 65 | } 66 | 67 | $tempFilename = sprintf('%s/melody-%s.phar', $tempDirectory, uniqid()); 68 | 69 | @copy($remoteFilename, $tempFilename); 70 | if (!file_exists($tempFilename)) { 71 | $output->writeln('Unable to download new versions from the server.'); 72 | 73 | return 1; 74 | } 75 | 76 | chmod($tempFilename, 0777 & ~umask()); 77 | try { 78 | // test the phar validity 79 | $phar = new \Phar($tempFilename); 80 | 81 | // free the variable to unlock the file 82 | unset($phar); 83 | } catch (\Exception $e) { 84 | unlink($tempFilename); 85 | $output->writeln(sprintf('The download is corrupt (%s).', $e->getMessage())); 86 | $output->writeln('Please re-run the self-update command to try again.'); 87 | 88 | return 1; 89 | } 90 | 91 | call_user_func($moveCallback, $tempFilename, $localFilename); 92 | 93 | $output->writeln('melody updated.'); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Exception/ConfigException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ConfigException extends \LogicException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Exception/ParseException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ParseException extends \LogicException 11 | { 12 | public function __construct($message = null, $code = 0, \Exception $e = null) 13 | { 14 | parent::__construct($this->getHelp($message), $code, $e); 15 | } 16 | 17 | private function getHelp($message = null) 18 | { 19 | return << 11 | */ 12 | class TrustException extends \LogicException 13 | { 14 | private $resource; 15 | 16 | public function __construct(Resource $resource, $message = 'You are running an untrusted resource', $code = 0, \Exception $e = null) 17 | { 18 | parent::__construct($message, $code, $e); 19 | $this->resource = $resource; 20 | } 21 | 22 | public function getResource() 23 | { 24 | return $this->resource; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Handler/FileHandler.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Grégoire Pineau 13 | */ 14 | class FileHandler implements ResourceHandlerInterface 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function supports($filename) 20 | { 21 | return is_file($filename) && is_readable($filename); 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function createResource($filename) 28 | { 29 | $stat = stat($filename); 30 | $metadata = new Metadata( 31 | $stat['ino'], 32 | $stat['uid'], 33 | new \DateTime(date('c', $stat['ctime'])), 34 | new \DateTime(date('c', $stat['mtime'])), 35 | 1, 36 | sprintf('file://%s', realpath($filename)) 37 | ); 38 | 39 | $content = file_get_contents($filename); 40 | if ('#!' === substr($content, 0, 2)) { 41 | $content = explode("\n", $content."\n", 2)[1]; 42 | } 43 | 44 | return new LocalResource($filename, $content, $metadata); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Handler/GistHandler.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Grégoire Pineau 14 | * @author Jérémy Derussé 15 | */ 16 | class GistHandler implements ResourceHandlerInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function supports($uri) 22 | { 23 | return 0 !== preg_match(Gist::URI_PATTERN, $uri); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function createResource($uri) 30 | { 31 | $gist = new Gist($uri); 32 | $data = $gist->get(); 33 | 34 | if (array_key_exists('message', $data)) { 35 | throw new \InvalidArgumentException('There is an issue with your gist URL: '.$data['message']); 36 | } 37 | 38 | $files = $data['files']; 39 | 40 | // Throw an error if the gist contains multiple files 41 | if (1 !== count($files)) { 42 | throw new \InvalidArgumentException('The gist should contain a single file'); 43 | } 44 | 45 | // Fetch the only element in the array 46 | $file = current($files); 47 | $metadata = new Metadata( 48 | $data['id'], 49 | $data['owner']['login'], 50 | new \DateTime($data['created_at']), 51 | new \DateTime($data['updated_at']), 52 | count($data['history']), 53 | $data['html_url'] 54 | ); 55 | 56 | return new Resource($file['content'], $metadata); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Handler/Github/Gist.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Gist 11 | { 12 | const URI_PATTERN = '#^(?:https://gist.github.com/)?(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\/)?([0-9a-f]+)$#'; 13 | 14 | private $id; 15 | private $content; 16 | 17 | /** 18 | * Extracts a gist's information from a gist URL. 19 | * 20 | * @param string $url The gist's URL 21 | * 22 | * @return array 23 | * 24 | * @throws \InvalidArgumentException 25 | */ 26 | public function __construct($url) 27 | { 28 | if (!preg_match(self::URI_PATTERN, $url, $matches)) { 29 | throw new \InvalidArgumentException(sprintf('"%s" does not seem to be a Gist URL.', $url)); 30 | } 31 | 32 | $this->id = $matches[1]; 33 | } 34 | 35 | public function get() 36 | { 37 | if (null === $this->content) { 38 | $this->content = $this->download(); 39 | } 40 | 41 | return $this->content; 42 | } 43 | 44 | /** 45 | * Call Github and return JSON data. 46 | * 47 | * @return mixed the content of API call to Github 48 | */ 49 | public function download() 50 | { 51 | $handle = curl_init(); 52 | $http_proxy = filter_input(INPUT_ENV, 'HTTPS_PROXY', FILTER_SANITIZE_URL); 53 | 54 | curl_setopt_array($handle, array( 55 | CURLOPT_URL => sprintf('https://api.github.com/gists/%s', $this->id), 56 | CURLOPT_HTTPHEADER => array( 57 | 'Accept: application/vnd.github.v3+json', 58 | 'User-Agent: Melody-Script', 59 | ), 60 | CURLOPT_RETURNTRANSFER => 1, 61 | )); 62 | 63 | if ($http_proxy) { 64 | curl_setopt($handle, CURLOPT_PROXY, $http_proxy); 65 | } 66 | 67 | $content = curl_exec($handle); 68 | curl_close($handle); 69 | 70 | if (!$content) { 71 | throw new \InvalidArgumentException(sprintf('Gist "%s" not found', $this->id)); 72 | } 73 | 74 | return json_decode($content, true); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Handler/ResourceHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Grégoire Pineau 12 | */ 13 | interface ResourceHandlerInterface 14 | { 15 | /** 16 | * Returns whether the filename is supported or not by the current handler. 17 | * 18 | * @param string $filename 19 | * 20 | * @return bool 21 | */ 22 | public function supports($filename); 23 | 24 | /** 25 | * Creates a new resources, based on a filename. 26 | * 27 | * @param string $filename 28 | * 29 | * @return Resource 30 | */ 31 | public function createResource($filename); 32 | } 33 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Handler/StreamHandler.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StreamHandler implements ResourceHandlerInterface 14 | { 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function supports($filename) 19 | { 20 | $opened = @fopen($filename, 'r'); 21 | 22 | return false !== $opened; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function createResource($filename) 29 | { 30 | $metadata = new Metadata( 31 | basename($filename), 32 | null, 33 | new \DateTime(), 34 | new \DateTime(), 35 | 1, 36 | $filename 37 | ); 38 | 39 | return new Resource(file_get_contents($filename), $metadata); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Melody.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Melody 25 | { 26 | const VERSION = '1.0'; 27 | 28 | private $garbageCollector; 29 | private $handlers; 30 | private $wdFactory; 31 | private $scriptBuilder; 32 | private $composer; 33 | private $runner; 34 | 35 | public function __construct() 36 | { 37 | $storagePath = sprintf('%s/melody', sys_get_temp_dir()); 38 | $this->garbageCollector = new GarbageCollector($storagePath); 39 | $this->handlers = array( 40 | new FileHandler(), 41 | new GistHandler(), 42 | new StreamHandler(), 43 | ); 44 | $this->scriptBuilder = new ScriptBuilder(); 45 | $this->wdFactory = new WorkingDirectoryFactory($storagePath); 46 | $this->composer = new Composer(); 47 | $this->runner = new Runner($this->composer->getVendorDir()); 48 | } 49 | 50 | public function run($resourceName, array $arguments, RunConfiguration $runConfiguration, UserConfiguration $userConfiguration, $cliExecutor) 51 | { 52 | $this->garbageCollector->run(); 53 | 54 | $resource = $this->createResource($resourceName); 55 | $this->assertTrustedResource($resource, $runConfiguration, $userConfiguration); 56 | 57 | $script = $this->scriptBuilder->buildScript($resource, $arguments); 58 | 59 | $workingDirectory = $this->wdFactory->createTmpDir($script->getPackages(), $script->getRepositories()); 60 | 61 | if ($runConfiguration->noCache()) { 62 | $workingDirectory->clear(); 63 | } 64 | 65 | if ($workingDirectory->isNew()) { 66 | $workingDirectory->create(); 67 | 68 | $process = $this->composer->buildProcess( 69 | $script->getPackages(), 70 | $script->getRepositories(), 71 | $workingDirectory->getPath(), 72 | $runConfiguration->preferSource() 73 | ); 74 | 75 | $cliExecutor($process, true); 76 | 77 | // errors were already sent by the cliExecutor, just stop further processing 78 | if ($process->getExitCode() !== 0) { 79 | return; 80 | } 81 | 82 | $workingDirectory->lock(); 83 | } 84 | 85 | $process = $this->runner->getProcess($script, $workingDirectory->getPath()); 86 | 87 | return $cliExecutor($process, false); 88 | } 89 | 90 | /** 91 | * @param string $resourceName path to the resource 92 | * 93 | * @return Resource 94 | */ 95 | private function createResource($resourceName) 96 | { 97 | foreach ($this->handlers as $handler) { 98 | if (!$handler->supports($resourceName)) { 99 | continue; 100 | } 101 | 102 | return $handler->createResource($resourceName); 103 | } 104 | 105 | throw new \LogicException(sprintf('No handler found for resource "%s".', $resourceName)); 106 | } 107 | 108 | private function assertTrustedResource(Resource $resource, RunConfiguration $runConfiguration, UserConfiguration $userConfiguration) 109 | { 110 | if ($resource instanceof LocalResource 111 | || $runConfiguration->isTrusted() 112 | || in_array($resource->getMetadata()->getOwner(), $userConfiguration->getTrustedUsers()) 113 | || in_array($resource->getSignature(), $userConfiguration->getTrustedSignatures()) 114 | ) { 115 | return; 116 | } 117 | 118 | throw new TrustException($resource); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Resource/LocalResource.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class LocalResource extends Resource 11 | { 12 | private $filename; 13 | 14 | public function __construct($filename, $content, Metadata $metadata) 15 | { 16 | parent::__construct($content, $metadata); 17 | $this->filename = $filename; 18 | } 19 | 20 | public function getFilename() 21 | { 22 | return $this->filename; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Resource/Metadata.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Metadata 11 | { 12 | private $id; 13 | private $owner; 14 | private $createdAd; 15 | private $updatedAt; 16 | private $revision; 17 | private $uri; 18 | 19 | public function __construct($id = null, $owner = null, \DateTime $createdAd = null, \DateTime $updatedAt = null, $revision = null, $uri = null) 20 | { 21 | $this->id = $id; 22 | $this->owner = $owner; 23 | $this->createdAd = $createdAd; 24 | $this->updatedAt = $updatedAt; 25 | $this->revision = $revision; 26 | $this->uri = $uri; 27 | } 28 | 29 | public function getId() 30 | { 31 | return $this->id; 32 | } 33 | 34 | public function getOwner() 35 | { 36 | return $this->owner; 37 | } 38 | 39 | public function getCreatedAt() 40 | { 41 | return $this->createdAd; 42 | } 43 | 44 | public function getUpdatedAt() 45 | { 46 | return $this->updatedAt; 47 | } 48 | 49 | public function getRevision() 50 | { 51 | return $this->revision; 52 | } 53 | 54 | public function getUri() 55 | { 56 | return $this->uri; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Resource/Resource.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Jérémy Derussé 10 | */ 11 | class Resource 12 | { 13 | private $content; 14 | private $metadata; 15 | 16 | public function __construct($content, Metadata $metadata = null) 17 | { 18 | $this->content = $content; 19 | $this->metadata = $metadata ?: new Metadata(); 20 | } 21 | 22 | public function getContent() 23 | { 24 | return $this->content; 25 | } 26 | 27 | public function getMetadata() 28 | { 29 | return $this->metadata; 30 | } 31 | 32 | public function getSignature() 33 | { 34 | return hash('sha512', $this->content); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Resource/ResourceParser.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ResourceParser 15 | { 16 | const MELODY_PATTERN = '{^(?:#![^\n]+\n)?<\?php\s*<<.+)CONFIG;(?P.+)$}sU'; 17 | 18 | public function parseResource(Resource $resource) 19 | { 20 | preg_match(self::MELODY_PATTERN, $resource->getContent(), $matches); 21 | 22 | if (!$matches) { 23 | throw new ParseException('Impossible to parse the content of the document.'); 24 | } 25 | 26 | try { 27 | return Yaml::parse($matches['config']); 28 | } catch (YamlException $e) { 29 | throw new ParseException('The front matter is not a valid Yaml.', 0, $e); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SensioLabs/Melody/Runner/Runner.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Runner 18 | { 19 | const BOOTSTRAP_FILENAME = 'bootstrap.php'; 20 | 21 | private $vendorDir; 22 | 23 | public function __construct($vendorDir = 'vendor') 24 | { 25 | $this->vendorDir = trim($vendorDir, '/'); 26 | } 27 | 28 | public function getProcess(Script $script, $dir) 29 | { 30 | $bootstrap = $this->getBootstrap($script->getResource()); 31 | 32 | $file = sprintf('%s/%s', $dir, self::BOOTSTRAP_FILENAME); 33 | 34 | file_put_contents($file, $bootstrap); 35 | 36 | $phpFinder = new PhpExecutableFinder(); 37 | 38 | $process = ProcessBuilder::create(array_merge( 39 | array($phpFinder->find(false)), 40 | $script->getConfiguration()->getPhpOptions(), 41 | $phpFinder->findArguments(), 42 | array($file), 43 | $script->getArguments() 44 | )) 45 | // forward the PATH variable from the user running the webserver, to the subprocess 46 | // so it can find binaries like e.g. composer 47 | ->setEnv('PATH', $_SERVER['PATH']) 48 | ->getProcess() 49 | ; 50 | 51 | if (!defined('PHP_WINDOWS_VERSION_BUILD') && PHP_SAPI === 'cli') { 52 | try { 53 | $process->setTty(true); 54 | } catch (RuntimeException $e) { 55 | } 56 | } 57 | 58 | return $process; 59 | } 60 | 61 | private function getBootstrap(Resource $resource) 62 | { 63 | $template = <<