Projekt-Vorlagen mit cookiecutter


Veröffentlicht:   |   Weitere Einträge über entwicklung   |  

cookiecutter ("Ausstechförmchen") erzeugt aus Vorlagen beliebige Projektgrundstrukturen und kann dabei vordefinierte Werte einsetzen sowie fehlende erfragen.

Ziel von cookiecutter und anderen sogl. Scaffolding-Werkzeugen ist, beim Erstellen neuer Projekte nur noch die nötigsten Schritte per Hand durchführen zu müssen.

Existiert eine passende Vorlage, kann mit

# cookiecutter <Name der Vorlage>

im aktuellen Verzeichnis ein Projekt angelegt werden. Die benötigten Vorlagen können dabei lokal und auch auf GitHub liegen. Ein Beispiel aus der Projektdokumentation:

# cookiecutter https://github.com/audreyr/cookiecutter-pypackage.git

Die von GitHub bezogene Vorlage wird zusätzlich im Verzeichnis ~/.cookiecutters/ abgelegt und kann dann später wie eine lokale Vorlage direkt verwendet werden:

# cookiecutter ~/.cookiecutter/cookiecutter-pypackage

Angaben, die unabhängig von den Vorlagen immer gleich bleiben, wie bspw. der Name des Entwicklers oder die E-Mail-Adresse, lassen sich in der Datei ~/.cookiecutterrc speichern:

default_context:
  full_name: "fuller"
  email: "fuller@daemogorgon.net"
  github_username: "fuller"
cookiecutters_dir: "/home/.../my-custom-cookiecutters-dir/"

Im Abschnitt default_context können beliebige Schlüssel-Wert-Paare stehen. Sollen die Vorlagen woanders als im Standardverzeichnis liegen, definiert man dies mit cookiecutters_dir.

Eigene Vorlagen

Grundlegende Struktur

Als Basis für eine eigene Vorlage kann entweder eine vorhandene Vorlage mit passender Ausrichtung dienen oder man entwickelt eine eigene Vorlage auf Basis folgende Struktur:

meine-vorlage/
├── {{ cookiecutter.repo_name }}/
│   └── ...
└── cookiecutter.json

Verzeichnis- und Dateinamen und Dateiinhalte der Form {{ cookiecutter.<Parameter> }} werden durch cookiecutter ersetzt. Die Datei cookiecutter.json enthält Angaben zu Parametern und voreingestellten Werten:

{
  "full_name": "John Doe",
  "email": "jdoe@mail.com",
  "github_username": "johnd",
  "project_name": "My New Project",
  "repo_name": "my_project",
  "project_short_description": "My project ...",
  "release_date": "2013-12-11",
  "year": "2013",
  "version": "0.1"
}

Parameter

Die Parameter sind Vorlage-spezifisch, Werte können aber in ~/.cookiecutterrc bereits vorbelegt werden:

full_name:
Name des Entwicklers
email:
E-Mail-Adresse
github_username:
Login für GitHub
project_name:
offizieller Projektname
repo_name:
Name des Reositories
project_short_description:
Kurzbeschreibung des Projekts
release_date:
Releasedatum
year:
Jahr
version:
Version

Die Template-Sprache Jinja2

Mit Jinja2 können Vorlagen verschiedenster Art und Kompliziertheit erstellt werden. Dank seiner Python-ähnlichen Sprache ist die Erstellung einfach und schnell möglich.

Struktur

Jinja nutzt zwei grundlegende Strukturen. Mit

{% for item in list %}
  ...
{% endfor %}

werden Kontrollstrukturen abgebildet wie Schleifen oder Konditionalbedingungen. Mit

{{ item.href }}

werden Variablen verarbeitet, d.h. ihr Wert wird bei der Verarbeitung der Vorlage eingesetzt.

Variablen

Auf Attribute von Variablen kann auf zweierlei Art zugegriffen werden:

  • item.href
  • item['href']

Variablen erhalten Werte mit set:

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

Whitespace-Kontrolle

Normalerweise werden durch den Ersetzungsprozess an die Stelle von Statements wie {% endblock %} Leerzeilen gesetzt. Das führt normalerweise zu einem unerwünscht "luftigen" Ergebnis. Seit Version 2.2 werden abschließende Newlines automatisch entfernt.

Fügt man ein Minus-Zeichen an den Anfangs- oder Endblock, so werden Whitespaces vor und nach dem Block entfernt:

{% for item in seq -%}
  {{ item }}
{%- endfor %}

Filter

Neben den üblichen Kontrollstrukturen besitzt Jinja einen umfangreichen Fundus an sog. Filtern. Diese können mit Hilfe des Pipe-Symbols | verkettet werden. Ein Beispiel:

{{ name|striptags|title }}}

