├── .gitignore ├── .travis.yml ├── LICENSE ├── LISEZ-MOI.md ├── README.md ├── examples └── RecetteÀMarcel ├── logo.png ├── marcel.py ├── requirements ├── dev.txt ├── main.txt └── test.txt ├── setup.py ├── tests ├── __init__.py └── test_translation.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | # command to install dependencies 7 | install: "pip install -r requirements/test.txt coveralls" 8 | # command to run tests 9 | script: py.test --cov marcel.py --cov-report term-missing 10 | after_success: 11 | - coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Balthazar Rouberol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LISEZ-MOI.md: -------------------------------------------------------------------------------- 1 | # Marcel, docker à la française 2 | 3 | ![logo](https://brouberol.github.io/marcel/images/logo/marcel-logo-yellow.png) 4 | 5 | Marcel est une surcouche française qui se base sur l'interface de commande docker et qui a pour but de remplacer docker, afin de préparer le chemin pour le nouveau Système d'Exploitation souverain Français. 6 | 7 | ## Exemples 8 | 9 | * ``docker run`` → ``marcel chauffe`` 10 | * ``docker images`` → ``marcel cederoms`` 11 | * ``docker login`` → ``marcel vos-papiers`` 12 | * ``docker logs`` → ``marcel bûches`` 13 | * ``docker pause`` → ``marcel rtt`` 14 | * ``docker suspend`` → ``marcel grève`` 15 | * ``docker tag`` → ``marcel graffiti`` 16 | * ``docker rmi`` → ``marcel rsa`` 17 | 18 | ## RecetteÀMarcel 19 | 20 | Pour des raisons évidentes, ``Dockerfile`` ne nous semble pas assez souverain, c'est pourquoi marcel utilise ``RecetteÀMarcel`` en lieu et place de ``Dockerfile``. 21 | 22 | Pour que cela fonctionne, il vous suffit d'intégrer le fichier ``RecetteÀMarcel`` dans votre dossier courant où vous exécuterez ensuite ``marcel bricole``. Et voilà, vous pouvez travailler ! 23 | 24 | ## Auto-complétion avec bash 25 | Ajoutez dans votre ``~/.bashrc`` : 26 | ```bash 27 | source <(marcel complète bash) 28 | ``` 29 | 30 | ## Comment contribuer ? 31 | 32 | Pour commencer, merci de contribuer à la splendeur de la « french tech ». Vous aurez besoin pour cela d'installer les dépendances dans votre environnement virtuel: 33 | 34 | ```$ pip install -r requirements/dev.txt``` 35 | 36 | Puis, créez une branche à partir de la branche maître et publiez vos fonctionnalités (et tests ;). Vous pouvez vérifier si tout fonctionne en lançant le commande tox. Quand tous les tests seront au vert, poussez votre fonctionnalité et créez une requête d'intégration. C'est tout! 37 | 38 | ## En quoi marcel est-il lié au système d'exploitation souverain français ? 39 | 40 | Ce projet vise à fournir un socle technologique acceptable pour le Grand SE de France. Vous pouvez jeter un coup d'œil au [document officiel](http://www.assemblee-nationale.fr/14/amendements/3318/CION_LOIS/CL129.asp) pour constater à quel point nous sommes enthousiastes et engagés dans le développement des technologies nécessaires au SE de demain, à l'instar de RedStar OS. Aidez-nous à rendre le monde meilleur en annihilant les SE non autorisés, comme ceux qui ont le culot d'intégrer des technologies de chiffrement sans fournir au préalable la clé à la DST ou aux services secrets français, si volontaires pour défendre les intérêts et les droits de nos concitoyens. 41 | 42 | ## Remerciements 43 | L'[idée originelle](https://github.com/docker/docker/issues/19396) revient à [@ndeloof](https://github.com/ndeloof). 44 | Le logo a été conçu par [jkneb](https://github.com/jkneb). 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marcel, the french Docker - Marcel, le docker français 2 | [![Build Status](https://travis-ci.org/brouberol/marcel.svg?branch=master)](https://travis-ci.org/brouberol/marcel) [![Coverage Status](https://coveralls.io/repos/github/brouberol/marcel/badge.svg)](https://coveralls.io/github/brouberol/marcel?branch=master) 3 | 4 | ![logo](https://brouberol.github.io/marcel/images/logo/marcel-logo-yellow.png) 5 | 6 | Marcel is a french wrapper around the docker CLI, intended as a drop-in replacement of docker, for the future french sovereign operating system. 7 | 8 | ## Examples 9 | 10 | * ``docker run`` → ``marcel chauffe`` 11 | * ``docker images`` → ``marcel cederoms`` 12 | * ``docker login`` → ``marcel vos-papiers`` 13 | * ``docker logs`` → ``marcel bûches`` 14 | * ``docker pause`` → ``marcel rtt`` 15 | * ``docker suspend`` → ``marcel grève`` 16 | * ``docker tag`` → ``marcel graffiti`` 17 | * ``docker rmi`` → ``marcel rsa`` 18 | 19 | ## Dockerfile 20 | 21 | Obviously, the ``Dockerfile`` name is not sovereign enough for us. That's why instead of ``Dockerfile``s, marcel uses ``RecetteÀMarcel`` files. 22 | For now, they use the exact same syntax as ``Dockerfile``, but we'll see about that. 23 | 24 | For it to work, you just need to include a ``RecetteÀMarcel`` file in the current directory where you execute your ``marcel bricole`` command, are you're good to go. 25 | 26 | ## Bash completion 27 | 28 | Add this line to your ``~/.bashrc``: 29 | ```bash 30 | source <(marcel complète bash) 31 | ``` 32 | 33 | ## Contributing. 34 | 35 | First of all, thanks for even considering contributing to the splendor of the French tech industry. You'll need to install the dev dependencies in your virtualenv: 36 | 37 | ```bash 38 | $ pip install -r requirements/dev.txt 39 | ``` 40 | 41 | Then, create a branch from master and commit your feature (and tests please :). You can test that everything works correctly by running the ``tox`` command. 42 | When all tests are green, push your feature, and create a pull request. Thats it! 43 | 44 | ## How is marcel related to the french OS ? 45 | 46 | This project aims at providing an acceptable technology background on which the Great OS (not firewall) of France could be based on. Take a look at [the official document](http://www.assemblee-nationale.fr/14/amendements/3318/CION_LOIS/CL129.asp) to see how compliant and willingfull to help we are, already developing the technologies for the OS of tomorrow, just like RedStar OS is. Help us make the World better by destroying non-compliant operating systems, e.g. thoses who includes encryption without backdoors. 47 | 48 | ## Thanks 49 | The [original idea](https://github.com/docker/docker/issues/19396) came of [@ndeloof](https://github.com/ndeloof)'s mind. 50 | The logo was provided by [jkneb](https://github.com/jkneb). 51 | -------------------------------------------------------------------------------- /examples/RecetteÀMarcel: -------------------------------------------------------------------------------- 1 | # This is an example RecetteÀMarcel file 2 | DEPUIS debian:latest 3 | CRÉATEUR Thomas Maurice 4 | 5 | LANCE apt-get update && apt-get upgrade -y 6 | LANCE useradd manuel 7 | 8 | UTILISATEUR manuel 9 | 10 | ORDRE echo "La baguette hon hon hon" 11 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brouberol/marcel/ce03fbc9b654f9c838311090eb3090248f0469f2/logo.png -------------------------------------------------------------------------------- /marcel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | Marcel is a french wrapper around the docker CLI, intended as a drop-in 6 | replacement of docker, for the future french sovereign operating system. 7 | """ 8 | 9 | import subprocess 10 | import sys 11 | import re 12 | import os 13 | import six 14 | 15 | from os.path import exists, join 16 | 17 | __version__ = '0.1.0' 18 | 19 | TRANSLATIONS = { 20 | # Commands 21 | u'chauffe': u'run', 22 | u'guillotine': u'exec', 23 | u'pousse': u'push', 24 | u'apporte': u'pull', 25 | u'bûches': u'logs', 26 | u'grève': u'suspend', 27 | u'matuer': u'kill', 28 | u'perquisitionne': u'inspect', 29 | u'bricole': u'build', 30 | u'charge': u'load', 31 | u'plagie': u'copy', 32 | u'france24': u'info', 33 | u'insee': u'stats', 34 | u'rtt': u'pause', 35 | u'sur-ecoute': u'attach', 36 | u'cederoms': u'images', 37 | u'vos-papiers': u'login', 38 | u'déchéance': u'logout', 39 | u'sauvegarde': u'save', 40 | u'graffiti': u'tag', 41 | u'rsa': u'rmi', 42 | u'assigne-à-résidence': u'commit', 43 | u'roman-national': u'history', 44 | u'recycle': u'rm', 45 | u'cherche': u'search', 46 | u'réseau': u'network', 47 | u'marseille': u'port', 48 | u'renomme': u'rename', 49 | u'auboulot': u'unpause', 50 | u'barrage': u'wait', 51 | u'socialistes': u'ps', 52 | # Options 53 | u'--aide': u'--help', 54 | u'--graffiti': u'--tag', 55 | u'--sortie': u'--output', 56 | u'--auteur': u'--author', 57 | u'--49-3': u'--force', 58 | u'--etat-d-urgence': u'--privileged', 59 | u'--disque-numerique-polyvalent': u'--dvd' 60 | } 61 | 62 | MARCELFILE_TRANSLATIONS = { 63 | u'DEPUIS': u'FROM', 64 | u'CRÉATEUR': u'MAINTAINER', 65 | u'LANCE': u'RUN', 66 | u'ORDRE': u'CMD', 67 | u'ÉTIQUETTE': u'LABEL', 68 | u'DÉSIGNER': u'EXPOSE', 69 | u'EELV': u'ENV', 70 | u'AJOUTER': u'ADD', 71 | u'COPIER': u'COPY', 72 | u'POINT D\'ENTRÉE': u'ENTRYPOINT', 73 | u'UTILISATEUR': u'USER', 74 | u'LIEU DE TRAVAIL': u'WORKDIR', 75 | u'BTP': u'ONBUILD', 76 | u'APÉRITIF': u'STOPSIGNAL', 77 | } 78 | 79 | 80 | def output_completion(shell): 81 | if shell == 'bash': 82 | print(""" 83 | function _marcel_complete { 84 | res=(${COMP_LINE:0:$COMP_POINT}) 85 | res=${res[-1]} 86 | ary=(""" + ' '.join(TRANSLATIONS) + """) 87 | for cmd in ${ary[*]} 88 | do if [[ $cmd == $res* && $cmd != $res ]]; then COMPREPLY="$cmd $COMPREPLY"; fi 89 | done 90 | COMPREPLY=($(echo -e "${COMPREPLY}" | sed -e 's/[[:space:]]*$//')) 91 | } 92 | 93 | complete -F _marcel_complete marcel 94 | """) 95 | 96 | 97 | def translate_marcelfile(marcelfile): 98 | """ 99 | Converts a RecetteÀMarcel to a Dockerfile 100 | 101 | :param input_file: Input filename 102 | :param output_file: Output filename 103 | :return: The translated Dockerfile as a string 104 | """ 105 | 106 | for key in MARCELFILE_TRANSLATIONS: 107 | expression = re.compile(r'(^|\n)%s' % key, re.UNICODE) 108 | marcelfile = expression.sub(r"\1%s" % MARCELFILE_TRANSLATIONS[key], marcelfile) 109 | return marcelfile 110 | 111 | 112 | def use_marcelfile(command): 113 | """ 114 | Detect if a RecettesÀMarcel file is present in the current directory. 115 | If so, inject a "-f ./RecettesÀMarcel" argument in the docker build command, 116 | if such an argument was not already passed. 117 | """ 118 | curdir = os.getcwd() 119 | marcelfile_path = join(curdir, u'RecetteÀMarcel') 120 | dockerfile_path = join(curdir, u'.RecetteÀMarcel.Dockerfile') 121 | if not exists(marcelfile_path) or '-f' in command: 122 | return command 123 | 124 | # We want to generate a file with the proper Dockerfile format 125 | with open(marcelfile_path) as marcelfile, open(dockerfile_path, 'w') as dockefile: 126 | marcelfile_content = marcelfile.read() 127 | if six.PY2: # pragma: no cover 128 | marcelfile_content = marcelfile_content.decode('utf-8') 129 | translated_marcelfile = translate_marcelfile(marcelfile_content) 130 | if six.PY2: # pragma: no cover 131 | translated_marcelfile = translated_marcelfile.encode('utf-8') 132 | dockefile.write(translated_marcelfile) 133 | command = command[:2] + ['-f', u'./.RecetteÀMarcel.Dockerfile'] + command[2:] 134 | return command 135 | 136 | 137 | def replace_command(command): 138 | """Replace the executable itself for given values of the first command.""" 139 | if len(command) > 1 and command[1] == 'et-son-orchestre': 140 | command.pop(0) 141 | command[0] = 'docker-compose' 142 | else: 143 | command[0] = 'docker' 144 | return command 145 | 146 | 147 | def translate_command(command): 148 | """Translate the french parts of the command to docker syntax.""" 149 | command = replace_command(command) 150 | return [TRANSLATIONS.get(chunk, chunk) for chunk in command if chunk] 151 | 152 | 153 | def build_command(command): 154 | """Translate the command from marcel syntax to docker.""" 155 | command = translate_command(command) 156 | if len(command) > 1: 157 | subcommand = command[1] 158 | if subcommand == 'build': 159 | command = use_marcelfile(command) 160 | return command 161 | 162 | 163 | def main(): # pragma: no cover 164 | if len(sys.argv) > 2 and sys.argv[1] == "complète": 165 | output_completion(sys.argv[2]) 166 | return 167 | """Run docker commands from marcel syntax.""" 168 | subprocess.call(build_command(sys.argv)) 169 | 170 | 171 | if __name__ == '__main__': # pragma: no cover 172 | main() 173 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r test.txt 2 | tox 3 | -------------------------------------------------------------------------------- /requirements/main.txt: -------------------------------------------------------------------------------- 1 | six 2 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r main.txt 2 | pytest 3 | pytest-cov 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import re 4 | 5 | from os.path import dirname, join, abspath 6 | from setuptools import setup 7 | 8 | VERSION = re.search( 9 | r"__version__\s=\s'(\d\.\d\.\d)'", 10 | open(abspath(join(dirname(__file__), 'marcel.py'))).read() 11 | ).group(1) 12 | 13 | setup( 14 | name="marcel", 15 | version=VERSION, 16 | license='3-clause BSD', 17 | description='Le docker français', 18 | author="Balthazar Rouberol", 19 | author_email="br@imap.cc", 20 | url='http://github.com/brouberol/marcel', 21 | py_modules=['marcel'], 22 | entry_points={'console_scripts': ['marcel=marcel:main']}, 23 | classifiers=[ 24 | 'Environment :: Console', 25 | 'License :: OSI Approved :: BSD License', 26 | 'Operating System :: Unix', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 3', 29 | 'Topic :: Software Development :: Build Tools', 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brouberol/marcel/ce03fbc9b654f9c838311090eb3090248f0469f2/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_translation.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """Test suite of the marcel <--> docker translation""" 4 | 5 | import pytest 6 | import os 7 | import six 8 | 9 | from marcel import ( 10 | translate_command, 11 | replace_command, 12 | translate_marcelfile, 13 | use_marcelfile, 14 | build_command, 15 | ) 16 | 17 | 18 | @pytest.mark.parametrize('command, expected', [ 19 | (['marcel', 'chauffe'], ['docker', 'run']), 20 | (['marcel', 'guillotine'], ['docker', 'exec']), 21 | (['marcel', 'pousse'], ['docker', 'push']), 22 | (['marcel', 'apporte'], ['docker', 'pull']), 23 | (['marcel', u'bûches'], ['docker', 'logs']), 24 | (['marcel', u'grève'], ['docker', 'suspend']), 25 | (['marcel', 'matuer'], ['docker', 'kill']), 26 | (['marcel', 'perquisitionne'], ['docker', 'inspect']), 27 | (['marcel', 'bricole'], ['docker', 'build']), 28 | (['marcel', 'charge'], ['docker', 'load']), 29 | (['marcel', 'plagie'], ['docker', 'copy']), 30 | (['marcel', 'france24'], ['docker', 'info']), 31 | (['marcel', 'insee'], ['docker', 'stats']), 32 | (['marcel', 'rtt'], ['docker', 'pause']), 33 | (['marcel', 'cederoms'], ['docker', 'images']), 34 | (['marcel', 'vos-papiers'], ['docker', 'login']), 35 | (['marcel', u'déchéance'], ['docker', 'logout']), 36 | (['marcel', 'sauvegarde'], ['docker', 'save']), 37 | (['marcel', 'graffiti'], ['docker', 'tag']), 38 | (['marcel', 'rsa'], ['docker', 'rmi']), 39 | (['marcel', u'assigne-à-résidence'], ['docker', 'commit']), 40 | (['marcel', 'roman-national'], ['docker', 'history']), 41 | (['marcel', 'recycle'], ['docker', 'rm']), 42 | (['marcel', '--aide'], ['docker', '--help']), 43 | (['marcel', 'sauvegarde', '--sortie'], ['docker', 'save', '--output']), 44 | (['marcel', 'bricole', '--graffiti'], ['docker', 'build', '--tag']), 45 | (['marcel', 'recycle', '--49-3'], ['docker', 'rm', '--force']), 46 | (['marcel', 'cherche'], ['docker', 'search']), 47 | (['marcel', u'réseau'], ['docker', 'network']), 48 | (['marcel', 'marseille'], ['docker', 'port']), 49 | (['marcel', 'renomme'], ['docker', 'rename']), 50 | (['marcel', 'auboulot'], ['docker', 'unpause']), 51 | ]) 52 | def test_translate_command(command, expected): 53 | """Check the marcel --> docker command translation.""" 54 | assert translate_command(command) == expected 55 | 56 | 57 | @pytest.mark.parametrize('command, expected', [ 58 | (['marcel', 'pousse'], ['docker', 'pousse']), 59 | (['marcel', 'et-son-orchestre', 'up'], ['docker-compose', 'up']), 60 | ]) 61 | def test_replace_command(command, expected): 62 | """Check the logic behing the command replacement.""" 63 | assert replace_command(command) == expected 64 | 65 | 66 | def test_translate_marcefile(): 67 | """Test the RecetteÀMarcel --> Dockerfile translation.""" 68 | marcelfile_content = u""" 69 | DEPUIS debian:latest 70 | CRÉATEUR Thomas Maurice 71 | 72 | LANCE apt-get update && apt-get upgrade -y 73 | LANCE useradd manuel 74 | 75 | BTP echo "Coucou" 76 | 77 | UTILISATEUR manuel 78 | 79 | APÉRITIF SIGSTOP 80 | 81 | LIEU DE TRAVAIL /app 82 | 83 | ORDRE echo "La baguette hon hon hon" 84 | """ 85 | expected = u""" 86 | FROM debian:latest 87 | MAINTAINER Thomas Maurice 88 | 89 | RUN apt-get update && apt-get upgrade -y 90 | RUN useradd manuel 91 | 92 | ONBUILD echo "Coucou" 93 | 94 | USER manuel 95 | 96 | STOPSIGNAL SIGSTOP 97 | 98 | WORKDIR /app 99 | 100 | CMD echo "La baguette hon hon hon" 101 | """ 102 | assert translate_marcelfile(marcelfile_content) == expected 103 | 104 | 105 | def test_use_marcelfile(tmpdir): 106 | marcelfile_content = u""" 107 | DEPUIS debian:latest 108 | CRÉATEUR Thomas Maurice 109 | 110 | LANCE apt-get update && apt-get upgrade -y 111 | LANCE useradd manuel 112 | 113 | UTILISATEUR manuel 114 | 115 | ORDRE echo "La baguette hon hon hon" 116 | """ 117 | os.chdir(str(tmpdir)) 118 | with open(u'RecetteÀMarcel', 'w') as marcelfile: 119 | if six.PY2: 120 | marcelfile_content = marcelfile_content.encode('utf-8') 121 | marcelfile.write(marcelfile_content) 122 | command = ['marcel', 'bricole'] 123 | command = use_marcelfile(command) 124 | assert command == ['marcel', 'bricole', '-f', u'./.RecetteÀMarcel.Dockerfile'] 125 | 126 | 127 | def test_use_marcelfile_with_provided_marcefile(): 128 | command = ['marcel', 'bricole', '-f', 'Dockerfile'] 129 | assert use_marcelfile(command) == command 130 | 131 | 132 | def test_build_command(tmpdir): 133 | os.chdir(str(tmpdir)) 134 | assert build_command(['marcel', 'bricole']) == ['docker', 'build'] 135 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py34,py35 3 | skip_missing_interpreters = True 4 | 5 | [testenv] 6 | passenv = TRAVIS 7 | commands = 8 | py.test 9 | deps = -rrequirements/test.txt 10 | install_command = pip install {packages} --------------------------------------------------------------------------------