Beaucoup de frameworks viennent avec des templates de projets maintenant. Django vient avec django-admin startproject --template
, tandis que crossbar vient avec crossbar init --template
. L’idée d’avoir de templates pour ses projets n’a rien de nouveau, d’ailleurs la plupart des OS modernes ont au moins ça intégré pour des fichiers. Ainsi Ubuntu à un dossière “Modèles”, et tout ce qu’il y a dedans peut être dupliqué ailleurs via un clic droit dans Nautilus.
Néanmoins la meilleure solution à ce jour vient de Audrey Roy, qui est avant tout connue pour avoir co-écrit le livre Two scoops of Django, et qui a aussi pondu cookiecutter.
L’outil est très simple. D’abord un pip pour l’installer :
pip install cookiecutter
Notez que bien que le projet soit en Python, les templates peuvent être en n’importe quel langage, cookiecutter s’en branle. Lui, il va juste copier le contenu du template dans un dossier, et remplacer les variables notées {{}}
dedans.
Pour commencer, on créé un dossier nommé {{cookiecutter.repo_name}}
qui va contenir le template de son projet. Oui, le nom du dossier est {{cookiecutter.repo_name}}
. En effet les noms de dossiers et fichiers sont aussi templatisables, et passés à la moulinette de jinja2.
Dans ce dossier, on peut mettre tous les fichiers qu’on veut, avec des noms normaux, ou avec des variables. Le contenu des fichiers peut, bien entendu, contenir aussi des variables. Les variables sont toujours de la forme {{cookiecutter.nom_de_variable}}
.
Ensuite, à côté de notre dossier nommé {{cookiecutter.repo_name}}
(le dossier du template), on peut créer un fichier cookiecutter.json
qui va contenir les valeurs par défaut de nos variables :
{ "repo_name": "nouveau_projet", "version": "0.1.0", "autre_variable": "Bidule" } |
Enfin pour récolter les fruits de son labeur, on lance cookiecutter /chemin/vers/template
et l’outil va vous poser tout un tas de question pour remplir les variables, puis va générer le nouveau projet dans le dossier courant.
Cookiecutter accepte aussi des urls de repo git et mercurial comme source de template, et l’auteur de l’outil fournit un template de projet Python très complet par ce biais.
On va l’utiliser comme exemple.
Voici son contenu :
├── cookiecutter.json <= valeur par défaut des variables ├── {{cookiecutter.repo_name}} <= template du projet │ ├── AUTHORS.rst │ ├── CONTRIBUTING.rst │ ├── {{cookiecutter.repo_name}} │ │ ├── {{cookiecutter.repo_name}}.py │ │ └── __init__.py │ ├── docs │ │ ├── authors.rst │ │ ├── conf.py │ │ ├── contributing.rst │ │ ├── history.rst │ │ ├── index.rst │ │ ├── installation.rst │ │ ├── make.bat │ │ ├── Makefile │ │ ├── readme.rst │ │ └── usage.rst │ ├── HISTORY.rst │ ├── LICENSE │ ├── Makefile │ ├── MANIFEST.in │ ├── README.rst │ ├── requirements.txt │ ├── setup.cfg │ ├── setup.py │ ├── tests │ │ ├── __init__.py │ │ └── test_{{cookiecutter.repo_name}}.py │ └── tox.ini └── README.rst
Certains usages de variables sont assez évident, comme par exemple le fichier __init__:
# -*- coding: utf-8 -*- __author__ = '{{ cookiecutter.full_name }}' __email__ = '{{ cookiecutter.email }}' __version__ = '{{ cookiecutter.version }}' |
D'autres sont assez malines et inattendues comme le fichier de test :
#!/usr/bin/env python # -*- coding: utf-8 -*- """ test_{{ cookiecutter.repo_name }} ---------------------------------- Tests for `{{ cookiecutter.repo_name }}` module. """ import unittest from {{ cookiecutter.repo_name }} import {{ cookiecutter.repo_name }} class Test{{ cookiecutter.repo_name|capitalize }}(unittest.TestCase): def setUp(self): pass def test_something(self): pass def tearDown(self): pass if __name__ == '__main__': unittest.main() |
On note l'usage du filtre jinja capitalize
. Tous les outils de jinja sont à disposition, ce serait con de s'en priver.
Pour utiliser le template on fait $ cookiecutter https://github.com/audreyr/cookiecutter-pypackage.git
, ce qui nous donne :
Clonage dans 'cookiecutter-pypackage'... remote: Counting objects: 505, done. remote: Total 505 (delta 0), reused 0 (delta 0), pack-reused 505 Réception d'objets: 100% (505/505), 77.89 KiB | 0 bytes/s, done. Résolution des deltas: 100% (265/265), done. Vérification de la connectivité... fait. full_name (default is "Audrey Roy")? Sam email (default is "audreyr@gmail.com")? lesametlemax@gmail.com github_username (default is "audreyr")? sam project_name (default is "Python Boilerplate")? essai repo_name (default is "boilerplate")? essai project_short_description (default is "Python Boilerplate contains all the boilerplate you need to create a Python package.")? C'est un essai j'ai dis release_date (default is "2015-01-11")? year (default is "2015")? version (default is "0.1.0")?
Notez les valeurs par défaut du fichier JSON, qui sont celles de l'auteur.
Le résultat généré :
├── essai │ ├── AUTHORS.rst │ ├── CONTRIBUTING.rst │ ├── docs │ │ ├── authors.rst │ │ ├── conf.py │ │ ├── contributing.rst │ │ ├── history.rst │ │ ├── index.rst │ │ ├── installation.rst │ │ ├── make.bat │ │ ├── Makefile │ │ ├── readme.rst │ │ └── usage.rst │ ├── essai │ │ ├── essai.py │ │ └── __init__.py │ ├── HISTORY.rst │ ├── LICENSE │ ├── Makefile │ ├── MANIFEST.in │ ├── README.rst │ ├── requirements.txt │ ├── setup.cfg │ ├── setup.py │ ├── tests │ │ ├── __init__.py │ │ └── test_essai.py │ └── tox.ini └── README.rst
Le fichier __init__ est devenu :
# -*- coding: utf-8 -*- __author__ = 'Sam' __email__ = 'lesametlemax@gmail.com' __version__ = '0.1.0' |
Et le fichier de test :
#!/usr/bin/env python # -*- coding: utf-8 -*- """ test_essai ---------------------------------- Tests for `essai` module. """ import unittest from essai import essai class TestEssai(unittest.TestCase): def setUp(self): pass def test_something(self): pass def tearDown(self): pass if __name__ == '__main__': unittest.main() |
L'outil accepte un beau degré de customisation, avec un fichier de configuration général au niveau de l'utilisateur (et donc des valeurs par défaut par utilisateur), une API programmable en Python, des hooks pre et post generation (qui permettent d'injecter des variables dynamiquement comme par exemple un uuid ou un timestamp).
Donc si vous recréez souvent les mêmes layout de projet encore et encore, pensez à cookiecutter.
Mais surtout, surtout, si vous codez votre propre framework, ne faites pas comme Django, utilisez l'API de cookiecutter au lieu de réinventer la roue.
Merci pour le partage de cet outil.
J’ai un script python perso qui s’occupe de générer des templates de code python, avec Jinja, qui me fait l’init de git et qui me crée le projet pour sublime text (avec les variables pour les linters et autocomplete par projet). Sauf que quand il faut le distribuer entre les machines, c’est un peu relou. Et j’ai eu la flemme de le packager correctement et du coup, je ne m’en sert quasiment pas, hormis la fois où je l’ai écris.
Avoir une solution un peu plus propre et universel devrait me permettre de l’utiliser (!).
Je vais jeter un oeil, et je pense qu’avec le système de hook, il doit être faisable de faire ce que je faisais pour git et sublime.
Merci pour le partage de cet outil.
J’ai un script python perso qui s’occupe de générer des templates de code python, avec Jinja, qui me fait l’init de git et qui me crée le projet pour sublime text (avec les variables pour les linters et autocomplete par projet). Sauf que quand il faut le distribuer entre les machines, c’est un peu relou. Et j’ai eu la flemme de le packager correctement et du coup, je ne m’en sert quasiment pas, hormis la fois où je l’ai écris.
Avoir une solution un peu plus propre et universel devrait me permettre de l’utiliser (!).
Je vais jeter un oeil, et je pense qu’avec le système de hook, il doit être faisable de faire ce que je faisais pour git et sublime.
@Kikoololmdr : ton histoire d’initialisation pour sublim text me plait. Tu peux développer un peu plus ?
2scoops of Django pour 1.8 est un bouquin fantastique. En dehors de la bible que ce devrait être pour tout bon dév Django, il démontre page après page qu’un bon dev est encore meilleur si elle/il est créatif.
Vraiment un chouette outil, merci !
“L’idée d’avoir de templates pour ses projets n’a rien de nouveaux, ”
@Digaboy
J’ai oublié de préciser que c’est appelé depuis les hooks de virtualenvwrapper (d’où les appels à env dans ce qui suit)
Le bout de code qui gère ça :
Et le code en entier est là (il manque les templates, mais le script se suffit à lui-même pour la lecture rapide)
Et j’en profite pour m’excuser pour le double commentaire, j’ai foiré le click ce matin (oui, c’est possible).
Cookiecutter est très puissant, on s’en sert pour créer des daemons à la chaine: https://github.com/Eyepea/cookiecutter-API-Hour
Par contre, j’ai eu du retour de personnes qui m’ont dit que c’était trop compliqué ou qu’ils ne voulaient pas l’utiliser.
Pour finir, j’ai dû faire un simple exemple dans la documentation: http://pythonhosted.org/api_hour/tutorials/all_in_one.html
Comme quoi, même pour un “bête” outil comme cookiecutter qui est utilisé en mode one shot, la résistance au changement du Pythoniste moyen est quand même assez forte ;-)
Merci, je m’en sers maintenant abusivement pour créer différents template latex, cookiecutter est bien pensé et simple d’utilisation.
Je n’ai par contre pas réussi à mettre une variable à None ou false pour appliquer {% if cookiecutter.variable %} dans le template.
Ce n’est pas une utilisation abuse, au contraire, c’est parfait pour ça. Ou pour générer du xml également plutôt que de builder tout ça avec un gros arbre dom. Met une chaine vide dans le json.