entfernt aus dem Wert der Variable name alle HTML-Tags und konvertiert die Wortanfänge in Grobuchstaben.

Kommentare

Kommentare werden mit # erstellt:

{# ...
  ...
#}

Escaping

Natürlich können auch Jinja-Vorlagen mit Hilfe von Jinja erstellt werden. Dazu ist allerdings ein Escaping der geschweiften Klammern Notwendig:

{{ '{{' }}

Template-Vererbung

Jinja ermöglicht die Entwicklung eines Basis-Templates, dass von anderen Templates geerbt werden kann. Im Basis-Template werden sog. block s definiert, die von den Erben ersetzt werden.

Basis-Template base.html:

...
{% block head %}
...
{% endblock %}

Erbendes Template:

{% extends "base.html" %}
...
{% block head %}
  {{ super() }}
  ...
{% endblock %}
...

Im Basis-Template wird hier ein allgemeines Kopfelement definiert, dass dann vom Kind verwendet wird. Mittels super() wird dabei der ursprüngliche Block übernommen und anschließend ergänzt.

Soll ein schon definierter Block weiter unten im Template erneut zum Einsatz kommen, lässt sich mit self bewerkstelligen:

<title>{%  block title %}{% endblock %}</title>
<h1>{{  self.title() }}</h1>
{%  block body %}{% endblock %}

Blöcke können beliebig veschachtelt werden; zur Verbesserung der Lesbarkeit erlaubt Jinja die Nennung des Blocknamens im endblock-Element. Allerdings sind "äußere" Variablen in einem geschachtelten Block per se nicht verfügbar, es sei denn, sie sind explizit dafür vorbereitet:

{% for item in seq %}
  <li>{% block loop_item %}{{ item }}{% endblock %}</li>
{% endfor %}

Hier ist item im Block loop_item nicht bekannt, da ein Kind-Template diesen Block überschreiben könnte. Mit scope (ab Version 2.2) ist item trotzdem verfügbar:

{% for item in seq %}
  <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}

HTML-Escaping

Wenn ein HTML-Template erstellt werden soll, besteht das Risiko, dass Variablen Werte wie < oder & enthalten und damit syntaktisch relevant sind für den Code. Jinja bietet dazu den Filter e an:

{{ user.username|e }}

Makros

Makros sind vergleichbar mit selbst definierten Funktionen.

{% macro input(name, value='', type='text', size=20) -%}
  <input type="{{ type }}" name="{{ name }}" value="{{
      value|e }}" size="{{ size }}">
{%- endmacro %}

Aufrufen lässt sich ein Makro so:

<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>

Befindet sich die Makrodefinition in einer anderen Templatedatei, muss diese vorher importiert werden.

Testen eines Templates

Jinja2 bietet nur eine API an, kein Werkzeug, um ein Template zu testen. Die API ist glücklicherweise so einfach, dass man schnell ein solches Werkzeug zusammenstellen kann.

Sämtliche Vorlagen müssen hierbei im Verzeichnis templates liegen.

# generate.py

from ast import literal_eval
import sys
from jinja2 import Environment, FileSystemLoader

template_dir = "templates"
jinja = Environment(loader=FileSystemLoader(template_dir))


def render(template_filename, args):
  tpl = jinja.get_template(template_filename)
  content = tpl.render(args)

  return content


def help():
    print("Usage: {0} <template filename> <output|-> <dict>".format(sys.argv[0]), file=sys.stderr)
    print("e.g. /usr/bin/python3 generate.py tmp.tmp - '{\"a\":1, \"bla\": \"blub\"}'", file=sys.stderr)
    print("You *must* use the single quotes, otherwise your shell will try to interpret some characters!", file=sys.stderr)

if __name__ == "__main__":
    if len(sys.argv) != 4:
        help()
        sys.exit(1)

    template_filename = sys.argv[1]
    output = sys.argv[2]

    args = sys.argv[3]
    kwargs = literal_eval(args)

    if template_filename == "":
        print("You must give a template filename as first argument", file=sys.stderr)
        sys.exit(1)

    rendered_content = render(template_filename, args)

    if output == "-":
        output_handle = sys.stdout
        output_handle.write(content)
    else:
        with open(output, 'w') as output_handle:
            output_handle.write(content)

Beispiele

Der Autor von cookiecutter hat auf GitHub eine Beispiel-Vorlage für Python-Projekte veröffentlicht. Auf der cookiecutter-Projektseite befindet sich eine Liste mit Vorlagen für verschiedenste Zwecke.

Inhalte © 2014 - fuller - Impressum - Haftungsausschluss - Datenschutz

Creative Commons Lizenzvertrag
daemogorgon.net von fuller ist lizenziert unter einer Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Unported Lizenz.