[
  {
    "path": ".gitignore",
    "content": "# IDE\n/.build_dir\n/.externalTollBuilders\n/.settings\n/.project\n/.buildpath\n\n# Dependencies\n/vendor\n\n# Bin\n/pharcc.phar\n/jolici.phar"
  },
  {
    "path": ".gush.yml",
    "content": "adapter: github\nissue_tracker: github\n"
  },
  {
    "path": ".pharcc.yml",
    "content": "name: jolici.phar\nmain: bin/jolici\ninclude:\n    - src/\n    - vendor/\n    - resources/\nexclude:\n    - \"/[Tt]ests?/\"\n    - \"/docs?/\"\n    - \"^vendor/phpunit\"\n    - \"^vendor/bin\""
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n    - 5.5\n    - 5.6\n    - hhvm\n    - 7.0\n\nmatrix:\n    allow_failures:\n        - php: hhvm\n\nbefore_script:\n    - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source install\n\nscript: ./vendor/bin/phpunit\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## Change Log\n\n### v0.3.1 (2015/04/02)\n\n- Add [images-update command](docs/command/updates-image.md)\n- Add php7 support\n- Fix quoted environnement variable #52\n- Assumed default versions for each language #51\n- Add desktop notifications\n- Fix output of versbose mode with new lines\n\n### v0.3.0 (2015/02/25)\n\n- Better stability with updated docker-php library and error management\n- Services ! (check the dock)\n- Cleaning old images and tests\n\n### v0.2.4 (2014/08/01)\n- [1690410](https://github.com/jolicode/JoliCi/commit/16904105468a718a705376baf9d9ac66bd280c64) Add php 5.6 support (@joelwurtz)\n- [99a9dea](https://github.com/jolicode/JoliCi/commit/99a9dea3d196845545c21e1da0449865a900d4f1) Remove hack for phpenv, as new images use phpenv (@joelwurtz)\n- [093a309](https://github.com/jolicode/JoliCi/commit/093a309cb1d804748cde099df618c21967809c48) Add jolici binary to composer. (@nubs)\n- [d494e90](https://github.com/jolicode/JoliCi/commit/d494e90324f0d87cd6a705b41f825474763a094a) Remove non-available builds from filesystem. (@nubs)\n- [0a715f8](https://github.com/jolicode/JoliCi/commit/0a715f86cb52fe815f379ff28c4bc03a9b746cf9) Add support for gush (@cordoval)\n- [9714ab0](https://github.com/jolicode/JoliCi/commit/9714ab02dc5da5b1af78b4bb7ec4881de7c4620a) Use stable version for dependencies (@nubs)\n\n### v0.2.2 (2014/06/26)\n- [239a2de](https://github.com/jolicode/JoliCi/commit/239a2de6cf5d0db31404dbb39ab6f4b9210aa921) Update dependencies versions (@joelwurtz)\n\n### v0.2.1 (2014/05/15)\n- [ad1ba9a](https://github.com/jolicode/JoliCi/commit/ad1ba9a3eca7fb4a0e4f3f3e5aba59904d525127) Add ruby versions (@joelwurtz)\n- [d697ee8](https://github.com/jolicode/JoliCi/commit/d697ee8eedcd0b33b42c2b5e89c918fb222eefbc) Encapsulate command in bash logged with profile support (@joelwurtz)\n- [c9b86ce](https://github.com/jolicode/JoliCi/commit/c9b86ce4c63ceb430a10930e5a7345e70706a321) Add node js support (@joelwurtz)\n- [9b1c684](https://github.com/jolicode/JoliCi/commit/9b1c684ebe11991253d76825847d79c91ad92ac8) Add support for env variables (@joelwurtz)\n- [d1cf3b7](https://github.com/jolicode/JoliCi/commit/d1cf3b7966b385759acc351c299b067b653ac06d) Add timeout support, and set to 5 min by default (@joelwurtz)\n- [9a551c8](https://github.com/jolicode/JoliCi/commit/9a551c8938f61ef74beaff3737b8dfa7c2091b9e) Add static progress bar when getting image from docker (@joelwurtz)\n- [f6662c2](https://github.com/jolicode/JoliCi/commit/f6662c25c8ad55ead5b654a75a29668ffa35f236) Make php dockerfile work when using phpenv specific action (like symfony) (@joelwurtz)\n\n### v0.2.0 (2014/02/21)\n- [7d85d78](https://github.com/jolicode/JoliCi/commit/7d85d78774eaf6a5d41c1cd98a4be4ce7497c054) Adding ruby to travis strategy (@joelwurtz)\n- [71364c3](https://github.com/jolicode/JoliCi/commit/71364c3814e071cf63983f98933e11ec75fb5ea9) Add a default timeout to 10min for long running build (@joelwurtz)\n- [7100908](https://github.com/jolicode/JoliCi/commit/7100908c6f78966bd750310b2abfa0af5173f1fb) Refactoring creation of Dockerfile with twig generation (@joelwurtz)\n\n### v0.1.1 (2014/01/18)\n- [69276ca](https://github.com/jolicode/JoliCi/commit/69276ca8b982b1343519f4119d188a299258c40b) Add travis ci support (@joelwurtz)\n- [8d469ec](https://github.com/jolicode/JoliCi/commit/8d469ecad0b696f72032e21f0d654ee8ad304c47) Add error management (@joelwurtz)\n- [945a31a](https://github.com/jolicode/JoliCi/commit/945a31a1750c366eae15f1baeebf1aaff73001ff) Allow to override command when running test (@joelwurtz)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nFirst, thank you!\n\nHere are a few rules to follow in order to ease code reviews, and discussions before maintainers accept and merge your work.\n\n* You **MUST** follow the [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/).\n* You **MUST** run the test suite.\n* You **MUST** write (or update) unit tests.\n* You **SHOULD** write documentation.\n\nPlease, write [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) before submitting your Pull Request.\n\nOne may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to \"clean\" your Pull Request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.).\n\nAlso, when creating your Pull Request on GitHub, you **MUST** write a description which gives the context and/or explains why you are creating it.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2014 Joel Wurtz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy \nof this software and associated documentation files (the \"Software\"), to deal \nin the Software without restriction, including without limitation the rights \nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished \nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all \ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN \nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# JoliCi\n\nJoliCi is a free and open source Continuous Integration _Client_ written in PHP (5.4 minimum) and powered by Docker (please use a recent version). It has been written to be compliant \nwith existent Ci services like Travis-Ci and not create a new standard. ([Remove that smile, I KNOW what you're thinking.](http://xkcd.com/927/))\n\n**This project is still in beta, there may be bugs and missing features**\n\n[![Build Status](https://travis-ci.org/jolicode/JoliCi.png?branch=master)](https://travis-ci.org/jolicode/JoliCi) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/jolicode/JoliCi/badges/quality-score.png?s=1ba180546468c07ca8fc0996dcdc4a740dcf23fc)](https://scrutinizer-ci.com/g/jolicode/JoliCi/)\n\n## Usage\n\n[Have a .travis.yml in your project](http://docs.travis-ci.com/user/getting-started/#Step-three%3A-Add-.travis.yml-file-to-your-repository)\n\n[Download the last version of jolici](https://github.com/jolicode/JoliCi/releases) and run it, i.e. for v0.3.1:\n\n```bash\nwget https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar\nphp jolici.phar run\n```\n\nFirst run can be quite long since it has to build everything from the beginning. Subsequent build should be faster thanks to docker caching.\n\n![JoliCi Demo](https://github.com/jolicode/JoliCi/raw/master/docs/jolici-terminal.gif \"JoliCi Demo\")\n\nIf you want to see what happens behind this black box:\n\n```bash\nphp jolici.phar run -v\n```\n\n## Ci supported\n\n* Travis-Ci\n* [...](CONTRIBUTING.md)\n\n## I want to read more\n\n* [Installation](docs/installation.md)\n* Usage\n    * [The run command](docs/command/run.md)\n    * [The clean command](docs/command/clean.md)\n    * [The images-update command](docs/command/updates-image.md)\n* Strategies (a.k.a. how to create a build from a configuration file)\n    * [Travis-Ci](docs/strategies/TravisCiStrategy.md)\n    * [JoliCi](docs/strategies/JoliCiStrategy.md)\n\n## Credits\n\n* [All contributors](https://github.com/jolicode/JoliCi/graphs/contributors)\n* Some parts of this project are inspired by :\n\t* [Docker Client](https://github.com/dotcloud/docker/blob/master/commands.go)\n* This README is heavily inspired by a @willdurand [blog post](http://williamdurand.fr/2013/07/04/on-open-sourcing-libraries/).\n* [@ubermuda](https://github.com/ubermuda) for accepting many pull requests on [docker-php](https://github.com/stage1/docker-php) library\n\n## License\n\nView the [LICENSE](LICENSE) file attach to this project.\n"
  },
  {
    "path": "bin/jolici",
    "content": "#!/usr/bin/env php\n<?php\n\nuse Joli\\JoliCi\\Command\\RunCommand;\nuse Joli\\JoliCi\\Command\\CleanCommand;\nuse Symfony\\Component\\Console\\Application;\nuse cbednarski\\Pharcc\\Git;\n\nif (file_exists(__DIR__ . '/../vendor/autoload.php')) {\n    require_once(__DIR__ . '/../vendor/autoload.php');\n} elseif (file_exists(__DIR__ . '/../../../../vendor/autoload.php')) {\n    require_once(__DIR__ . '/../../../../vendor/autoload.php');\n} else {\n    throw new \\Exception('Unable to load autoloader');\n}\n\n$version = \"dev\";\n\nif (class_exists(\"\\cbednarski\\Pharcc\\Git\")) {\n    $version = Git::getVersion(__DIR__);\n}\n\n$application = new Application('jolici', $version);\n\n$application->add(new RunCommand());\n$application->add(new CleanCommand());\n$application->add(new \\Joli\\JoliCi\\Command\\UpdateImageCommand());\n\n$application->run();\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"jolicode/jolici\",\n    \"description\": \"Run your TravisCi builds locally\",\n    \"type\": \"library\",\n    \"license\": \"MIT\",\n    \"keywords\": [\"test\", \"ci\", \"travisci\", \"docker\", \"continuous\", \"integration\"],\n    \"authors\": [\n        {\n            \"name\": \"Joel Wurtz\",\n            \"email\": \"jwurtz@jolicode.com\",\n            \"homepage\": \"https://twitter.com/JoelWurtz\"\n        }\n    ],\n    \"require\" : {\n        \"php\" : \">=5.3\",\n        \"symfony/console\" : \"^2.4\",\n        \"symfony/filesystem\" : \"^2.4\",\n        \"symfony/finder\" : \"^2.4\",\n        \"symfony/yaml\" : \"^2.4\",\n        \"monolog/monolog\": \"^1.6\",\n        \"docker-php/docker-php\": \"^1.22\",\n        \"cedriclombardot/twig-generator\": \"^1.0\",\n        \"guzzlehttp/streams\": \"^1.3\",\n        \"behat/transliterator\": \"^1.0\",\n        \"jolicode/jolinotif\": \"^1.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^4.8\",\n        \"mikey179/vfsStream\": \"^1.2\"\n    },\n    \"bin\": [\n        \"bin/jolici\"\n    ],\n    \"autoload\" : {\n        \"psr-0\" : {\n            \"Joli\" : \"src\"\n        }\n    }\n}\n"
  },
  {
    "path": "docs/command/clean.md",
    "content": "# The clean command\n\nJoliCi try to keep the number of docker images, containers and build directories as low as possible when running test.\n \nBut sometimes thing can break hard and cleaning process is not run, so you will need to run this command in order \nto not overflow your hard drive.\n\n## Default command:\n\n```\nphp jolici.phar clean\n```\n\nThe default command will clean all images, containers and build directories except for the last one, in the current project directory.\n\n## Options\n\nHere is a list of options you can pass to the clean command:\n\n* `--project-path DIRECTORY` / `-p DIRECTORY`: Set the root path (DIRECTORY) of your project, use current directory as a default\n* `--keep NUMBER` / `-k NUMBER`: How many versions (NUMBER) of images, containers and / or build directories should the clean process keeps\n* `--only-containers`: Remove only containers\n* `--only-directories`: Remove only build directories\n* `--only-images`: Remove only images\n* `--force`: Force removal of images\n\n"
  },
  {
    "path": "docs/command/run.md",
    "content": "# The run command\n\nThe run command is the main command of JoliCi. It create and prepare the differents environments (jobs) and execute the command on every one.\n\n## Workflow\n\n### Creating environments\n\nAn environment is created when a build strategy is detected on your project, \nthis can be as simple as [parsing a `.travis.yml`](../strategies/TravisCiStrategy.md) file or more complex\nby [reading a `.jolici directory](../strategies/JoliCiStrategy.md)\n\nEach environment will be prepared in a temporary directory so this will never modify your project (no need to add a line in \nyour .gitignore file)\n\n### Building environment\n\nAn environment is simply a docker image build with a Dockerfile, which can be generated for you when using\nTravisCi strategy or using your own with JoliCi strategy\n\n### Starting services\n\nBefore running test, JoliCi will try to launch services determined by your configuration file (only on TravisCi for the moment), \nin order to have mysql, memcached, elasticsearch... services available for your tests.\n\nEach service will start from a clean state, data is not keeped.\n\nFor the moment, services are not host in the test container but run in a separate container.\nTo use them you need to set the correct host name for the service on your different configuration files.\n\nThe hostname for each service is the same as the service name, so i.e. connecting to mysql can be done like that in \nyour test container : \n\n```\nmysql -u travis -h mysql\n```\n\nAlso be aware that services are only available during the script execution, any action using a service before the script \nwill fail (like creating the schema in the install part).\n\nElimination of this two downsides are currently the focus for the next releases.\n\n### Running test\n\nOnce the environment is ready, JoliCi will run your test command on it and display the output directly on your console.\n\n### Exit code\n\nIf all test on each environment is successful (return 0 for exit code), jolici will return as well 0 for exit code.\nOtherwise it will return the number of failing tests.\n\n### Cleaning\n\nJoliCi try to do his best to keep disk space as low as possible without decreasing speed. In order to do this it will delete all\nfiles related to an environment at the exception of the last run so we can use his cache.\n\n## Default command:\n\n```bash\nphp jolici.phar run\n```\n\nThe default command will run test for each environments created, this will only output the result of the test command. If you \nwant to see the output of the build process (like TravisCi) you can run this command with a verbose option:\n\n```bash\nphp jolici.phar run -v\n```\n\n## Options\n\nHere is a list of options you can pass to the clean command:\n\n* `--project-path DIRECTORY` / `-p DIRECTORY`: Set the root path (DIRECTORY) of your project, use current directory as a default\n* `--keep NUMBER` / `-k NUMBER`: How many versions (NUMBER) of images, containers and / or build directories should be clean after running test (default to 1)\n* `--no-cache`: Use this option if you don't want to use the cache from the last build\n* `--timeout TIMEOUT` / `-t TIMEOUT`: This is the timeout, in seconds, for the run command, it allows to aborting test if a command hangs up forever (default to 5 minutes)\n* `--notify`: Use this option to display desktop notifications for each job\n\n## Overriding test command\n\nIf one more argument is present there will be considered as the command line for running test, \nfor example to see the php version of an environment created by JoliCi you can do the following command:\n\n```bash\nphp jolici.phar run php -v\n```\n"
  },
  {
    "path": "docs/command/updates-image.md",
    "content": "# The images-update command\n\nThis command is used to update all images jolici used to create test environments.\n\n```\n$ jolici images-update\nUpdate jolicode/php56 image\nUpdate jolicode/php55 image\nUpdate jolicode/hhvm image\nUpdate jolicode/php54 image\n```\n"
  },
  {
    "path": "docs/installation.md",
    "content": "# Installation\n\n## Install globally\n\n### Download\n\n[Download the last version of jolici](https://github.com/jolicode/JoliCi/releases), i.e. for v0.3.1:\n\n```bash\ncurl https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar -o /usr/local/bin/jolici\nchmod +x /usr/local/bin/jolici\n```\n\n```bash\nwget https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar -O /usr/local/bin/jolici\nchmod +x /usr/local/bin/jolici\n```\n\n### Composer\n\n```\ncomposer global require \"jolicode/jolici:*\"\n```\n\n## Install per project\n\n### Download\n\n```\ncurl http://jolici.jolicode.com/jolici.phar\n```\n\n```\nwget http://jolici.jolicode.com/jolici.phar\n```\n\n### Composer\n\n```\ncomposer require --dev \"jolicode/jolici:*\"\n```\n\nThis tool is mainly used for development purpose so here we set the --dev option, if this tool is not only for developers remove that option.\n\n\n"
  },
  {
    "path": "docs/strategies/JoliCiStrategy.md",
    "content": "# JoliCiBuildStrategy\n\nThis strategy is based on a directory structure and Dockerfile, this is the most flexible strategy as you can whatever you want for environment.\n\n## Usage\n\nTo create jobs the project **MUST** have a `.jolici` directory.\n\nEach subdirectory is then considered as a different job, they **MUST** have a `Dockerfile` which will explain how to create the environment.\n\nThe test command is determined by the `CMD` or `ENTRYPOINT` keywords in the `Dockerfile`.\n\n## Overriding files\n\nYou may want to have a different configuration file for each environment. In order to have this behavior all files under the job directory (`.jolici/my_job_environment`) will be \ncopied, before creation, to the root directory of the project.\n\n## Add source to the build\n\nDue to the precedent behavior, the Dockerfile is now at the root of your project. To add the source code of your project in your job environment you \ncan add this command inside your Dockerfile:\n\n```\nADD . /project\n```\n\nYour project will then be available at the `/project` path in the environment.\n"
  },
  {
    "path": "docs/strategies/TravisCiStrategy.md",
    "content": "# TravisCiBuildStrategy\n\nThis strategy parses the .travis.yml file at the root of your project to create a Dockerfile for each job and run tests.\n\n## Language and version support\n\nFor the moment only the following language / version list is supported, the goal is to support what travis supports:\n\n* php\n\t* 5.3 (5.3.28)\n\t* 5.4 (5.4.31)\n\t* 5.5 (5.5.15)\n\t* 5.6 (5.6.3)\n\t* hhvm (3.3.0)\n* ruby\n    * 1.9.3\n    * 2.0.0\n    * 2.1.0\n* node\n    * 0.6\n    * 0.8\n    * 0.10\n    * 0.11\n\n## Service support\n\nSome services are supported for you to use\n\n## Docker images\n\nThis strategy use pre-built docker images available on this github repository: [https://github.com/jolicode/docker-images](https://github.com/jolicode/docker-images)\n\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->\n<phpunit\n    backupGlobals               = \"false\"\n    backupStaticAttributes      = \"false\"\n    colors                      = \"true\"\n    convertErrorsToExceptions   = \"true\"\n    convertNoticesToExceptions  = \"true\"\n    convertWarningsToExceptions = \"true\"\n    processIsolation            = \"false\"\n    stopOnFailure               = \"false\"\n    syntaxCheck                 = \"false\"\n    bootstrap                   = \"vendor/autoload.php\" >\n\n    <testsuites>\n        <testsuite name=\"JoliCi Unit Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n\n    <filter>\n        <whitelist processUncoveredFilesFromWhitelist=\"true\">\n            <directory>src</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "resources/templates/Dockerfile-travis.twig",
    "content": "{% extends \"Dockerfile.twig\" %}\n\n{% block env %}\n{% for key,value in global_env %}\nENV {{ key }}={{ value }}\n{% endfor %}\n\n{% for key,value in env %}\nENV {{ key }} {{ value }}\n{% endfor %}\n{% endblock %}\n\n{% block before_install %}\n    {% for line in before_install %}\nRUN /bin/bash -c -l \"cd $WORKDIR && {{ line|replace({\"$\": \"\\\\\\$\", '\"': '\\\\\\\"'}) }}\"\n    {% endfor %}\n{% endblock %}\n\n{% block install %}\n    {% for line in install %}\nRUN /bin/bash -c -l \"cd $WORKDIR && {{ line|replace({\"$\": \"\\\\\\$\", '\"': '\\\\\\\"'}) }}\"\n    {% endfor %}\n{% endblock %}\n\n{% block before_script %}\n    {% for line in before_script %}\nRUN /bin/bash -c -l \"cd $WORKDIR && {{ line|replace({\"$\": \"\\\\\\$\", '\"': '\\\\\\\"'}) }}\"\n    {% endfor %}\n{% endblock %}\n\n{% block script %}\nCMD /bin/bash -c -l \"cd $WORKDIR{% for line in script %} && {{ line|replace({\"$\": \"\\\\\\$\", '\"': '\\\\\\\"'}) }}{% endfor %}\"\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/Dockerfile.twig",
    "content": "{% block from %}\nFROM jolicode/base:latest\n{% endblock %}\n\n{% block content %}\n{% endblock %}\n\nLABEL com.jolici.image=\"true\"\nENV WORKDIR $HOME/project\nADD . $WORKDIR\nWORKDIR $HOME/project\nRUN sudo chown travis:travis -R $HOME/project\n\n{% block env %}{% endblock %}\n\n{% block before_install %}{% endblock %}\n\n{% block install %}{% endblock %}\n\n{% block before_script %}{% endblock %}\n\n{% block script %}{% endblock %}\n"
  },
  {
    "path": "resources/templates/node_js/Dockerfile-0.10.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/node-0.10:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/node_js/Dockerfile-0.11.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/node-0.11:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/node_js/Dockerfile-0.6.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/node-0.6:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/node_js/Dockerfile-0.8.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/node-0.8:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/php/Dockerfile-5.3.twig",
    "content": "{% extends \"php/Dockerfile.twig\" %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION php5.3\n{% endblock %}\n\n{% block from %}\nFROM jolicode/php53:latest\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile-5.4.twig",
    "content": "{% extends \"php/Dockerfile.twig\" %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION php5.4\n{% endblock %}\n\n{% block from %}\nFROM jolicode/php54:latest\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile-5.5.twig",
    "content": "{% extends \"php/Dockerfile.twig\" %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION php5.5\n{% endblock %}\n\n{% block from %}\nFROM jolicode/php55:latest\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile-5.6.twig",
    "content": "{% extends \"php/Dockerfile.twig\" %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION php5.6\n{% endblock %}\n\n{% block from %}\nFROM jolicode/php56:latest\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile-7.twig",
    "content": "{% extends \"php/Dockerfile.twig\" %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION php7.0-dev\n{% endblock %}\n\n{% block from %}\nFROM jolicode/php-master:latest\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile-hhvm.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/hhvm:latest\n{% endblock %}\n\n{% block env %}\n{{ parent() }}\nENV TRAVIS_PHP_VERSION hhvm\n{% endblock %}\n\n{% block before_install %}\nRUN echo 'date.timezone={{ timezone }}' | sudo tee -a /etc/hhvm/php.ini\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/php/Dockerfile.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block env %}\n{{ parent() }}\n{% endblock %}\n\n{% block before_install %}\nRUN echo 'date.timezone={{ timezone }}' >> /home/.phpenv/versions/`cat /home/.phpenv/version`/etc/php.ini\n{% endblock %}\n"
  },
  {
    "path": "resources/templates/ruby/Dockerfile-1.9.3.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/ruby-1.9.3:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/ruby/Dockerfile-2.0.0.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/ruby-2.0.0:latest\n{% endblock %}"
  },
  {
    "path": "resources/templates/ruby/Dockerfile-2.1.0.twig",
    "content": "{% extends \"Dockerfile-travis.twig\" %}\n\n{% block from %}\nFROM jolicode/ruby-2.1.0:latest\n{% endblock %}"
  },
  {
    "path": "src/Joli/JoliCi/BuildStrategy/BuildStrategyInterface.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Job;\n\n/**\n * Interface that all Build strategy must implement\n *\n * @author Joel Wurtz <jwurtz@jolicode.com>\n */\ninterface BuildStrategyInterface\n{\n    const WORKDIR = \"/home/project\";\n\n    /**\n     * Create / Get jobs for a project\n     *\n     * @param string $directory Location of project\n     *\n     * @return \\Joli\\JoliCi\\Job[] Return a list of jobs to create\n     */\n    public function getJobs($directory);\n\n    /**\n     * Prepare a job (generally copy its files to a new directory\n     *\n     * @param \\Joli\\JoliCi\\Job $job\n     *\n     * @return void\n     */\n    public function prepareJob(Job $job);\n\n    /**\n     * Return name of the build strategy\n     *\n     * @return string\n     */\n    public function getName();\n\n    /**\n     * Tell if the build strategy is supported for a project\n     *\n     * @param string $directory Location of project\n     *\n     * @return boolean\n     */\n    public function supportProject($directory);\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/BuildStrategy/ChainBuildStrategy.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Job;\n\nclass ChainBuildStrategy implements BuildStrategyInterface\n{\n    /**\n     * @var BuildStrategyInterface[] A list of strategy to use for this builder\n     */\n    private $strategies = array();\n\n    /**\n     * Add a build strategy to builder\n     *\n     * @param BuildStrategyInterface $strategy Strategy to add\n     */\n    public function pushStrategy(BuildStrategyInterface $strategy)\n    {\n        $this->strategies[$strategy->getName()] = $strategy;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getJobs($directory)\n    {\n        $builds = array();\n\n        foreach ($this->strategies as $strategy) {\n            if ($strategy->supportProject($directory)) {\n                $builds += $strategy->getJobs($directory);\n            }\n        }\n\n        return $builds;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function prepareJob(Job $job)\n    {\n        $this->strategies[$job->getStrategy()]->prepareJob($job);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getName()\n    {\n        return 'Chain';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function supportProject($directory)\n    {\n        foreach ($this->strategies as $strategy) {\n            if ($strategy->supportProject($directory)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/BuildStrategy/JoliCiBuildStrategy.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Job;\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\nuse Joli\\JoliCi\\Naming;\nuse Symfony\\Component\\Finder\\Finder;\n\n/**\n * JoliCi implementation for build\n *\n * A project must have a .jolici directory, each directory inside this one will be a type of build and must contain a Dockerfile to be executable\n *\n * @author Joel Wurtz <jwurtz@jolicode.com>\n */\nclass JoliCiBuildStrategy implements BuildStrategyInterface\n{\n    /**\n     * @var string Base path for build\n     */\n    private $buildPath;\n\n    /**\n     * @var Filesystem Filesystem service\n     */\n    private $filesystem;\n\n    /**\n     * @var Naming Use to name the image created\n     */\n    private $naming;\n\n    /**\n     * @param string     $buildPath  Directory where build must be created\n     * @param Naming     $naming     Naming service\n     * @param Filesystem $filesystem Filesystem service\n     */\n    public function __construct($buildPath, Naming $naming, Filesystem $filesystem)\n    {\n        $this->buildPath  = $buildPath;\n        $this->naming     = $naming;\n        $this->filesystem = $filesystem;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getJobs($directory)\n    {\n        $builds = array();\n        $finder = new Finder();\n        $finder->directories();\n\n        foreach ($finder->in($this->getJoliCiStrategyDirectory($directory)) as $dir) {\n            $builds[] = new Job(\n                $this->naming->getProjectName($directory),\n                $this->getName(),\n                $this->naming->getUniqueKey(array('build' => $dir->getFilename())),\n                array(\n                    'origin' => $directory,\n                    'build'  => $dir->getRealPath(),\n                ),\n                \"JoliCi Build: \".$dir->getFilename()\n            );\n        }\n\n        return $builds;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function prepareJob(Job $job)\n    {\n        $origin = $job->getParameters()['origin'];\n        $target = $this->buildPath.DIRECTORY_SEPARATOR. $job->getDirectory();\n        $build  = $job->getParameters()['build'];\n\n        // First mirroring target\n        $this->filesystem->mirror($origin, $target, null, array(\n            'delete'   => true,\n            'override' => true,\n        ));\n\n        // Second override target with build dir\n        $this->filesystem->mirror($build, $target, null, array(\n            'delete'   => false,\n            'override' => true,\n        ));\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getName()\n    {\n        return \"JoliCi\";\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function supportProject($directory)\n    {\n        return file_exists($this->getJoliCiStrategyDirectory($directory)) && is_dir($this->getJoliCiStrategyDirectory($directory));\n    }\n\n    /**\n     * Return the jolici strategy directory where there must be builds\n     *\n     * @param  string $projectPath\n     * @return string\n     */\n    protected function getJoliCiStrategyDirectory($projectPath)\n    {\n        return $projectPath.DIRECTORY_SEPARATOR.'.jolici';\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/BuildStrategy/TravisCiBuildStrategy.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n *\n * (c) Joel Wurtz <jwurtz@jolicode.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Job;\nuse Joli\\JoliCi\\Builder\\DockerfileBuilder;\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\nuse Joli\\JoliCi\\Matrix;\nuse Joli\\JoliCi\\Naming;\nuse Joli\\JoliCi\\Service;\nuse Symfony\\Component\\Yaml\\Yaml;\n\n/**\n * TravisCi implementation for build strategy\n *\n * A project must have a .travis.yml file\n *\n * @author Joel Wurtz <jwurtz@jolicode.com>\n */\nclass TravisCiBuildStrategy implements BuildStrategyInterface\n{\n    private $languageVersionKeyMapping = array(\n        'ruby' => 'rvm',\n    );\n\n    private $defaults = array(\n        'php' => array(\n            'before_install' => array(),\n            'install'        => array(),\n            'before_script'  => array(),\n            'script'         => array('phpunit'),\n            'env'            => array(),\n            'default_versions' => array('5.6')\n        ),\n        'ruby' => array(\n            'before_install' => array(),\n            'install'        => array(),\n            'before_script'  => array(),\n            'script'         => array('bundle exec rake'),\n            'env'            => array(),\n            'default_versions' => array('2.1.0')\n        ),\n        'node_js' => array(\n            'before_install' => array(),\n            'install'        => array(),\n            'before_script'  => array(),\n            'script'         => array('npm test'),\n            'env'            => array(),\n            'default_versions' => array('0.10')\n        ),\n    );\n\n    private $servicesMapping = array(\n        'mongodb' => array(\n            'repository' => 'mongo',\n            'tag' => '2.6',\n            'config' => array()\n        ),\n        'mysql'   => array(\n            'repository' => 'mysql',\n            'tag' => '5.5',\n            'config' => array(\n                'Env' => array(\n                    'MYSQL_ROOT_PASSWORD=\"\"',\n                    'MYSQL_USER=travis',\n                    'MYSQL_PASSWORD=\"\"'\n                )\n            )\n        ),\n        'postgresql' => array(\n            'repository' => 'postgres',\n            'tag'        => '9.1',\n            'config'     => array()\n        ),\n        'couchdb' => array(\n            'repository' => 'fedora/couchdb',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n        'rabbitmq' => array(\n            'repository' => 'dockerfile/rabbitmq',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n        'memcached' => array(\n            'repository' => 'sylvainlasnier/memcached',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n        'redis-server' => array(\n            'repository' => 'redis',\n            'tag' => '2.8',\n            'config' => array()\n        ),\n        'cassandra' => array(\n            'repository' => 'spotify/cassandra',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n        'neo4j' => array(\n            'repository' => 'tpires/neo4j',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n        'elasticsearch' => array(\n            'repository' => 'dockerfile/elasticsearch',\n            'tag' => 'latest',\n            'config' => array()\n        ),\n    );\n\n    /**\n     * @var DockerfileBuilder Builder for dockerfile\n     */\n    private $builder;\n\n    /**\n     * @var string Build path for project\n     */\n    private $buildPath;\n\n    /**\n     * @var Filesystem Filesystem service\n     */\n    private $filesystem;\n\n    /**\n     * @var \\Joli\\JoliCi\\Naming Naming service to create docker name for images\n     */\n    private $naming;\n\n    /**\n     * @param DockerfileBuilder $builder    Twig Builder for Dockerfile\n     * @param string            $buildPath  Directory where builds are created\n     * @param Naming            $naming     Naming service\n     * @param Filesystem        $filesystem Filesystem service\n     */\n    public function __construct(DockerfileBuilder $builder, $buildPath, Naming $naming, Filesystem $filesystem)\n    {\n        $this->builder    = $builder;\n        $this->buildPath  = $buildPath;\n        $this->naming     = $naming;\n        $this->filesystem = $filesystem;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getJobs($directory)\n    {\n        $jobs       = array();\n        $config     = Yaml::parse(file_get_contents($directory.DIRECTORY_SEPARATOR.\".travis.yml\"));\n        $matrix     = $this->createMatrix($config);\n        $services   = $this->getServices($config);\n        $timezone   = ini_get('date.timezone');\n\n        foreach ($matrix->compute() as $possibility) {\n            $parameters   = array(\n                'language' => $possibility['language'],\n                'version' => $possibility['version'],\n                'environment' => $possibility['environment'],\n            );\n\n            $description = sprintf('%s = %s', $possibility['language'], $possibility['version']);\n\n            if ($possibility['environment'] !== null) {\n                $description .= sprintf(', Environment: %s', json_encode($possibility['environment']));\n            }\n\n            $jobs[] = new Job($this->naming->getProjectName($directory), $this->getName(), $this->naming->getUniqueKey($parameters), array(\n                'language'       => $possibility['language'],\n                'version'        => $possibility['version'],\n                'before_install' => $possibility['before_install'],\n                'install'        => $possibility['install'],\n                'before_script'  => $possibility['before_script'],\n                'script'         => $possibility['script'],\n                'env'            => $possibility['environment'],\n                'global_env'     => $possibility['global_env'],\n                'timezone'       => $timezone,\n                'origin'         => realpath($directory),\n            ), $description, null, $services);\n        }\n\n        return $jobs;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function prepareJob(Job $job)\n    {\n        $parameters = $job->getParameters();\n        $origin     = $parameters['origin'];\n        $target     = $this->buildPath.DIRECTORY_SEPARATOR. $job->getDirectory();\n\n        // First mirroring target\n        $this->filesystem->mirror($origin, $target, null, array(\n            'delete' => true,\n            'override' => true,\n        ));\n\n        // Create dockerfile\n        $this->builder->setTemplateName(sprintf(\"%s/Dockerfile-%s.twig\", $parameters['language'], $parameters['version']));\n        $this->builder->setVariables($parameters);\n        $this->builder->setOutputName('Dockerfile');\n        $this->builder->writeOnDisk($target);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getName()\n    {\n        return \"TravisCi\";\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function supportProject($directory)\n    {\n        return file_exists($directory.DIRECTORY_SEPARATOR.\".travis.yml\") && is_file($directory.DIRECTORY_SEPARATOR.\".travis.yml\");\n    }\n\n    /**\n     * Get command lines to add for a configuration value in .travis.yml file\n     *\n     * @param array  $config   Configuration of travis ci parsed\n     * @param string $language Language for getting the default value if no value is set\n     * @param string $key      Configuration key\n     *\n     * @return array A list of command to add to Dockerfile\n     */\n    private function getConfigValue($config, $language, $key)\n    {\n        if (!isset($config[$key]) || empty($config[$key])) {\n            if (isset($this->defaults[$language][$key])) {\n                return $this->defaults[$language][$key];\n            }\n\n            return array();\n        }\n\n        if (!is_array($config[$key])) {\n            return array($config[$key]);\n        }\n\n        return $config[$key];\n    }\n\n    /**\n     * Create matrix of build\n     *\n     * @param array $config\n     *\n     * @return Matrix\n     */\n    protected function createMatrix($config)\n    {\n        $language = isset($config['language']) ? $config['language'] : 'ruby';\n\n        if (!isset($this->defaults[$language])) {\n            throw new \\Exception(sprintf('Language %s not supported', $language));\n        }\n\n        $versionKey       = isset($this->languageVersionKeyMapping[$language]) ? $this->languageVersionKeyMapping[$language] : $language;\n        $environmentLines = $this->getConfigValue($config, $language, \"env\");\n        $environnements   = array();\n        $globalEnv        = array();\n        $matrixEnv        = $environmentLines;\n        $versions         = (array) (isset($config[$versionKey]) ? $config[$versionKey] : $this->defaults[$language]['default_versions']);\n\n        foreach ($versions as $key => $version) {\n            if (!$this->isLanguageVersionSupported($language, $version)) {\n                unset($versions[$key]);\n            }\n        }\n\n        if (isset($environmentLines['matrix'])) {\n            $matrixEnv = $environmentLines['matrix'];\n        }\n\n        if (isset($environmentLines['global'])) {\n            foreach ($environmentLines['global'] as $environementVariable) {\n                if (is_array($environementVariable) && array_key_exists('secure', $environementVariable)) {\n                    continue;\n                }\n\n                list ($key, $value) = $this->parseEnvironementVariable($environementVariable);\n                $globalEnv = array_merge($globalEnv, array($key => $value));\n            }\n\n            if (!isset($environmentLines['matrix'])) {\n                $matrixEnv = array();\n            }\n        }\n\n        // Parsing environnements\n        foreach ($matrixEnv as $environmentLine) {\n            $environnements[] = $this->parseEnvironmentLine($environmentLine);\n        }\n\n        $matrix = new Matrix();\n        $matrix->setDimension('language', array($language));\n        $matrix->setDimension('environment', $environnements);\n        $matrix->setDimension('global_env', array($globalEnv));\n        $matrix->setDimension('version', $versions);\n        $matrix->setDimension('before_install', array($this->getConfigValue($config, $language, 'before_install')));\n        $matrix->setDimension('install', array($this->getConfigValue($config, $language, 'install')));\n        $matrix->setDimension('before_script', array($this->getConfigValue($config, $language, 'before_script')));\n        $matrix->setDimension('script', array($this->getConfigValue($config, $language, 'script')));\n\n\n        return $matrix;\n    }\n\n    /**\n     * Get services list from travis ci configuration file\n     *\n     * @param $config\n     *\n     * @return Service[]\n     */\n    protected function getServices($config)\n    {\n        $services       = array();\n        $travisServices = isset($config['services']) && is_array($config['services']) ? $config['services'] : array();\n\n        foreach ($travisServices as $service) {\n            if (isset($this->servicesMapping[$service])) {\n                $services[] = new Service(\n                    $service,\n                    $this->servicesMapping[$service]['repository'],\n                    $this->servicesMapping[$service]['tag'],\n                    $this->servicesMapping[$service]['config']\n                );\n            }\n        }\n\n        return $services;\n    }\n\n    /**\n     * Parse an environnement line from Travis to return an array of variables\n     *\n     * Transform:\n     *   \"A=B C=D\"\n     * Into:\n     *   array('a' => 'b', 'c' => 'd')\n     *\n     * @param $environmentLine\n     * @return array\n     */\n    private function parseEnvironmentLine($environmentLine)\n    {\n        $variables     = array();@\n        $variableLines = explode(' ', $environmentLine ?: '');\n\n        foreach ($variableLines as $variableLine) {\n            if (!empty($variableLine)) {\n                list($key, $value) = $this->parseEnvironementVariable($variableLine);\n                $variables[$key]   = $value;\n            }\n        }\n\n        return $variables;\n    }\n\n    /**\n     * Parse an envar\n     *\n     * @param $envVar\n     * @return array<Key, Value>\n     */\n    private function parseEnvironementVariable($envVar)\n    {\n        return explode('=', $envVar);\n    }\n\n    private function isLanguageVersionSupported($language, $version)\n    {\n        return file_exists(__DIR__\n            . DIRECTORY_SEPARATOR . '..'\n            . DIRECTORY_SEPARATOR . '..'\n            . DIRECTORY_SEPARATOR . '..'\n            . DIRECTORY_SEPARATOR . '..'\n            . DIRECTORY_SEPARATOR . 'resources'\n            . DIRECTORY_SEPARATOR . 'templates'\n            . DIRECTORY_SEPARATOR . $language\n            . DIRECTORY_SEPARATOR . 'Dockerfile-' . $version . '.twig');\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Builder/DockerfileBuilder.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\Builder;\n\nuse TwigGenerator\\Builder\\BaseBuilder;\n\nclass DockerfileBuilder extends BaseBuilder\n{\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Command/CleanCommand.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\Command;\n\nuse Joli\\JoliCi\\Container;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\n\nclass CleanCommand extends Command\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('clean');\n        $this->setDescription('Clean images, containers and/or directories of previous build for this project');\n        $this->addOption('project-path', 'p', InputOption::VALUE_OPTIONAL, \"Path where you project is (default to current directory)\", \".\");\n        $this->addOption('keep', 'k', InputOption::VALUE_OPTIONAL, \"Number of images / containers / directories per build to keep\", 1);\n        $this->addOption('only-containers', null, InputOption::VALUE_NONE, \"Only clean containers (no images or directories)\");\n        $this->addOption('only-directories', null, InputOption::VALUE_NONE, \"Only clean directories (no images or containers)\");\n        $this->addOption('only-images', null, InputOption::VALUE_NONE, \"Only clean images (no containers or directories), be aware that this may fail if containers are still attached to images (you may need to use force option)\");\n        $this->addOption('force', null, InputOption::VALUE_NONE, \"Force removal for images\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        $container = new Container();\n        $vacuum = $container->getVacuum();\n\n        if ($input->getOption('only-containers')) {\n            $vacuum->cleanContainers($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep')));\n\n            return 0;\n        }\n\n        if ($input->getOption('only-directories')) {\n            $vacuum->cleanDirectories($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep')));\n\n            return 0;\n        }\n\n        if ($input->getOption('only-images')) {\n            $vacuum->cleanImages($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep')), $input->getOption('force'));\n\n            return 0;\n        }\n\n        $vacuum->clean($input->getOption('project-path'), $input->getOption('keep'), $input->getOption('force'));\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Command/RunCommand.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi\\Command;\n\nuse Joli\\JoliCi\\Container;\nuse Joli\\JoliNotif\\Notification;\nuse Joli\\JoliNotif\\NotifierFactory;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputOption;\n\nclass RunCommand extends Command\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('run');\n        $this->setDescription('Run tests on your project');\n        $this->addOption('project-path', 'p', InputOption::VALUE_OPTIONAL, \"Path where you project is (default to current directory)\", \".\");\n        $this->addOption('keep', 'k', InputOption::VALUE_OPTIONAL, \"Number of images / containers / directories per build to keep when cleaning at the end of run\", 1);\n        $this->addOption('no-cache', null, InputOption::VALUE_NONE, \"Do not use cache of docker\");\n        $this->addOption('timeout', null, InputOption::VALUE_OPTIONAL, \"Timeout for docker client in seconds (default to 5 minutes)\", \"300\");\n        $this->addOption('notify', null, InputOption::VALUE_NONE, \"Show desktop notifications when a build is finished\");\n        $this->addArgument('cmd', InputArgument::OPTIONAL, \"Override test command\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        $container  = new Container();\n        $verbose    = (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity());\n        $strategy   = $container->getChainStrategy();\n        $executor   = $container->getExecutor(!$input->getOption('no-cache'), $verbose, $input->getOption('timeout'));\n        $serviceManager = $container->getServiceManager($verbose);\n        $notifier   = NotifierFactory::create();\n\n        $output->writeln(\"<info>Creating builds...</info>\");\n\n        $jobs = $strategy->getJobs($input->getOption(\"project-path\"));\n\n        $output->writeln(sprintf(\"<info>%s builds created</info>\", count($jobs)));\n\n        $exitCode = 0;\n\n        try {\n            foreach ($jobs as $job) {\n                $output->writeln(sprintf(\"\\n<info>Running job %s</info>\\n\", $job->getDescription()));\n\n                $serviceManager->start($job);\n                $strategy->prepareJob($job);\n\n                $success  = $executor->test($job, $input->getArgument('cmd'));\n                $exitCode += $success == 0 ? 0 : 1;\n\n                if ($input->getOption('notify')) {\n                    $notification = new Notification();\n                    $notification->setBody(sprintf('Test results for %s on %s', $container->getNaming()->getProjectName($input->getOption('project-path')), $job->getDescription()));\n                    $notification->setTitle($success == 0 ? 'Tests passed' : 'Tests failed');\n\n                    $notifier->send($notification);\n                }\n\n                $serviceManager->stop($job);\n            }\n        } catch (\\Exception $e) {\n            // Try stop last builds\n            if (isset($job)) {\n                $serviceManager->stop($job);\n            }\n            // We do not deal with exception (Console Component do it well),\n            // we just catch it to allow cleaner to be runned even if one of the build failed hard\n            // Simulation of a finally for php < 5.6 :-°\n        }\n\n        $container->getVacuum()->clean($input->getOption(\"project-path\"), $input->getOption(\"keep\"));\n\n        if (isset($e)) {\n            throw $e;\n        }\n\n        return $exitCode;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Command/UpdateImageCommand.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\Command;\n\nuse Joli\\JoliCi\\Container;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputOption;\n\nclass UpdateImageCommand extends Command\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure()\n    {\n        $this->setName('images-update');\n        $this->setDescription('Update docker images used to build test environnement');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function execute(InputInterface $input, OutputInterface $output)\n    {\n        $container = new Container();\n        $docker = $container->getDocker();\n        $logger = $container->getLoggerCallback((OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()));\n\n        foreach ($docker->getImageManager()->findAll() as $image) {\n            if (preg_match('#^jolicode/(.+?)$#', $image->getRepository())) {\n                $output->writeln(sprintf(\"Update %s image\", $image->getRepository()));\n                $docker->getImageManager()->pull($image->getRepository(), 'latest', $logger->getBuildCallback());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Container.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\Docker;\nuse Joli\\JoliCi\\BuildStrategy\\ChainBuildStrategy;\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\nuse Joli\\JoliCi\\Log\\SimpleFormatter;\nuse Joli\\JoliCi\\BuildStrategy\\TravisCiBuildStrategy;\nuse Joli\\JoliCi\\BuildStrategy\\JoliCiBuildStrategy;\nuse Joli\\JoliCi\\Builder\\DockerfileBuilder;\nuse Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy;\nuse Monolog\\Handler\\FingersCrossedHandler;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse TwigGenerator\\Builder\\Generator;\n\nclass Container\n{\n    private $docker;\n\n    /**\n     * Strategy based on the \".travis.yml\" file\n     *\n     * @return TravisCiBuildStrategy\n     */\n    public function getTravisCiStrategy()\n    {\n        $builder   = new DockerfileBuilder();\n        $generator = new Generator();\n        $generator->setTemplateDirs(array(\n            __DIR__.\"/../../../resources/templates\",\n        ));\n        $generator->setMustOverwriteIfExists(true);\n        $generator->addBuilder($builder);\n\n        return new TravisCiBuildStrategy($builder, $this->getBuildPath(), $this->getNaming(), $this->getFilesystem());\n    }\n\n    /**\n     * Strategy based on the \".jolici\" folder\n     *\n     * @return JoliCiBuildStrategy\n     */\n    public function getJoliCiStrategy()\n    {\n        return new JoliCiBuildStrategy($this->getBuildPath(), $this->getNaming(), $this->getFilesystem());\n    }\n\n    /**\n     * Chain strategy to allow multiples ones\n     *\n     * @return ChainBuildStrategy\n     */\n    public function getChainStrategy()\n    {\n        $strategy = new ChainBuildStrategy();\n        $strategy->pushStrategy($this->getTravisCiStrategy());\n        $strategy->pushStrategy($this->getJoliCiStrategy());\n\n        return $strategy;\n    }\n\n    /**\n     * Alias for the main strategy\n     *\n     * @return \\Joli\\JoliCi\\BuildStrategy\\BuildStrategyInterface\n     */\n    public function getStrategy()\n    {\n        return $this->getChainStrategy();\n    }\n\n    /**\n     * Get a console with finger crossed handler\n     *\n     * @param bool $verbose\n     *\n     * @return Logger\n     */\n    public function getConsoleLogger($verbose = false)\n    {\n        $logger               = new Logger(\"standalone-logger\");\n        $handler              = new StreamHandler(\"php://stdout\", $verbose ? Logger::DEBUG : Logger::INFO);\n        $simpleFormatter      = new SimpleFormatter();\n\n        $handler->setFormatter($simpleFormatter);\n        $logger->pushHandler($handler);\n\n        if (!$verbose) {\n            $stdErrHandler = new StreamHandler(\"php://stderr\", Logger::DEBUG);\n            $fingerCrossedHandler = new FingersCrossedHandler($stdErrHandler, new ErrorLevelActivationStrategy(Logger::ERROR), 10);\n\n            $logger->pushHandler($fingerCrossedHandler);\n            $stdErrHandler->setFormatter($simpleFormatter);\n        }\n\n        return $logger;\n    }\n\n    public function getVacuum()\n    {\n        return new Vacuum($this->getDocker(), $this->getNaming(), $this->getStrategy(), $this->getFilesystem(), $this->getBuildPath());\n    }\n\n    public function getFilesystem()\n    {\n        return new Filesystem();\n    }\n\n    public function getDocker()\n    {\n        if (!$this->docker) {\n            $this->docker = new Docker();\n        }\n\n        return $this->docker;\n    }\n\n    public function getExecutor($cache = true, $verbose = false, $timeout = 600)\n    {\n        return new Executor($this->getLoggerCallback($verbose), $this->getDocker(), $this->getBuildPath(), $cache, false, $timeout);\n    }\n\n    public function getServiceManager($verbose = false)\n    {\n        return new ServiceManager($this->getDocker(), $this->getLoggerCallback($verbose));\n    }\n\n    public function getBuildPath()\n    {\n        return sys_get_temp_dir().DIRECTORY_SEPARATOR.\".jolici-builds\";\n    }\n\n    public function getNaming()\n    {\n        return new Naming();\n    }\n\n    public function getLoggerCallback($verbose)\n    {\n        return new LoggerCallback($this->getConsoleLogger($verbose));\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Executor.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\API\\Model\\BuildInfo;\nuse Docker\\API\\Model\\ContainerConfig;\nuse Docker\\API\\Model\\HostConfig;\nuse Docker\\Docker;\nuse Docker\\Context\\Context;\nuse Docker\\Manager\\ContainerManager;\nuse Docker\\Manager\\ImageManager;\nuse Docker\\Stream\\BuildStream;\nuse Http\\Client\\Plugin\\Exception\\ClientErrorException;\nuse Monolog\\Logger;\n\nclass Executor\n{\n    /**\n     * Docker client\n     *\n     * @var Docker\n     */\n    protected $docker;\n\n    /**\n     * Logger to log message when building\n     *\n     * @var LoggerCallback\n     */\n    protected $logger;\n\n    /**\n     * @var boolean Use cache when building\n     */\n    private $usecache = true;\n\n    /**\n     * @var boolean Use cache when building\n     */\n    private $quietBuild = true;\n\n    /**\n     * @var integer Default timeout for run\n     */\n    private $timeout = 600;\n\n    /**\n     * @var string Base directory where builds are located\n     */\n    private $buildPath;\n\n    public function __construct(LoggerCallback $logger, Docker $docker, $buildPath, $usecache = true, $quietBuild = true, $timeout = 600)\n    {\n        $this->logger     = $logger;\n        $this->docker     = $docker;\n        $this->usecache   = $usecache;\n        $this->quietBuild = $quietBuild;\n        $this->timeout    = $timeout;\n        $this->buildPath  = $buildPath;\n    }\n\n    /**\n     * Test a build\n     *\n     * @param Job $build\n     * @param array|string $command\n     *\n     * @return integer\n     */\n    public function test(Job $build, $command = null)\n    {\n        $exitCode = 1;\n\n        if (false !== $this->create($build)) {\n            $exitCode = $this->run($build, $command);\n        }\n\n        return $exitCode;\n    }\n\n    /**\n     * Create a build\n     *\n     * @param Job $job Build used to create image\n     *\n     * @return \\Docker\\API\\Model\\Image|boolean Return the image created if successful or false otherwise\n     */\n    public function create(Job $job)\n    {\n        $context  = new Context($this->buildPath . DIRECTORY_SEPARATOR . $job->getDirectory());\n\n        $buildStream = $this->docker->getImageManager()->build($context->toStream(), [\n            't' => $job->getName(),\n            'q' => $this->quietBuild,\n            'nocache' => !$this->usecache\n        ], ImageManager::FETCH_STREAM);\n\n        $buildStream->onFrame($this->logger->getBuildCallback());\n        $buildStream->wait();\n\n        try {\n            return $this->docker->getImageManager()->find($job->getName());\n        } catch (ClientErrorException $e) {\n            if ($e->getResponse()->getStatusCode() == 404) {\n                return false;\n            }\n\n            throw $e;\n        }\n    }\n\n    /**\n     * Run a build (it's suppose the image exist in docker\n     *\n     * @param Job $job Build to run\n     * @param string|array $command Command to use when run the build (null, by default, will use the command registered to the image)\n     *\n     * @return integer The exit code of the command run inside (0 = success, otherwise it has failed)\n     */\n    public function run(Job $job, $command)\n    {\n        if (is_string($command)) {\n            $command = ['/bin/bash', '-c', $command];\n        }\n\n        $image = $this->docker->getImageManager()->find($job->getName());\n\n        $hostConfig = new HostConfig();\n\n        $config = new ContainerConfig();\n        $config->setCmd($command);\n        $config->setImage($image->getId());\n        $config->setHostConfig($hostConfig);\n        $config->setLabels(new \\ArrayObject([\n            'com.jolici.container=true'\n        ]));\n        $config->setAttachStderr(true);\n        $config->setAttachStdout(true);\n\n        $links = [];\n\n        foreach ($job->getServices() as $service) {\n            if ($service->getContainer()) {\n                $serviceContainer = $this->docker->getContainerManager()->find($service->getContainer());\n\n                $links[] = sprintf('%s:%s', $serviceContainer->getName(), $service->getName());\n            }\n        }\n\n        $hostConfig->setLinks($links);\n\n        $containerCreateResult = $this->docker->getContainerManager()->create($config);\n        $attachStream = $this->docker->getContainerManager()->attach($containerCreateResult->getId(), [\n            'stream' => true,\n            'stdout' => true,\n            'stderr' => true,\n        ], ContainerManager::FETCH_STREAM);\n\n        $attachStream->onStdout($this->logger->getRunStdoutCallback());\n        $attachStream->onStderr($this->logger->getRunStderrCallback());\n\n        $this->docker->getContainerManager()->start($containerCreateResult->getId());\n\n        $attachStream->wait();\n\n        $containerWait = $this->docker->getContainerManager()->wait($containerCreateResult->getId());\n\n        return $containerWait->getStatusCode();\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Filesystem/Filesystem.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n *\n * (c) Joel Wurtz <jwurtz@jolicode.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Joli\\JoliCi\\Filesystem;\n\nuse Symfony\\Component\\Filesystem\\Filesystem as BaseFilesystem;\n\nclass Filesystem extends BaseFilesystem\n{\n    /**\n     * Add keeping same permissions as origin file\n     *\n     * @see \\Symfony\\Component\\Filesystem\\Filesystem::copy()\n     *\n     * @param string  $originFile\n     * @param string  $targetFile\n     * @param Boolean $override\n     */\n    public function copy($originFile, $targetFile, $override = false)\n    {\n        parent::copy($originFile, $targetFile, $override);\n\n        $this->chmod($targetFile, fileperms($originFile));\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Job.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi;\n\nclass Job\n{\n    const BASE_NAME = 'jolici';\n\n    /**\n     * @var string Name of job\n     */\n    protected $project;\n\n    /**\n     * @var string Strategy associated with this job\n     */\n    protected $strategy;\n\n    /**\n     * @var string Uniq key for this \"kind\" of job\n     *\n     * This key is not a identifier (the name is the identifier in a job), it's more like a category,\n     * job with same parameters MUST have the same uniq key, this key is use to track the history\n     * of a job over time (for cleaning, reports, etc ....)\n     */\n    protected $uniq;\n\n    /**\n     * @var array Parameters of this job\n     *\n     * It mainly depend on the strategy, ie for TravisCi strategy this will include the language used, version used, etc ...\n     */\n    protected $parameters;\n\n    /**\n     * @var string Description of this job (generally a nice name for end user)\n     */\n    protected $description;\n\n    /**\n     * @var \\DateTime Date of creation of the job\n     */\n    protected $created;\n\n    /**\n     * @var Service[] Services linked to this job\n     */\n    private $services = array();\n\n    /**\n     * @param string    $project     Project of the job\n     * @param string    $strategy    Strategy of the job\n     * @param string    $uniq        A uniq identifier for this kind of job\n     * @param array     $parameters  Parameters of the job (mainly depend on the strategy)\n     * @param string    $description Description of this job (generally a nice name for end user)\n     * @param \\DateTime $created     Date of creation of the job\n     * @param array $services Services linked to the job\n     */\n    public function __construct($project, $strategy, $uniq, $parameters = array(), $description = \"\", $created = null, $services = array())\n    {\n        $this->project     = $project;\n        $this->description = $description;\n        $this->strategy    = $strategy;\n        $this->parameters  = $parameters;\n        $this->uniq        = $uniq;\n\n        if (null === $created) {\n            $created = new \\DateTime();\n        }\n\n        $this->created = $created;\n        $this->services = $services;\n    }\n\n    /**\n     * Get name of this job\n     *\n     * @return string\n     */\n    public function getName()\n    {\n        return sprintf('%s:%s', $this->getRepository(), $this->getTag());\n    }\n\n    /**\n     * Get repository name for docker images job with this strategy\n     *\n     * @return string\n     */\n    public function getRepository()\n    {\n        return sprintf('%s_%s/%s', static::BASE_NAME, strtolower($this->strategy), $this->project);\n    }\n\n    /**\n     * Generate the tag name for a docker image\n     *\n     * @return string\n     */\n    public function getTag()\n    {\n        return sprintf('%s-%s', $this->uniq, $this->created->format('U'));\n    }\n\n    /**\n     * Add a service to the job\n     *\n     * @param Service $service\n     */\n    public function addService(Service $service)\n    {\n        $this->services[] = $service;\n    }\n\n    /**\n     * Return all services linked to this job\n     *\n     * @return Service[]\n     */\n    public function getServices()\n    {\n        return $this->services;\n    }\n\n    /**\n     * Return directory of job\n     *\n     * @return string\n     */\n    public function getDirectory()\n    {\n        return $this->getName();\n    }\n\n    /**\n     * @return string\n     */\n    public function getDescription()\n    {\n        return $this->description;\n    }\n\n    /**\n     * @return string\n     */\n    public function getStrategy()\n    {\n        return $this->strategy;\n    }\n\n    /**\n     * @return string\n     */\n    public function getUniq()\n    {\n        return $this->uniq;\n    }\n\n    /**\n     * @return array\n     */\n    public function getParameters()\n    {\n        return $this->parameters;\n    }\n\n    /**\n     * @return \\DateTime\n     */\n    public function getCreated()\n    {\n        return $this->created;\n    }\n\n    public function __toString()\n    {\n        return $this->getName();\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Log/SimpleFormatter.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n*\n* (c) Joel Wurtz <jwurtz@jolicode.com>\n*\n* For the full copyright and license information, please view the LICENSE\n* file that was distributed with this source code.\n*/\n\nnamespace Joli\\JoliCi\\Log;\n\nuse Monolog\\Formatter\\FormatterInterface;\n\nclass SimpleFormatter implements FormatterInterface\n{\n    private $static = array();\n\n    private $lineNumber = 0;\n\n    /*\n     * (non-PHPdoc) @see \\Monolog\\Formatter\\FormatterInterface::format()\n     */\n    public function format(array $record)\n    {\n        if (isset($record['context']['clear-static'])) {\n            $this->static = array();\n\n            return \"\";\n        }\n\n        $message = $record['message'];\n\n        if (isset($record['context']['static']) && $record['context']['static']) {\n            $id      = $record['context']['static-id'];\n\n            if (!isset($this->static[$id])) {\n                $this->static[$id] = array(\n                    'current_line' => $this->lineNumber,\n                    'message'      => $message,\n                );\n\n                $message = sprintf(\"%s\\n\", $message);\n            } else {\n                $diff                         = ($this->lineNumber - $this->static[$id]['current_line']);\n                $lastMessage                  = $this->static[$id]['message'];\n                $this->static[$id]['message'] = $message;\n\n                //Add space to replace old string in message\n                if (mb_strlen($lastMessage) > mb_strlen($message)) {\n                    $message = str_pad($message, mb_strlen($lastMessage), \" \", STR_PAD_RIGHT);\n                }\n\n                $message = sprintf(\"\\x0D\\x1B[%sA%s\\x1B[%sB\\x0D\", $diff, $message, $diff);\n            }\n        }\n\n        if (preg_match('#\\n#', $message)) {\n            $this->lineNumber++;\n        }\n\n        return $message;\n    }\n\n    /*\n     * (non-PHPdoc) @see \\Monolog\\Formatter\\FormatterInterface::formatBatch()\n     */\n    public function formatBatch(array $records)\n    {\n        $message = '';\n        foreach ($records as $record) {\n            $message .= $this->format($record);\n        }\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/LoggerCallback.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\API\\Model\\BuildInfo;\nuse Psr\\Log\\LoggerInterface;\n\nclass LoggerCallback\n{\n    /**\n     * @var \\Closure\n     */\n    private $buildCallback;\n\n    /**\n     * @var \\Closure\n     */\n    private $runStdoutCallback;\n\n    /**\n     * @var \\Closure\n     */\n    private $runStderrCallback;\n\n    /**\n     * @var LoggerInterface\n     */\n    private $logger;\n\n    public function __construct(LoggerInterface $logger)\n    {\n        $build     = new \\ReflectionMethod($this, 'buildCallback');\n        $runStdout = new \\ReflectionMethod($this, 'runStdoutCallback');\n        $runStderr = new \\ReflectionMethod($this, 'runStderrCallback');\n\n        $this->buildCallback = $build->getClosure($this);\n        $this->runStdoutCallback = $runStdout->getClosure($this);\n        $this->runStderrCallback = $runStderr->getClosure($this);\n        $this->logger = $logger;\n    }\n\n    /**\n     * Get the build log callback when building / pulling an image\n     *\n     * @return callable\n     */\n    public function getBuildCallback()\n    {\n        return $this->buildCallback;\n    }\n\n    /**\n     * Get the run stdout callback for docker\n     *\n     * @return callable\n     */\n    public function getRunStdoutCallback()\n    {\n        return $this->runStdoutCallback;\n    }\n\n    /**\n     * Get the run stderr callback for docker\n     *\n     * @return callable\n     */\n    public function getRunStderrCallback()\n    {\n        return $this->runStderrCallback;\n    }\n\n    /**\n     * Clear static log buffer\n     */\n    public function clearStatic()\n    {\n        $this->logger->debug(\"\", array('clear-static' => true));\n    }\n\n    /**\n     * The build callback when creating a image, useful to see what happens during building\n     *\n     * @param BuildInfo $output An encoded json string from docker daemon\n     */\n    private function buildCallback(BuildInfo $output)\n    {\n        $message = \"\";\n\n        if ($output->getError()) {\n            $this->logger->error(sprintf(\"Error when creating job: %s\\n\", $output->getError()), array('static' => false, 'static-id' => null));\n            return;\n        }\n\n        if ($output->getStream()) {\n            $message = $output->getStream();\n        }\n\n        if ($output->getStatus()) {\n            $message = $output->getStatus();\n\n            if ($output->getProgress()) {\n                $message .= \" \" . $output->getProgress();\n            }\n        }\n\n        // Force new line\n        if (!$output->getId() && !preg_match('#\\n#', $message)) {\n            $message .= \"\\n\";\n        }\n\n        $this->logger->debug($message, array(\n            'static' => $output->getId() !== null,\n            'static-id' => $output->getId(),\n        ));\n    }\n\n    /**\n     * Run callback to catch stdout logs of test running\n     *\n     * @param string $output Output from run (stdout)\n     */\n    private function runStdoutCallback($output)\n    {\n        $this->logger->info($output);\n    }\n\n    /**\n     * Run callback to catch stderr logs of test running\n     *\n     * @param string $output Output from run (stderr)\n     */\n    private function runStderrCallback($output)\n    {\n        $this->logger->error($output);\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Matrix.php",
    "content": "<?php\n/*\n * This file is part of JoliCi.\n *\n * (c) Joel Wurtz <jwurtz@jolicode.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Joli\\JoliCi;\n\n/**\n * Create the Job list by computing all available possibility through dimensions\n */\nclass Matrix\n{\n    private $dimensions = array();\n\n    /**\n     * Set a dimension for this matrix\n     *\n     * @param string $name   Name of the dimension\n     * @param array  $values Value for this dimension\n     */\n    public function setDimension($name, array $values)\n    {\n        if (empty($values)) {\n            $values = array(null);\n        }\n\n        $this->dimensions[$name] = $values;\n    }\n\n    /**\n     * Return all possibility for the matrix\n     *\n     * @return array\n     */\n    public function compute()\n    {\n        $dimensions = $this->dimensions;\n\n        if (empty($dimensions)) {\n            return array();\n        }\n\n        // Pop first dimension\n        $values = reset($dimensions);\n        $name   = key($dimensions);\n        unset($dimensions[$name]);\n\n        // Create all possiblites for the first dimension\n        $posibilities = array();\n\n        foreach ($values as $v) {\n            $posibilities[] = array($name => $v);\n        }\n\n        // If only one dimension return simple all the possibilites created (break point of recursivity)\n        if (empty($dimensions)) {\n            return $posibilities;\n        }\n\n        // If not create a new matrix with remaining dimension\n        $matrix = new Matrix();\n\n        foreach ($dimensions as $name => $values) {\n            $matrix->setDimension($name, $values);\n        }\n\n        $result    = $matrix->compute();\n        $newResult = array();\n\n        foreach ($result as $value) {\n            foreach ($posibilities as $possiblity) {\n                $newResult[] = $value + $possiblity;\n            }\n        }\n\n        return $newResult;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Naming.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Behat\\Transliterator\\Transliterator;\n\nclass Naming\n{\n    /**\n     * Return a translitared name for a project\n     *\n     * @param string $projectPath Project directory\n     *\n     * @return string\n     */\n    public function getProjectName($projectPath)\n    {\n        $project = basename(realpath($projectPath));\n        $project = Transliterator::transliterate($project, '-');\n\n        return $project;\n    }\n\n    /**\n     * Generate a unique key for a list of parameters, with same parameters\n     * the key must not change (no random stuff) and the order does not matter\n     *\n     * @param array $parameters\n     *\n     * @return integer\n     */\n    public function getUniqueKey($parameters = array())\n    {\n        // First ordering parameters\n        ksort($parameters);\n\n        // Return a hash of the serialzed parameters\n        return crc32(serialize($parameters));\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Service.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\Container as DockerContainer;\n\n/**\n * A service is just an application or a tool link to a build which helps running tests\n *\n * It can be for example a MySQL database which contains the needed fixtures in order\n * to make functional tests\n *\n * Multiple services can be link to a Job and they are started before creation of the Job.\n * Once the Job is finished, all services linkes are shutdown and reset to initial state for subsequent Job\n */\nclass Service\n{\n    /**\n     * @var string Service name (use in link to container)\n     */\n    private $name;\n\n    /**\n     * @var string Repository for this service (from docker hub)\n     */\n    private $repository;\n\n    /**\n     * @var string Tag for this service (generally the version)\n     */\n    private $tag;\n\n    /**\n     * @var array Config when creating a container\n     */\n    private $config;\n\n    /**\n     * @var string Container id used for this service\n     */\n    private $container;\n\n    public function __construct($name, $repository, $tag, $config = array())\n    {\n        $this->name       = $name;\n        $this->repository = $repository;\n        $this->tag        = $tag;\n        $this->config     = $config;\n    }\n\n    /**\n     * @return string\n     */\n    public function getName()\n    {\n        return $this->name;\n    }\n\n    /**\n     * @return string\n     */\n    public function getRepository()\n    {\n        return $this->repository;\n    }\n\n    /**\n     * @return string\n     */\n    public function getTag()\n    {\n        return $this->tag;\n    }\n\n    /**\n     * @return array\n     */\n    public function getConfig()\n    {\n        return $this->config;\n    }\n\n    /**\n     * @return string The container id\n     */\n    public function getContainer()\n    {\n        return $this->container;\n    }\n\n    /**\n     * @param string $container The container id\n     */\n    public function setContainer($container)\n    {\n        $this->container = $container;\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/ServiceManager.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\API\\Model\\ContainerConfig;\nuse Docker\\Container as DockerContainer;\nuse Docker\\Docker;\nuse Docker\\Exception\\ImageNotFoundException;\nuse Docker\\Exception\\UnexpectedStatusCodeException;\nuse Docker\\Manager\\ImageManager;\nuse Http\\Client\\Plugin\\Exception\\ClientErrorException;\nuse Psr\\Log\\LoggerInterface;\n\nclass ServiceManager\n{\n    private $docker;\n\n    private $logger;\n\n    public function __construct(Docker $docker, LoggerCallback $logger)\n    {\n        $this->docker = $docker;\n        $this->logger = $logger;\n    }\n\n    /**\n     * Start services for a Job\n     *\n     * @param Job $build\n     */\n    public function start(Job $build)\n    {\n        foreach ($build->getServices() as $service) {\n            try {\n                $this->docker->getImageManager()->find(sprintf('%s:%s', $service->getRepository(), $service->getTag()));\n            } catch (ClientErrorException $e) {\n                if ($e->getResponse()->getStatusCode() == 404) {\n                    $buildStream = $this->docker->getImageManager()->create(null, [\n                        'fromImage' => sprintf('%s:%s', $service->getRepository(), $service->getTag())\n                    ], ImageManager::FETCH_STREAM);\n\n                    $buildStream->onFrame($this->logger->getBuildCallback());\n                    $buildStream->wait();\n                } else {\n                    throw $e;\n                }\n            }\n\n            $serviceConfig = $service->getConfig();\n            $containerConfig = new ContainerConfig();\n            $containerConfig->setImage(sprintf('%s:%s', $service->getRepository(), $service->getTag()));\n            $containerConfig->setLabels([\n                'com.jolici.container=true'\n            ]);\n\n            if (isset($serviceConfig['Env'])) {\n                $containerConfig->setEnv($serviceConfig['Env']);\n            }\n\n            $containerCreateResult = $this->docker->getContainerManager()->create($containerConfig);\n            $this->docker->getContainerManager()->start($containerCreateResult->getId());\n            $service->setContainer($containerCreateResult->getId());\n        }\n    }\n\n    /**\n     * Stop services for a Job and reinit volumes\n     *\n     * @param Job $job     The job to stop services\n     * @param int $timeout Timeout to wait before killing the service\n     */\n    public function stop(Job $job, $timeout = 10)\n    {\n        foreach ($job->getServices() as $service) {\n            if ($service->getContainer()) {\n                try {\n                    $this->docker->getContainerManager()->stop($service->getContainer(), [\n                        't' => $timeout\n                    ]);\n                } catch (ClientErrorException $e) {\n                    if ($e->getResponse()->getStatusCode() != 304) {\n                        throw $e;\n                    }\n                }\n\n                $this->docker->getContainerManager()->remove($service->getContainer(), [\n                    'v' => true,\n                    'force' => true\n                ]);\n\n                $service->setContainer(null);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Joli/JoliCi/Vacuum.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Docker\\API\\Model\\ImageItem;\nuse Docker\\Docker;\nuse Docker\\Image;\nuse Joli\\JoliCi\\BuildStrategy\\BuildStrategyInterface;\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\n\nclass Vacuum\n{\n    /**\n     * @var \\Docker\\Docker Docker API Client\n     */\n    private $docker;\n\n    /**\n     * @var Naming Service use to create name for image and project\n     */\n    private $naming;\n\n    /**\n     * @var BuildStrategy\\BuildStrategyInterface Strategy where we want to clean the builds\n     */\n    private $strategy;\n\n    /**\n     * @var string Location where the build are\n     */\n    private $buildPath;\n\n    /**\n     * @var \\Joli\\JoliCi\\Filesystem\\Filesystem\n     */\n    private $filesystem;\n\n    /**\n     * @param Docker                 $docker     Docker API Client\n     * @param Naming                 $naming     Naming service\n     * @param BuildStrategyInterface $strategy   Strategy used to create builds\n     * @param Filesystem             $filesystem Filesystem service\n     * @param string                 $buildPath  Directory where builds are created\n     */\n    public function __construct(Docker $docker, Naming $naming, BuildStrategyInterface $strategy, Filesystem $filesystem, $buildPath)\n    {\n        $this->docker     = $docker;\n        $this->naming     = $naming;\n        $this->strategy   = $strategy;\n        $this->buildPath  = $buildPath;\n        $this->filesystem = $filesystem;\n    }\n\n    /**\n     * Clean containers, images and directory from a project\n     *\n     * @param string  $projectPath Location of the project\n     * @param int     $keep        How many versions does we need to keep (1 is the default in order to have cache for each test)\n     * @param boolean $force       Force removal for images\n     */\n    public function clean($projectPath, $keep = 1, $force = false)\n    {\n        $builds = $this->getJobsToRemove($projectPath, $keep);\n\n        $this->cleanDirectories($builds);\n        $this->cleanContainers($builds);\n        $this->cleanImages($builds, $force);\n    }\n\n    /**\n     * Clean directories for given builds\n     *\n     * @param \\Joli\\JoliCi\\Job[] $jobs A list of jobs to remove images from\n     */\n    public function cleanDirectories($jobs = array())\n    {\n        foreach ($jobs as $job) {\n            $this->filesystem->remove($job->getDirectory());\n        }\n    }\n\n    /**\n     * Clean images for given builds\n     *\n     * @param \\Joli\\JoliCi\\Job[] $jobs A list of jobs to remove images from\n     */\n    public function cleanContainers($jobs = array())\n    {\n        $images     = array();\n        $containers = array();\n\n        foreach ($jobs as $job) {\n            if (isset($job->getParameters()['image'])) {\n                $images[] = $job->getParameters()['image'];\n            } else {\n                $images[] = sprintf('%s:%s', $job->getRepository(), $job->getTag());\n            }\n        }\n\n        foreach ($this->docker->getContainerManager()->findAll(['all' => 1]) as $container) {\n            $imageName = $container->getImage();\n\n            foreach ($images as $image) {\n                if ($image == $imageName) {\n                    $containers[] = $container;\n                }\n\n                if (preg_match('#^'.$imageName.'#', $image->getId())) {\n                    $containers[] = $container;\n                }\n            }\n        }\n\n        foreach ($containers as $container) {\n            $this->docker->getContainerManager()->remove($container->getId(), [\n                'v' => true,\n                'force' => true\n            ]);\n        }\n    }\n\n    /**\n     * Clean images for given builds\n     *\n     * @param \\Joli\\JoliCi\\Job[] $jobs   A list of jobs to remove images from\n     * @param boolean            $force  Force removal for images\n     */\n    public function cleanImages($jobs = array(), $force = false)\n    {\n        foreach ($jobs as $job) {\n            $this->docker->getImageManager()->remove(sprintf('%s:%s', $job->getRepository(), $job->getTag()), [\n                'force' => $force\n            ]);\n        }\n    }\n\n    /**\n     * Get all jobs to remove given a project and how many versions to keep\n     *\n     * @param string $projectPath The project path\n     * @param int    $keep        Number of project to keep\n     *\n     * @return \\Joli\\JoliCi\\Job[] A list of jobs to remove\n     */\n    public function getJobsToRemove($projectPath, $keep = 1)\n    {\n        $currentJobs  = $this->strategy->getJobs($projectPath);\n        $existingJobs = $this->getJobs($projectPath);\n        $uniqList = array();\n        $removes  = array();\n        $ordered  = array();\n\n        foreach ($currentJobs as $job) {\n            $uniqList[] = $job->getUniq();\n        }\n\n        // Remove not existant image (old build configuration)\n        foreach ($existingJobs as $job) {\n            if (!in_array($job->getUniq(), $uniqList)) {\n                $removes[] = $job;\n            } else {\n                $ordered[$job->getUniq()][$job->getCreated()->format('U')] = $job;\n            }\n        }\n\n        // Remove old image\n        foreach ($ordered as $jobs) {\n            ksort($jobs);\n            $keeped = count($jobs);\n\n            while ($keeped > $keep) {\n                $removes[] = array_shift($jobs);\n                $keeped--;\n            }\n        }\n\n        return $removes;\n    }\n\n    /**\n     * Get all jobs related to a project\n     *\n     * @param string $projectPath Directory where the project is\n     *\n     * @return \\Joli\\JoliCi\\Job[]\n     */\n    protected function getJobs($projectPath)\n    {\n        $jobs            = array();\n        $project         = $this->naming->getProjectName($projectPath);\n        $repositoryRegex = sprintf('#^%s_([a-z]+?)/%s:\\d+-\\d+$#', Job::BASE_NAME, $project);\n\n        foreach ($this->docker->getImageManager()->findAll() as $image) {\n            foreach ($image->getRepoTags() as $name) {\n                if (preg_match($repositoryRegex, $name, $matches)) {\n                    $jobs[] = $this->getJobFromImage($image, $name, $matches[1], $project);\n                }\n            }\n        }\n\n        return $jobs;\n    }\n\n    /**\n     * Create a job from a docker image\n     *\n     * @param ImageItem $image\n     * @param string    $strategy\n     * @param string    $project\n     *\n     * @return \\Joli\\JoliCi\\Job\n     */\n    protected function getJobFromImage(ImageItem $image, $imageName, $strategy, $project)\n    {\n        $tag = explode(':', $imageName)[1];\n        list($uniq, $timestamp)     = explode('-', $tag);\n\n        return new Job($project, $strategy, $uniq, array('image' => $image), \"\", \\DateTime::createFromFormat('U', $timestamp));\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/ChainBuildStrategyTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Job;\n\nclass ChainBuildStrategyTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testGetJobs()\n    {\n        $builder = new ChainBuildStrategy();\n        $builder->pushStrategy(new FooBuildStrategy());\n\n        $jobs = $builder->getJobs(\"test\");\n\n        $this->assertCount(1, $jobs);\n\n        $job = $jobs[0];\n\n        $this->assertEquals(\"test\", $job->getStrategy());\n        $this->assertContains(\"jolici_test/test:test-\", $job->getName());\n        $this->assertContains(\"jolici_test/test:test-\", $job->getDirectory());\n    }\n\n    public function testNoJobsEmpty()\n    {\n        $builder = new ChainBuildStrategy();\n        $builder->pushStrategy(new NoBuildStrategy());\n\n        $jobs = $builder->getJobs(\"test\");\n\n        $this->assertCount(0, $jobs);\n    }\n}\n\nclass FooBuildStrategy implements BuildStrategyInterface\n{\n    public function getJobs($directory)\n    {\n        return array(new Job(\"test\", \"test\", \"test\"));\n    }\n\n    public function getName()\n    {\n        return \"dummy\";\n    }\n\n    public function prepareJob(Job $job)\n    {\n    }\n\n    public function supportProject($directory)\n    {\n        return true;\n    }\n}\n\nclass NoBuildStrategy extends FooBuildStrategy\n{\n    public function supportProject($directory)\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/JoliCiBuildStrategyTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nuse Joli\\JoliCi\\BuildStrategy\\JoliCiBuildStrategy;\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\nuse org\\bovigo\\vfs\\vfsStream;\n\nclass JoliCiBuildStrategyTest extends \\PHPUnit_Framework_TestCase\n{\n    public function setUp()\n    {\n        $this->buildPath = vfsStream::setup('build-path');\n        $this->strategy = new JoliCiBuildStrategy(vfsStream::url('build-path'), new Naming(), new Filesystem());\n    }\n\n    public function testCreateJobs()\n    {\n        $jobs  = $this->strategy->getJobs(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"jolici\".DIRECTORY_SEPARATOR.\"project1\");\n\n        $this->assertCount(1, $jobs);\n    }\n\n    public function testPrepareJob()\n    {\n        $jobs = $this->strategy->getJobs(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"jolici\".DIRECTORY_SEPARATOR.\"project1\");\n        $job  = $jobs[0];\n\n        $this->strategy->prepareJob($job);\n\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())));\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()).\"/Dockerfile\"));\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()).\"/foo\"));\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()).\"/.jolici\"));\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()).\"/.jolici/test\"));\n        $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()).\"/.jolici/test/Dockerfile\"));\n    }\n\n    public function testSupportTrue()\n    {\n        $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"jolici\".DIRECTORY_SEPARATOR.\"project1\");\n\n        $this->assertTrue($support);\n    }\n\n    public function testSupportFalse()\n    {\n        $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"jolici\".DIRECTORY_SEPARATOR.\"project2\");\n\n        $this->assertFalse($support);\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/TravisCIBuildStrategyTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi\\BuildStrategy;\n\nuse Joli\\JoliCi\\Filesystem\\Filesystem;\nuse Joli\\JoliCi\\Naming;\nuse org\\bovigo\\vfs\\vfsStream;\nuse Joli\\JoliCi\\Builder\\DockerfileBuilder;\n\nclass TravisCIBuildStrategyTest extends \\PHPUnit_Framework_TestCase\n{\n    public function setUp()\n    {\n        $this->buildPath = vfsStream::setup('build-path');\n        $this->strategy = new TravisCiBuildStrategy(new DockerfileBuilder(), vfsStream::url('build-path'), new Naming(), new Filesystem());\n    }\n\n    public function testSupportTrue()\n    {\n        $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"travisci\".DIRECTORY_SEPARATOR.\"project1\");\n\n        $this->assertTrue($support);\n    }\n\n    public function testSupportFalse()\n    {\n        $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR.\"fixtures\".DIRECTORY_SEPARATOR.\"travisci\".DIRECTORY_SEPARATOR.\"project2\");\n\n        $this->assertFalse($support);\n    }\n\n    /**\n     * @dataProvider createMatrixVersionDataProvider\n     */\n    public function testCreateMatrixCanObtainVersions($versions)\n    {\n        $testConfig = [\n            'language' => 'php',\n            'php'      => $versions,\n        ];\n\n        $createMatrix = function ($config) {\n            return $this->createMatrix($config);\n        };\n        $createMatrix = $createMatrix->bindTo($this->strategy, $this->strategy);\n\n        $matrix = $createMatrix($testConfig);\n\n        $this->assertAttributeContains((array) $versions, 'dimensions', $matrix);\n    }\n\n    /**\n     * @return array\n     */\n    public function createMatrixVersionDataProvider()\n    {\n        return [\n            // Test with float\n            [5.5],\n            // Test with string\n            ['5.5'],\n            // Test with list\n            [['5.5', '5.6', '7']]\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project1/.jolici/test/Dockerfile",
    "content": "FROM ubuntu\n\nCMD echo 'lol'"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project1/foo",
    "content": "bar"
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project2/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Joli/JoliCi/BuildStrategy/fixtures/travisci/project1/.travis.yml",
    "content": "language: php\n\nphp:\n  - 5.3\n  - 5.4\n  - 5.5\n\nbefore_script:\n    - git config --global user.name travis-ci\n    - git config --global user.email travis@example.com\n\nscript:\n    - ./vendor/bin/phpunit"
  },
  {
    "path": "tests/Joli/JoliCi/JobTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nclass JobTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testConstructInitialisesAllTheFields()\n    {\n        $projectMock     = uniqid();\n        $strategyMock    = uniqid();\n        $uniqMock        = uniqid();\n        $parametersMock  = array('test' => uniqid());\n        $descriptionMock = 'test';\n        $createdMock     = new \\DateTime();\n        $servicesMock    = array('service' => uniqid());\n\n        $jobMock = new Job(\n            $projectMock,\n            $strategyMock,\n            $uniqMock,\n            $parametersMock,\n            $descriptionMock,\n            $createdMock,\n            $servicesMock\n        );\n\n        $this->assertAttributeSame($projectMock, 'project', $jobMock);\n        $this->assertSame($strategyMock, $jobMock->getStrategy());\n        $this->assertSame($uniqMock, $jobMock->getUniq());\n        $this->assertSame($parametersMock, $jobMock->getParameters());\n        $this->assertSame($descriptionMock, $jobMock->getDescription());\n        $this->assertSame($createdMock, $jobMock->getCreated());\n        $this->assertSame($servicesMock, $jobMock->getServices());\n\n        return $jobMock;\n    }\n\n    /**\n     * @param Job $jobMock\n     * @depends testConstructInitialisesAllTheFields\n     */\n    public function testAddAndGetServices($jobMock)\n    {\n        $serviceMock     = new Service('test', 'test', 'test');\n        $currentServices = $jobMock->getServices();\n\n        $jobMock->addService($serviceMock);\n\n        $this->assertEquals(\n            array_merge($currentServices, array($serviceMock)),\n            $jobMock->getServices()\n        );\n    }\n\n    public function testToStringReturnsTheNameOfTheJob()\n    {\n        $jobMock = new Job('project', 'strategy', uniqid());\n\n        $this->assertEquals($jobMock->getName(), $jobMock->__toString());\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/MatrixTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nclass MatrixTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testSetDimensionWillUseDefaultValuesIfEmptyProvided()\n    {\n        $matrixMock = new Matrix();\n\n        $matrixMock->setDimension('test', array());\n\n        $this->assertAttributeContains(array(null), 'dimensions', $matrixMock);\n    }\n\n    public function testComputeWillReturnEmptyIfNoDimensions()\n    {\n        $matrixMock = new Matrix();\n\n        $this->assertEquals(array(), $matrixMock->compute());\n    }\n\n    public function testCompute()\n    {\n        $matrix = new Matrix();\n        $matrix->setDimension('a', array(1, 2, 3));\n        $matrix->setDimension('b', array(1, 2, 3));\n        $matrix->setDimension('c', array(1, 2, 3));\n\n        $possibilities = $matrix->compute();\n\n        $expected = array(\n            array('a' => 1, 'b' => 1, 'c' => 1),\n            array('a' => 1, 'b' => 1, 'c' => 2),\n            array('a' => 1, 'b' => 1, 'c' => 3),\n\n            array('a' => 1, 'b' => 2, 'c' => 1),\n            array('a' => 1, 'b' => 2, 'c' => 2),\n            array('a' => 1, 'b' => 2, 'c' => 3),\n\n            array('a' => 1, 'b' => 3, 'c' => 1),\n            array('a' => 1, 'b' => 3, 'c' => 2),\n            array('a' => 1, 'b' => 3, 'c' => 3),\n\n            array('a' => 2, 'b' => 1, 'c' => 1),\n            array('a' => 2, 'b' => 1, 'c' => 2),\n            array('a' => 2, 'b' => 1, 'c' => 3),\n\n            array('a' => 2, 'b' => 2, 'c' => 1),\n            array('a' => 2, 'b' => 2, 'c' => 2),\n            array('a' => 2, 'b' => 2, 'c' => 3),\n\n            array('a' => 2, 'b' => 3, 'c' => 1),\n            array('a' => 2, 'b' => 3, 'c' => 2),\n            array('a' => 2, 'b' => 3, 'c' => 3),\n\n            array('a' => 3, 'b' => 1, 'c' => 1),\n            array('a' => 3, 'b' => 1, 'c' => 2),\n            array('a' => 3, 'b' => 1, 'c' => 3),\n\n            array('a' => 3, 'b' => 2, 'c' => 1),\n            array('a' => 3, 'b' => 2, 'c' => 2),\n            array('a' => 3, 'b' => 2, 'c' => 3),\n\n            array('a' => 3, 'b' => 3, 'c' => 1),\n            array('a' => 3, 'b' => 3, 'c' => 2),\n            array('a' => 3, 'b' => 3, 'c' => 3),\n        );\n\n        $this->assertCount(count($expected), $possibilities);\n\n        foreach ($expected as $value) {\n            $this->assertContains($value, $possibilities);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Joli/JoliCi/ServiceTest.php",
    "content": "<?php\n\nnamespace Joli\\JoliCi;\n\nclass ServiceTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testConstructInitialisesAllTheFields()\n    {\n        $nameMock       = uniqid();\n        $repositoryMock = uniqid();\n        $tagMock        = uniqid();\n        $configMock     = array('test' => uniqid());\n\n        $serviceMock = new Service($nameMock, $repositoryMock, $tagMock, $configMock);\n\n        $this->assertSame($nameMock, $serviceMock->getName());\n        $this->assertSame($repositoryMock, $serviceMock->getRepository());\n        $this->assertSame($tagMock, $serviceMock->getTag());\n        $this->assertSame($configMock, $serviceMock->getConfig());\n\n        return $serviceMock;\n    }\n\n    /**\n     * @param Service $serviceMock\n     * @depends testConstructInitialisesAllTheFields\n     */\n    public function testSetAndGetContainer($serviceMock)\n    {\n        $dockerContainerMock =\n            $this->getMockBuilder('Docker\\Container')\n                 ->setMethods(null)\n                 ->getMock();\n\n        $serviceMock->setContainer($dockerContainerMock);\n\n        $this->assertSame($dockerContainerMock, $serviceMock->getContainer());\n    }\n}\n"
  }
]