├── .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 | 
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%1$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 = <<{{ code }}
66 |
67 | TEMPLATE;
68 |
69 | return strtr($template, array(
70 | '{{ head }}' => $this->getHead(),
71 | '{{ code }}' => $resource->getContent(),
72 | ));
73 | }
74 |
75 | private function getHead()
76 | {
77 | $head = <<<'HEAD'
78 | $this->vendorDir,
95 | ));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Script/Script.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Script
14 | {
15 | private $resource;
16 | private $arguments;
17 | private $configuration;
18 |
19 | public function __construct(Resource $resource, array $arguments, ScriptConfiguration $configuration)
20 | {
21 | $this->resource = $resource;
22 | $this->arguments = $arguments;
23 | $this->configuration = $configuration;
24 | }
25 |
26 | public function getResource()
27 | {
28 | return $this->resource;
29 | }
30 |
31 | public function getArguments()
32 | {
33 | return $this->arguments;
34 | }
35 |
36 | public function getConfiguration()
37 | {
38 | return $this->configuration;
39 | }
40 |
41 | public function getPackages()
42 | {
43 | return $this->configuration->getPackages();
44 | }
45 |
46 | public function getRepositories()
47 | {
48 | return $this->configuration->getRepositories();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Script/ScriptBuilder.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class ScriptBuilder
15 | {
16 | private $resourceParser;
17 | private $configurationParser;
18 |
19 | public function __construct()
20 | {
21 | $this->resourceParser = new ResourceParser();
22 | $this->configurationParser = new RunConfigurationParser();
23 | }
24 |
25 | public function buildScript(Resource $resource, array $arguments)
26 | {
27 | $config = $this->resourceParser->parseResource($resource);
28 |
29 | $configuration = $this->configurationParser->parseConfiguration($config);
30 |
31 | return new Script($resource, $arguments, $configuration);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Composer/ComposerTest.php:
--------------------------------------------------------------------------------
1 | workingDirPath = vfsStream::url('workingDir');
17 | $this->composer = new Composer();
18 | }
19 |
20 | protected function tearDown()
21 | {
22 | $this->composer = null;
23 | }
24 |
25 | public function testBuildProcessWithoutPreferSource()
26 | {
27 | $packages = array(
28 | 'symfony/symfony' => '*',
29 | );
30 |
31 | $process = $this->composer->buildProcess($packages, array(), $this->workingDirPath);
32 |
33 | $this->assertStringEndsWith(" 'update' '--prefer-dist'", $process->getCommandLine());
34 | }
35 |
36 | public function testBuildProcessWithPreferSource()
37 | {
38 | $packages = array(
39 | 'symfony/symfony' => '*',
40 | );
41 |
42 | $process = $this->composer->buildProcess($packages, array(), $this->workingDirPath, true);
43 |
44 | $this->assertStringEndsWith(" 'update' '--prefer-source'", $process->getCommandLine());
45 | }
46 |
47 | public function testBuildProcessWithPackages()
48 | {
49 | $packages = array(
50 | 'symfony/symfony' => '*',
51 | );
52 |
53 | $this->composer->buildProcess($packages, array(), $this->workingDirPath);
54 |
55 | $this->assertComposerJsonFileEquals(array('require' => $packages));
56 | }
57 |
58 | public function testBuildProcessWithRepositories()
59 | {
60 | $packages = array(
61 | 'pimple/pimple' => '*',
62 | 'sensiolabs/melody' => '*',
63 | 'example/projectA' => '*',
64 | 'foobar/TopLevelPackage1' => '*',
65 | 'smarty/smarty' => '3.1.*',
66 | );
67 | $repositories = array(
68 | array(
69 | 'type' => 'vcs',
70 | 'url' => 'https://example.com/Pimple',
71 | ),
72 | array(
73 | 'type' => 'vcs',
74 | 'url' => 'https://example.com/melody',
75 | ),
76 | array(
77 | 'type' => 'vcs',
78 | 'url' => 'http://svn.example.org/projectA/',
79 | 'trunk-path' => 'Trunk',
80 | 'branches-path' => 'Branches',
81 | 'tags-path' => 'Tags',
82 | 'svn-cache-credentials' => false,
83 | ),
84 | array(
85 | 'type' => 'pear',
86 | 'url' => 'http://pear.foobar.repo',
87 | 'vendor-alias' => 'foobar',
88 | ),
89 | array(
90 | 'type' => 'package',
91 | 'package' => array(
92 | 'name' => 'smarty/smarty',
93 | 'version' => '3.1.7',
94 | 'dist' => array(
95 | 'url' => 'http://www.smarty.net/files/Smarty-3.1.7.zip',
96 | 'type' => 'zip',
97 | ),
98 | 'source' => array(
99 | 'url' => 'http://smarty-php.googlecode.com/svn/',
100 | 'type' => 'svn',
101 | 'reference' => 'tags/Smarty_3_1_7/distribution/',
102 | ),
103 | 'autoload' => array(
104 | 'classmap' => array('libs/'),
105 | ),
106 | ),
107 | ),
108 | );
109 |
110 | $this->composer->buildProcess($packages, $repositories, $this->workingDirPath, true);
111 | $this->assertComposerJsonFileEquals(
112 | array(
113 | 'require' => $packages,
114 | 'repositories' => $repositories,
115 | )
116 | );
117 | }
118 |
119 | private function assertComposerJsonFileEquals($expected)
120 | {
121 | $composerJsonContent = file_get_contents($this->workingDirPath.'/composer.json');
122 | $composerJsonContentDecode = json_decode($composerJsonContent, true);
123 | $this->assertEquals($expected, $composerJsonContentDecode);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Configuration/RunConfigurationParserTest.php:
--------------------------------------------------------------------------------
1 | parser = new RunConfigurationParser();
14 | }
15 |
16 | public function tearDown()
17 | {
18 | $this->parser = null;
19 | }
20 |
21 | /**
22 | * @dataProvider providePackagesError
23 | */
24 | public function testParsePackagesError($packages, $exception)
25 | {
26 | $this->setExpectedException('SensioLabs\Melody\Exception\ParseException', $exception);
27 |
28 | $this->parser->parseConfiguration($packages);
29 | }
30 |
31 | public function providePackagesError()
32 | {
33 | return array(
34 | array('foobar', 'The configuration should be an array.'),
35 | array(array('foobar' => 'bar'), 'The configuration should define a "packages" key.'),
36 | array(array('packages' => 'string'), 'The packages configuration should be an array.'),
37 | array(array('packages' => array('symfony/symfony' => array())), 'The package at key "symfony/symfony" should be a string, "array" given.'),
38 | array(array('packages' => array('symfony/symfony: 1 :1')), 'The package named "symfony/symfony" is not valid. It should contain only one ":".'),
39 | array(array('packages' => array('symfony/symfony/nope: 1')), 'The package named "symfony/symfony/nope" is not valid.'),
40 | array(array('packages' => array('symfony/symfony:')), 'The package version named "symfony/symfony" is not valid.'),
41 | );
42 | }
43 |
44 | public function testParsePackages()
45 | {
46 | $config = $this->parser->parseConfiguration(array('packages' => array(
47 | 'symfony/finder',
48 | 'symfony/console: 1.2',
49 | 'symfony/filesystem',
50 | 'symfony/filesystem: 1.3',
51 | 'php: 5.5.0',
52 | 'ext-pdo: *',
53 | )));
54 |
55 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\ScriptConfiguration', $config);
56 | $expected = array(
57 | 'symfony/finder' => '*',
58 | 'symfony/console' => '1.2',
59 | 'symfony/filesystem' => '1.3',
60 | 'php' => '5.5.0',
61 | 'ext-pdo' => '*',
62 | );
63 | $this->assertSame($expected, $config->getPackages());
64 | }
65 |
66 | /**
67 | * @dataProvider providePhpOptionsError
68 | */
69 | public function testParsePhpOptionsError($phpOptions, $exception)
70 | {
71 | $this->setExpectedException('SensioLabs\Melody\Exception\ParseException', $exception);
72 |
73 | $this->parser->parseConfiguration($phpOptions);
74 | }
75 |
76 | public function providePhpOptionsError()
77 | {
78 | return array(
79 | array(array('packages' => array('symfony/symfony: 1'), 'php-options' => 'string'), 'The php-options configuration should be an array.'),
80 | );
81 | }
82 |
83 | public function testParsePhpOptions()
84 | {
85 | $config = $this->parser->parseConfiguration(array(
86 | 'packages' => array(
87 | 'symfony/finder',
88 | ),
89 | 'php-options' => array(
90 | '-S',
91 | 'localhost:8000',
92 | ),
93 | ));
94 |
95 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\ScriptConfiguration', $config);
96 | $expected = array('-S', 'localhost:8000');
97 | $this->assertSame($expected, $config->getPhpOptions());
98 | }
99 |
100 | public function testParseRepositories()
101 | {
102 | $config = $this->parser->parseConfiguration(array(
103 | 'packages' => array(
104 | 'symfony/finder',
105 | 'symfony/console',
106 | ),
107 | 'repositories' => array(
108 | array(
109 | 'type' => 'vcs',
110 | 'url' => 'https://github.com/symfony/Finder.git',
111 | ),
112 | array(
113 | 'type' => 'vcs',
114 | 'url' => 'file:///home/symfony/Console',
115 | ),
116 | array(
117 | 'type' => 'vcs',
118 | 'url' => 'http://svn.example.org/projectA/',
119 | 'trunk-path' => 'Trunk',
120 | 'branches-path' => 'Branches',
121 | 'tags-path' => 'Tags',
122 | 'svn-cache-credentials' => false,
123 | ),
124 | array(
125 | 'type' => 'pear',
126 | 'url' => 'http://pear.foobar.repo',
127 | 'vendor-alias' => 'foobar',
128 | ),
129 | array(
130 | 'type' => 'package',
131 | 'package' => array(
132 | 'name' => 'smarty/smarty',
133 | 'version' => '3.1.7',
134 | 'dist' => array(
135 | 'url' => 'http://www.smarty.net/files/Smarty-3.1.7.zip',
136 | 'type' => 'zip',
137 | ),
138 | 'source' => array(
139 | 'url' => 'http://smarty-php.googlecode.com/svn/',
140 | 'type' => 'svn',
141 | 'reference' => 'tags/Smarty_3_1_7/distribution/',
142 | ),
143 | 'autoload' => array(
144 | 'classmap' => array('libs/'),
145 | ),
146 | ),
147 | ),
148 | ),
149 | ));
150 |
151 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\ScriptConfiguration', $config);
152 | $expected = array(
153 | array(
154 | 'type' => 'vcs',
155 | 'url' => 'https://github.com/symfony/Finder.git',
156 | ),
157 | array(
158 | 'type' => 'vcs',
159 | 'url' => 'file:///home/symfony/Console',
160 | ),
161 | array(
162 | 'type' => 'vcs',
163 | 'url' => 'http://svn.example.org/projectA/',
164 | 'trunk-path' => 'Trunk',
165 | 'branches-path' => 'Branches',
166 | 'tags-path' => 'Tags',
167 | 'svn-cache-credentials' => false,
168 | ),
169 | array(
170 | 'type' => 'pear',
171 | 'url' => 'http://pear.foobar.repo',
172 | 'vendor-alias' => 'foobar',
173 | ),
174 | array(
175 | 'type' => 'package',
176 | 'package' => array(
177 | 'name' => 'smarty/smarty',
178 | 'version' => '3.1.7',
179 | 'dist' => array(
180 | 'url' => 'http://www.smarty.net/files/Smarty-3.1.7.zip',
181 | 'type' => 'zip',
182 | ),
183 | 'source' => array(
184 | 'url' => 'http://smarty-php.googlecode.com/svn/',
185 | 'type' => 'svn',
186 | 'reference' => 'tags/Smarty_3_1_7/distribution/',
187 | ),
188 | 'autoload' => array(
189 | 'classmap' => array('libs/'),
190 | ),
191 | ),
192 | ),
193 | );
194 | $this->assertSame($expected, $config->getRepositories());
195 | }
196 |
197 | public function testParseRepositoriesError()
198 | {
199 | $this->setExpectedException(
200 | 'SensioLabs\Melody\Exception\ParseException',
201 | 'The repositories configuration should be an array'
202 | );
203 | $config = array(
204 | 'packages' => array(
205 | 'symfony/finder',
206 | ),
207 | 'repositories' => 'invalid',
208 | );
209 |
210 | $this->parser->parseConfiguration($config);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Configuration/UserConfigurationRepositoryTest.php:
--------------------------------------------------------------------------------
1 | load();
14 |
15 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\UserConfiguration', $config);
16 | $this->assertEquals(array('foo', 'bar'), $config->getTrustedSignatures());
17 | $this->assertEquals(array('baz', 'qux'), $config->getTrustedUsers());
18 | }
19 |
20 | public function testLoadWithWrongData()
21 | {
22 | $repository = new UserConfigurationRepository(__DIR__.'/../Fixtures/config-empty.yml');
23 | $config = $repository->load();
24 |
25 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\UserConfiguration', $config);
26 | $this->assertEmpty($config->getTrustedSignatures());
27 | $this->assertEmpty($config->getTrustedUsers());
28 | }
29 |
30 | public function testLoadWithoutPreviousData()
31 | {
32 | $repository = new UserConfigurationRepository(__DIR__.'/../Fixtures/does_not_exists.yml');
33 | $config = $repository->load();
34 |
35 | $this->assertInstanceOf('SensioLabs\Melody\Configuration\UserConfiguration', $config);
36 | $this->assertEmpty($config->getTrustedSignatures());
37 | $this->assertEmpty($config->getTrustedUsers());
38 | }
39 |
40 | /**
41 | * @expectedException \SensioLabs\Melody\Exception\ConfigException
42 | */
43 | public function testLoadWithoutWrongData()
44 | {
45 | $filename = sprintf('%s/%s', sys_get_temp_dir(), uniqid());
46 | if (file_exists($filename)) {
47 | unlink($filename);
48 | }
49 |
50 | file_put_contents($filename, 'foo: *bar');
51 |
52 | try {
53 | $repository = new UserConfigurationRepository($filename);
54 | $repository->load();
55 | unlink($filename);
56 | } catch (\Exception $e) {
57 | unlink($filename);
58 | throw $e;
59 | }
60 | }
61 |
62 | public function testSave()
63 | {
64 | $filename = sprintf('%s/%s', sys_get_temp_dir(), uniqid());
65 | if (file_exists($filename)) {
66 | unlink($filename);
67 | }
68 | $config = new UserConfiguration();
69 | $config->addTrustedSignatures(array('foo', 'bar'));
70 | $config->addTrustedUsers(array('baz', 'qux'));
71 |
72 | $repository = new UserConfigurationRepository($filename);
73 | $repository->save($config);
74 |
75 | $expected = 'trust:
76 | signatures:
77 | - foo
78 | - bar
79 | users:
80 | - baz
81 | - qux
82 | ';
83 |
84 | $this->assertFileExists($filename);
85 | $this->assertEquals($expected, file_get_contents($filename));
86 | unlink($filename);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/config-empty.yml:
--------------------------------------------------------------------------------
1 | foo:
2 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/config.yml:
--------------------------------------------------------------------------------
1 | trust:
2 | signatures:
3 | - foo
4 | - bar
5 | users:
6 | - baz
7 | - qux
8 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/fork-repositories.php:
--------------------------------------------------------------------------------
1 | 'value'));
12 | echo $container['key'];
13 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/hello-world-with-constraints.php:
--------------------------------------------------------------------------------
1 | =5.3.0"
7 | - "ext-pdo: *"
8 | CONFIG;
9 |
10 | $twig = new Twig_Environment(new Twig_Loader_Array(array(
11 | 'foo' => 'Hello {{ include("bar") }}',
12 | 'bar' => 'world',
13 | )));
14 |
15 | echo $twig->render('foo');
16 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/hello-world.phar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sensiolabs/melody/0bf7fc3347ba68a55b2a2e1b27368e2b9d3dbe41/src/SensioLabs/Melody/Tests/Fixtures/hello-world.phar
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/hello-world.php:
--------------------------------------------------------------------------------
1 | 'Hello {{ include("bar") }}',
10 | 'bar' => 'world',
11 | )));
12 |
13 | echo $twig->render('foo');
14 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/hello-world.php.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sensiolabs/melody/0bf7fc3347ba68a55b2a2e1b27368e2b9d3dbe41/src/SensioLabs/Melody/Tests/Fixtures/hello-world.php.gz
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/php-options.php:
--------------------------------------------------------------------------------
1 | 'value'));
9 | echo $container['key'];
10 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Fixtures/shebang.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S melody run
2 | 'Hello {{ include("bar") }}',
10 | 'bar' => 'world',
11 | )));
12 |
13 | echo $twig->render('foo');
14 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Handler/FileHandlerTest.php:
--------------------------------------------------------------------------------
1 | handler = new FileHandler();
14 | }
15 |
16 | protected function tearDown()
17 | {
18 | $this->handler = null;
19 | }
20 |
21 | public function provideSupports()
22 | {
23 | return array(
24 | array(__DIR__.'/../Fixtures/hello-world.php', true),
25 | array(__DIR__.'/../Fixtures/Path/To/InvalidFile.php', false),
26 | array(__DIR__.'/../Fixtures', false),
27 | array('https://gist.github.com/csarrazi/7494d27255d0561157b8', false),
28 | );
29 | }
30 |
31 | /**
32 | * @dataProvider provideSupports
33 | */
34 | public function testSupports($path, $expected)
35 | {
36 | $this->assertEquals($expected, $this->handler->supports($path));
37 | }
38 |
39 | public function testCreateResource()
40 | {
41 | $filename = __DIR__.'/../Fixtures/hello-world.php';
42 |
43 | $resource = $this->handler->createResource($filename);
44 |
45 | $this->assertInstanceOf('SensioLabs\Melody\Resource\Resource', $resource);
46 | $this->assertSame(file_get_contents($filename), $resource->getContent());
47 | }
48 |
49 | public function testCreatedResourceDontContainsShebang()
50 | {
51 | $filename = __DIR__.'/../Fixtures/shebang.php';
52 |
53 | $resource = $this->handler->createResource($filename);
54 |
55 | $this->assertInstanceOf('SensioLabs\Melody\Resource\Resource', $resource);
56 | $this->assertNotRegExp('/^#![^\n]+\n/u', $resource->getContent());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Handler/GistHandlerTest.php:
--------------------------------------------------------------------------------
1 | handler = new GistHandler();
17 | }
18 |
19 | protected function tearDown()
20 | {
21 | $this->handler = null;
22 | }
23 |
24 | public function provideSupports()
25 | {
26 | return array(
27 | array(__DIR__.'/../fixtures/foo.php', false),
28 | array(__DIR__.'/../fixtures/foobar.php', false),
29 | array(__DIR__.'/../fixtures', false),
30 | array('https://gist.github.com/foobar/7494d27255d0561157b8', true),
31 | array('https://gist.github.com/foobar/7494d27255d0561157b8', true),
32 | array('https://gist.github.com/foobar-/7494d27255d0561157b8', true),
33 | array('https://gist.github.com/foo-bar/7494d27255d0561157b8', true),
34 | array('https://gist.github.com/-foobar/7494d27255d0561157b8', false),
35 | array('https://gist.github.com/thisusernameistoolongandshouldnotbevalid/7494d27255d0561157b8', false),
36 | array('https://gist.github.com/thisusernameisnttoolongandshouldbevalid/7494d27255d0561157b8', true),
37 | array('foobar/7494d27255d0561157b8', true),
38 | array('foobar-/7494d27255d0561157b8', true),
39 | array('foo-bar/7494d27255d0561157b8', true),
40 | array('-foobar/7494d27255d0561157b8', false),
41 | array('thisusernameistoolongandshouldnotbevalid/7494d27255d0561157b8', false),
42 | array('thisusernameisnttoolongandshouldbevalid/7494d27255d0561157b8', true),
43 | );
44 | }
45 |
46 | /**
47 | * @dataProvider provideSupports
48 | */
49 | public function testSupports($url, $expected)
50 | {
51 | $this->assertEquals($expected, $this->handler->supports($url));
52 | }
53 |
54 | public function testCreateResource()
55 | {
56 | $resource = $this->handler->createResource(self::SINGLE_URL);
57 |
58 | $this->assertInstanceOf('SensioLabs\Melody\Resource\Resource', $resource);
59 | $this->assertContains('symfony/console', $resource->getContent());
60 | }
61 |
62 | /**
63 | * @expectedException \InvalidArgumentException
64 | */
65 | public function testCreateResourceWithMultipleFileInGist()
66 | {
67 | $this->handler->createResource(self::MULTI_URL);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/Handler/Github/GistTest.php:
--------------------------------------------------------------------------------
1 | handler = new StreamHandler();
14 | }
15 |
16 | protected function tearDown()
17 | {
18 | $this->handler = null;
19 | }
20 |
21 | public function provideSupports()
22 | {
23 | return array(
24 | array(__DIR__.'/../Fixtures/hello-world.php', true),
25 | array(__DIR__.'/../Fixtures/Path/To/InvalidFile.php', false),
26 | array('php://stdin', true),
27 | array('https://gist.githubusercontent.com/lyrixx/565752f13499a3fa17d9/raw/f561159b09a2014dd0d75727582c2cf8ee36e30e/melody.php', true),
28 | );
29 | }
30 |
31 | /**
32 | * @dataProvider provideSupports
33 | */
34 | public function testSupports($path, $expected)
35 | {
36 | $this->assertEquals($expected, $this->handler->supports($path));
37 | }
38 |
39 | public function testCreateResource()
40 | {
41 | $filename = __DIR__.'/../Fixtures/hello-world.php';
42 |
43 | $resource = $this->handler->createResource($filename);
44 |
45 | $this->assertInstanceOf('SensioLabs\Melody\Resource\Resource', $resource);
46 | $this->assertSame(file_get_contents($filename), $resource->getContent());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/IntegrationTest.php:
--------------------------------------------------------------------------------
1 | fs = new Filesystem();
18 | $this->cleanCache();
19 | }
20 |
21 | protected function tearDown()
22 | {
23 | $this->cleanCache();
24 | $this->fs = null;
25 | }
26 |
27 | public function testRunWithDefaultOption()
28 | {
29 | $output = $this->melodyRunFixture('hello-world.php');
30 | $this->assertContains('Loading composer repositories with package information', $output);
31 | $this->assertContains('Updating dependencies (including require-dev)', $output);
32 | $this->assertContains('Installing twig/twig (v1.16.0)', $output);
33 | $this->assertContains('Hello world', $output);
34 | }
35 |
36 | public function testRunWithShebang()
37 | {
38 | $output = $this->melodyRunFixture('shebang.php');
39 | $this->assertContains('Loading composer repositories with package information', $output);
40 | $this->assertContains('Updating dependencies (including require-dev)', $output);
41 | $this->assertContains('Installing twig/twig (v1.16.0)', $output);
42 | $this->assertNotContains('#!/usr/bin/env -S melody run', $output);
43 | $this->assertContains('Hello world', $output);
44 | }
45 |
46 | public function testRunWithCache()
47 | {
48 | $this->melodyRunFixture('hello-world.php');
49 | $output = $this->melodyRunFixture('hello-world.php');
50 | $this->assertSame('Hello world', $output);
51 | }
52 |
53 | public function testRunWithConstraints()
54 | {
55 | $output = $this->melodyRunFixture('hello-world-with-constraints.php');
56 | $this->assertContains('Hello world', $output);
57 | }
58 |
59 | public function testRunWithNoCache()
60 | {
61 | $this->melodyRunFixture('hello-world.php');
62 | $output = $this->melodyRunFixture('hello-world.php', array('no_cache' => true));
63 | $this->assertContains('Loading composer repositories with package information', $output);
64 | $this->assertContains('Updating dependencies (including require-dev)', $output);
65 | $this->assertContains('Installing twig/twig (v1.16.0)', $output);
66 | $this->assertContains('Hello world', $output);
67 | }
68 |
69 | public function testRunWithPreferSource()
70 | {
71 | $output = $this->melodyRunFixture('pimple.php', array('prefer_source' => true));
72 | $this->assertContains('Loading composer repositories with package information', $output);
73 | $this->assertContains('Updating dependencies (including require-dev)', $output);
74 | $this->assertContains('Installing pimple/pimple (v1.0.2)', $output);
75 | $this->assertContains('Cloning', $output);
76 | $this->assertContains('value', $output);
77 | }
78 |
79 | public function provideStreams()
80 | {
81 | return array(
82 | array('data', 'text/plain;base64,PD9waHANCjw8PENPTkZJRw0KcGFja2FnZXM6DQogICAgLSAidHdpZy90d2lnOjEuMTYuMCINCkNPTkZJRzsNCg0KJHR3aWcgPSBuZXcgVHdpZ19FbnZpcm9ubWVudChuZXcgVHdpZ19Mb2FkZXJfQXJyYXkoYXJyYXkoDQogICAgJ2ZvbycgPT4gJ0hlbGxvIHt7IGluY2x1ZGUoImJhciIpIH19JywNCiAgICAnYmFyJyA9PiAnd29ybGQnDQopKSk7DQoNCmVjaG8gJHR3aWctPnJlbmRlcignZm9vJyk7DQo='),
83 | array('phar', $this->getFixtureFile('hello-world.phar/hello-world.php')),
84 | array('compress.zlib', $this->getFixtureFile('hello-world.php.gz')),
85 | );
86 | }
87 |
88 | /**
89 | * @dataProvider provideStreams
90 | */
91 | public function testRunStream($protocol, $fixture)
92 | {
93 | $output = $this->melodyRunStream($protocol, $fixture, array('trust' => true));
94 | $this->assertContains('Hello world', $output);
95 | }
96 |
97 | public function testRunWithPhpOptions()
98 | {
99 | $output = $this->melodyRunFixture('php-options.php');
100 | $this->assertContains('memory_limit=42M', $output);
101 | }
102 |
103 | /**
104 | * @dataProvider provideGists
105 | */
106 | public function testRunGist($gist)
107 | {
108 | $output = $this->melodyRun($gist, array('trust' => true));
109 | $this->assertContains('Hello greg', $output);
110 | }
111 |
112 | public function provideGists()
113 | {
114 | return array(
115 | array('23bb3980daf65154c3d4'),
116 | array('lyricc/23bb3980daf65154c3d4'),
117 | array('https://gist.github.com/lyrixx/23bb3980daf65154c3d4'),
118 | );
119 | }
120 |
121 | /**
122 | * @expectedException \SensioLabs\Melody\Exception\TrustException
123 | */
124 | public function testRunGistUntrusted()
125 | {
126 | $this->melodyRun('23bb3980daf65154c3d4', array('trust' => false));
127 | }
128 |
129 | public function testRunWithForkRepositories()
130 | {
131 | $output = $this->melodyRunFixture('fork-repositories.php', array('prefer_source' => true));
132 |
133 | $this->assertContains('Loading composer repositories with package information', $output);
134 | $this->assertContains('Updating dependencies (including require-dev)', $output);
135 | $this->assertContains('Installing pimple/pimple (v1.0.2)', $output);
136 | $this->assertContains('Cloning', $output);
137 | $this->assertContains('value', $output);
138 | }
139 |
140 | private function melodyRunFixture($fixture, array $options = array())
141 | {
142 | return $this->melodyRun(sprintf('%s/Fixtures/%s', __DIR__, $fixture), $options);
143 | }
144 |
145 | private function melodyRunStream($protocol, $fixture, array $options = array())
146 | {
147 | $melody = new Melody();
148 |
149 | $filename = sprintf('%s://%s', $protocol, $fixture);
150 |
151 | $options = array_replace(array(
152 | 'trust' => false,
153 | 'prefer_source' => false,
154 | 'no_cache' => false,
155 | ), $options);
156 |
157 | $runConfiguration = new RunConfiguration($options['no_cache'], $options['prefer_source'], $options['trust']);
158 | $userConfiguration = new UserConfiguration();
159 |
160 | $output = null;
161 | $cliExecutor = function (Process $process, $useProcessHelper) use (&$output) {
162 | $process->setTty(false);
163 | $process->mustRun(function ($type, $text) use (&$output) {
164 | $output .= $text;
165 | });
166 | };
167 |
168 | $melody->run($filename, array(), $runConfiguration, $userConfiguration, $cliExecutor);
169 |
170 | return $output;
171 | }
172 |
173 | private function melodyRun($filename, array $options = array())
174 | {
175 | $melody = new Melody();
176 |
177 | $options = array_replace(array(
178 | 'trust' => false,
179 | 'prefer_source' => false,
180 | 'no_cache' => false,
181 | ), $options);
182 |
183 | $runConfiguration = new RunConfiguration($options['no_cache'], $options['prefer_source'], $options['trust']);
184 | $userConfiguration = new UserConfiguration();
185 |
186 | $output = null;
187 | $cliExecutor = function (Process $process, $useProcessHelper) use (&$output) {
188 | $process->setTty(false);
189 | $process->mustRun(function ($type, $text) use (&$output) {
190 | $output .= $text;
191 | });
192 | };
193 |
194 | $melody->run($filename, array(), $runConfiguration, $userConfiguration, $cliExecutor);
195 |
196 | return $output;
197 | }
198 |
199 | private function cleanCache()
200 | {
201 | $this->fs->remove(sys_get_temp_dir().'/melody');
202 | }
203 |
204 | private function getFixtureFile($fixtureName)
205 | {
206 | return sprintf('%s/Fixtures/%s', __DIR__, $fixtureName);
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/Tests/WorkingDirectory/WorkingDirectoryFactoryTest.php:
--------------------------------------------------------------------------------
1 | storageDir = vfsStream::setup('storageDir');
18 | $this->workingDirectoryFactory = new WorkingDirectoryFactory($this->storageDir->url());
19 | }
20 |
21 | public function testCreateTmpDir()
22 | {
23 | $workingDirectory = $this->workingDirectoryFactory->createTmpDir(array(), array());
24 |
25 | $workingDirectoryPath = $workingDirectory->getPath();
26 | $this->assertNotEquals($this->storageDir->url(), $workingDirectoryPath);
27 | $this->assertEquals($this->storageDir->url(), dirname($workingDirectoryPath));
28 | $this->assertNotEmpty(basename($workingDirectoryPath));
29 | }
30 |
31 | public static function sameConfigProvider()
32 | {
33 | return array(
34 | 'no_config' => array(
35 | array(), array(),
36 | array(), array(),
37 | ),
38 | 'one_package' => array(
39 | array('symfony/symfony' => '*'), array(),
40 | array('symfony/symfony' => '*'), array(),
41 | ),
42 | 'packages_order' => array(
43 | array(
44 | 'symfony/symfony' => '*',
45 | 'sensiolabs/melody' => '*',
46 | ),
47 | array(),
48 | array(
49 | 'sensiolabs/melody' => '*',
50 | 'symfony/symfony' => '*',
51 | ),
52 | array(),
53 | ),
54 | 'repositories_order' => array(
55 | array(
56 | 'symfony/symfony' => '*',
57 | 'sensiolabs/melody' => '*',
58 | ),
59 | array(
60 | array(
61 | 'type' => 'vcs',
62 | 'url' => 'https://example.com/symfony',
63 | ),
64 | array(
65 | 'type' => 'vcs',
66 | 'url' => 'https://example.com/melody',
67 | ),
68 | ),
69 | array(
70 | 'symfony/symfony' => '*',
71 | 'sensiolabs/melody' => '*',
72 | ),
73 | array(
74 | array(
75 | 'url' => 'https://example.com/melody',
76 | 'type' => 'vcs',
77 | ),
78 | array(
79 | 'url' => 'https://example.com/symfony',
80 | 'type' => 'vcs',
81 | ),
82 | ),
83 | ),
84 | 'packages_repositories_order' => array(
85 | array(
86 | 'symfony/symfony' => '*',
87 | 'sensiolabs/melody' => '*',
88 | 'pear-pear2.php.net/PEAR2_Text_Markdown' => '*',
89 | 'smarty/smarty' => '*',
90 | ),
91 | array(
92 | array(
93 | 'type' => 'vcs',
94 | 'url' => 'https://example.com/symfony',
95 | ),
96 | array(
97 | 'type' => 'vcs',
98 | 'url' => 'https://example.com/melody',
99 | ),
100 | array(
101 | 'type' => 'package',
102 | 'package' => array(
103 | 'name' => 'smarty/smarty',
104 | 'version' => '3.1.7',
105 | 'dist' => array(
106 | 'url' => 'https://www.smarty.net/files/Smarty-3.1.7.zip',
107 | 'type' => 'zip',
108 | ),
109 | 'source' => array(
110 | 'url' => 'https://smarty-php.googlecode.com/svn/',
111 | 'type' => 'svn',
112 | 'reference' => 'tags/Smarty_3_1_7/distribution/',
113 | ),
114 | ),
115 | ),
116 | array(
117 | 'type' => 'pear',
118 | 'url' => 'http://pear2.php.net',
119 | ),
120 | ),
121 | array(
122 | 'symfony/symfony' => '*',
123 | 'sensiolabs/melody' => '*',
124 | 'smarty/smarty' => '*',
125 | 'pear-pear2.php.net/PEAR2_Text_Markdown' => '*',
126 | ),
127 | array(
128 | array(
129 | 'type' => 'package',
130 | 'package' => array(
131 | 'name' => 'smarty/smarty',
132 | 'version' => '3.1.7',
133 | 'source' => array(
134 | 'url' => 'https://smarty-php.googlecode.com/svn/',
135 | 'type' => 'svn',
136 | 'reference' => 'tags/Smarty_3_1_7/distribution/',
137 | ),
138 | 'dist' => array(
139 | 'url' => 'https://www.smarty.net/files/Smarty-3.1.7.zip',
140 | 'type' => 'zip',
141 | ),
142 | ),
143 | ),
144 | array(
145 | 'url' => 'https://example.com/melody',
146 | 'type' => 'vcs',
147 | ),
148 | array(
149 | 'type' => 'pear',
150 | 'url' => 'http://pear2.php.net',
151 | ),
152 | array(
153 | 'url' => 'https://example.com/symfony',
154 | 'type' => 'vcs',
155 | ),
156 | ),
157 | ),
158 | );
159 | }
160 |
161 | /**
162 | * @dataProvider sameConfigProvider
163 | */
164 | public function testCreateTmpDirWithSameConfigShouldNotChange($packages, $repositories, $otherPackages, $otherRepositories)
165 | {
166 | $this->assertEquals(
167 | $this->workingDirectoryFactory->createTmpDir($packages, $repositories)->getPath(),
168 | $this->workingDirectoryFactory->createTmpDir($otherPackages, $otherRepositories)->getPath()
169 | );
170 | }
171 |
172 | public static function differentConfigProvider()
173 | {
174 | return array(
175 | array(
176 | array(), array(),
177 | array('symfony/symfony' => '*'), array(),
178 | ),
179 | array(
180 | array('sensiolabs/melody' => '*'),
181 | array(),
182 | array('sensiolabs/melody' => '*'),
183 | array(
184 | array(
185 | 'type' => 'vcs',
186 | 'url' => 'https://example.com/melody',
187 | ),
188 | ),
189 | ),
190 | array(
191 | array('sensiolabs/melody' => '*'),
192 | array(
193 | array(
194 | 'type' => 'vcs',
195 | 'url' => 'https://example.com/melody',
196 | ),
197 | ),
198 | array('sensiolabs/melody' => '*'),
199 | array(
200 | array(
201 | 'type' => 'vcs',
202 | 'url' => 'https://otherexample.com/melody',
203 | ),
204 | ),
205 | ),
206 | );
207 | }
208 |
209 | /**
210 | * @dataProvider differentConfigProvider
211 | */
212 | public function testCreateTmpDirWithDifferentConfigShouldChange($packages, $repositories, $otherPackages, $otherRepositories)
213 | {
214 | $this->assertNotEquals(
215 | $this->workingDirectoryFactory->createTmpDir($packages, $repositories)->getPath(),
216 | $this->workingDirectoryFactory->createTmpDir($otherPackages, $otherRepositories)->getPath()
217 | );
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/WorkingDirectory/GarbageCollector.php:
--------------------------------------------------------------------------------
1 |
14 | * @author Grégoire Pineau
15 | */
16 | class GarbageCollector
17 | {
18 | // One day
19 | const TTL = 86400;
20 |
21 | private $storePath;
22 | private $filesystem;
23 |
24 | public function __construct($storePath)
25 | {
26 | $this->storePath = $storePath;
27 | $this->filesystem = new Filesystem();
28 | }
29 |
30 | /**
31 | * Remove old workingDirectories.
32 | */
33 | public function run()
34 | {
35 | $maxATime = time() - self::TTL;
36 |
37 | $files = Finder::create()
38 | ->in($this->storePath)
39 | ->depth(1)
40 | ->name(Runner::BOOTSTRAP_FILENAME)
41 | ->filter(function (SplFileInfo $d) use ($maxATime) {
42 | return $d->getATime() < $maxATime;
43 | })
44 | ;
45 |
46 | $directories = array();
47 |
48 | foreach ($files as $file) {
49 | $directories[] = dirname($file);
50 | }
51 |
52 | $this->filesystem->remove($directories);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/WorkingDirectory/WorkingDirectory.php:
--------------------------------------------------------------------------------
1 |
11 | * @author Grégoire Pineau
12 | */
13 | class WorkingDirectory
14 | {
15 | private $path;
16 | private $filesystem;
17 |
18 | public function __construct($path, Filesystem $filesystem)
19 | {
20 | $this->path = $path;
21 | $this->filesystem = $filesystem;
22 | }
23 |
24 | public function isNew()
25 | {
26 | return !file_exists($this->getLockFile());
27 | }
28 |
29 | public function create()
30 | {
31 | $this->filesystem->mkdir($this->path);
32 | }
33 |
34 | public function clear()
35 | {
36 | $this->filesystem->remove($this->path);
37 | $this->create();
38 | }
39 |
40 | public function lock()
41 | {
42 | $this->filesystem->touch($this->getLockFile());
43 | }
44 |
45 | public function getPath()
46 | {
47 | return $this->path;
48 | }
49 |
50 | private function getLockFile()
51 | {
52 | return sprintf('%s/%s', $this->path, '.lock');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/SensioLabs/Melody/WorkingDirectory/WorkingDirectoryFactory.php:
--------------------------------------------------------------------------------
1 |
11 | * @author Grégoire Pineau
12 | */
13 | class WorkingDirectoryFactory
14 | {
15 | private $storagePath;
16 | private $filesystem;
17 |
18 | public function __construct($storagePath)
19 | {
20 | $this->storagePath = $storagePath;
21 | $this->filesystem = new Filesystem();
22 |
23 | $this->filesystem->mkdir($this->storagePath);
24 | }
25 |
26 | public function createTmpDir(array $packages, array $repositories)
27 | {
28 | $hash = $this->generateHash($packages, $repositories);
29 |
30 | $path = sprintf('%s/%s', $this->storagePath, $hash);
31 |
32 | return new WorkingDirectory($path, $this->filesystem);
33 | }
34 |
35 | private function generateHash(array $packages, array $repositories)
36 | {
37 | ksort($packages);
38 |
39 | if (empty($repositories)) {
40 | $config = $packages;
41 | } else {
42 | $this->sortRepositories($repositories);
43 | $config = array($repositories, $packages);
44 | }
45 |
46 | // Some application use `basename(__DIR__)` and may generate class
47 | // name with this dirname. And a sha256 hash may start with a number.
48 | // This will lead to a fatal error because PHP forbid that.
49 | return 'a'.hash('sha256', serialize($config));
50 | }
51 |
52 | private function sortRepositories(array &$repositories)
53 | {
54 | $this->ksortRecursive($repositories);
55 | array_multisort($this->getRepositoriesSortOrder($repositories), $repositories);
56 | }
57 |
58 | private function ksortRecursive(&$array)
59 | {
60 | ksort($array);
61 | foreach ($array as $k => $v) {
62 | if (is_array($v)) {
63 | $this->ksortRecursive($array[$k]);
64 | }
65 | }
66 | }
67 |
68 | private function getRepositoriesSortOrder(array $repositories)
69 | {
70 | $urlsByRepositories = $this->extractRepositoriesUrls($repositories);
71 | $stringifiedUrlsByRepositories = array_map(function ($urls) {
72 | return implode(' ', $urls);
73 | }, $urlsByRepositories);
74 |
75 | return $stringifiedUrlsByRepositories;
76 | }
77 |
78 | private function extractRepositoriesUrls(array $repositories)
79 | {
80 | $urls = array();
81 | $recursiveIteratorRepositories = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($repositories));
82 |
83 | foreach ($recursiveIteratorRepositories as $key => $leaf) {
84 | if ('url' === $key) {
85 | $repositoryPosition = $recursiveIteratorRepositories->getSubIterator(0)->key();
86 | $urls[$repositoryPosition][] = $leaf;
87 | }
88 | }
89 |
90 | return $urls;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------