Erweiterbare Skelette für Vim mit Python, Click und Jinja2

Problem statement As a member of the software team at Datawise, Inc., I want my part of code to be identifiable. Not just due to vanity, but also in order to be able to clearly identify the responsible when some part of our system fails.

For a long time, I believed that git is a perfect solution to this problem, as while being almost seemless, it allows one to track the authorship line-wise, which is more than enough.

However, something happened recently, which made rethinking this belief. My coworker moved the folder containing my part of code during some routine refactoring. As a result, git changed the authorship of every single line of my code, so my coworker became the "author".

I decided that while git is good most of the time, it would not be bad to supplement it with something simple and crude, so to distinguish my code more clearly and robustly. Inspired by vim-perl plugin for Perl in Vim, I decided that from now on, every single Python file I create, would start with the following header:

"""===============================================================================

        FILE: {filename}.py

       USAGE: ./{filename}.py

 DESCRIPTION: 

     OPTIONS: ---
REQUIREMENTS: ---
        BUGS: ---
       NOTES: ---
      AUTHOR: Alex Leontiev ({email})
ORGANIZATION: 
     VERSION: ---
     CREATED: 2020-11-13T15:54:47.995590
    REVISION: ---

==============================================================================="""

Main work

Part 1: Python Script

It was not very difficult. I put together the following simple script (you can find it at https://github.com/nailbiter/for/blob/master/forpython/new_file.py):

#!/usr/bin/env python3
from os.path import realpath, split, basename, join, splitext
import os.path
from os import access, X_OK, walk
from jinja2 import Template
import click
from datetime import datetime


def _get_template_dirname():
    dirname, _ = split(realpath(__file__))
    return join(dirname, "_new_file")


def _get_template_names():
    _, _, fns = next(walk(_get_template_dirname()))
    _TEMPLATE_EXT = ".jinja.py"
    return [fn[:-len(_TEMPLATE_EXT)] for fn in fns if fn.endswith(_TEMPLATE_EXT)]


def _render_template(fn, **kwargs):
    with open(join(_get_template_dirname(), fn)) as f:
        return Template(f.read()).render({
            **kwargs,
            "os": {"path": os.path},
            "converters": {
                "snake_to_camel": lambda s: "".join([s_.capitalize() for s_ in s.split("_")]),
            }})


@click.command()
@click.argument("fn", type=click.Path())
@click.option("-s", "--stdout", is_flag=True)
@click.option("-e", "--email", envvar="EMAIL", default="***@gmail.com")
@click.option("-o", "--organization", envvar="ORGANIZATION", default="")
@click.argument("archetype", type=click.Choice(_get_template_names()), default="default")
def new_file(fn, email, organization, archetype, stdout=False):
    s = _render_template(f"{archetype}.jinja.py",
                         filename=fn,
                         now=datetime.now(),
                         email=email,
                         is_executable=access(fn, X_OK),
                         organization=organization
                         )
    if stdout:
        print(s)
    else:
        with open(fn, "w") as f:
            f.write(s)


if __name__ == "__main__":
    new_file()

The script is basically used as

./new_file.py filename.py [archetype]

where optional archetype (hi, maven) is the name of any of the predefined templates:

  1. default (which is the default)
  2. click (the template for a click script)
  3. class (the template for a Python file containing a definition of a single class)
  4. test (the template for unittest)

When being called as above, the script takes the corresponding template, performs the necessary substititions, and writes it to filename.py. The additional flag -s/--stdout overwrites this behaviour, instead writing the rendered template to stdout. Also, -e and -o flags and corresponding EMAIL and ORGANIZATION environment variables are used to insert one's email and organization to the header. Incidentally, I found that it is useful to use direnv to change these environment variables based on the directory where source code is located, so to distinguish my own codes and the ones I do during my worktime.

Part 2: Hooking it into Vim Finally, I need to somehow hook in into my Vim, so that ideally this script gets run every time I open the new file. Unfortunately, I did not find a way to do this prettily. Moreover, now that script supports multiple archetypes, it is in principle impossible to decide which one to use at the time of file creation.

Therefore, I added the following to my .vimrc:

...
:au BufNewFile *.py 0r ~/.vim/skeletons/skeleton.py
...

where file skeleton.py is defined as follows:

(maybe, set `chmod +x` and) run `Init`!

Now, every time I create the new .py file, it greets me with the message above, reminding me to run Init <archetype> Vim command, which is defined in my python.vim as

command! -nargs=? Init execute "!~/for/forpython/new_file.py % <args>"

so that it runs the script we made in Part 1.

Future work

  1. if anyone knows how to hook this or similar script into Emacs, I would be happy to hear
  2. maybe, there is some way to hook the script into my Vim more seamlessly?

Recommended Posts

Erweiterbare Skelette für Vim mit Python, Click und Jinja2
Grundeinstellungen für die Verwendung von Python3.8 und pip unter CentOS8
Durchsuchen von Pixiv-Tags und Speichern von Illustrationen mit Python
[Python] Zugreifen auf und Zuschneiden von Bildpixeln mit OpenCV (für Anfänger)
Authentifizierung mit Tweepy-User-Authentifizierung und Anwendungsauthentifizierung (Python)
[TouchDesigner] Tipps für die Anweisung von Python
Clustering und Visualisierung mit Python und CytoScape
[Python] Grund für das Überschreiben mit super ()
[Python] Neunundneunzig Tabellen, die for-Anweisungen verwenden
Speichern, Wiederherstellen und Abfragen der Suche von Python-Klasseninstanzen mit mongodb
Erstellen eines Markierungsblatts mit Python OpenCV (Tipps zum guten Lesen)
Hinweise zur Verwendung von OpenCV mit Windows 10 Python 3.8.3.
Hinweise zur Verwendung von cChardet und python3-chardet in Python 3.3.1.
Von Python bis zur Verwendung von MeCab (und CaboCha)
Python-Entwicklungsumgebung für macOS mit venv 2016
[50 zählt] Schlüsselübertragung mit Python für Windows
Verwenden von Python und MeCab mit Azure Databricks
[Python, Multiprocessing] Verhalten für Ausnahmen bei Verwendung von Multiprocessing
6 Python-Bibliotheken für schnellere Entwicklung und Debugging
Tipps zur Verwendung von Python + Caffe mit TSUBAME
Implementierung und Beschreibung mit XGBoost für Anfänger
Hinweise zur Verwendung von Python (Pydev) mit Eclipse
Ich verwende Tox und Python 3.3 mit Travis-CI
SublimeText2 und SublimeLinter - Syntax prüfen auf Python3--
Python Jinja2
Effektiver Python-Hinweis Punkt 12 Vermeiden Sie die Verwendung von else-Blöcken nach for- und while-Schleifen
Sofortige Methodengrammatik für Python und Ruby (Lernen)
Schätzung der Kopforientierung mit Python und OpenCV + dlib
vprof - Ich habe versucht, den Profiler für Python zu verwenden
Ich habe versucht, Web-Scraping mit Python und Selen
[Vim] [Python] Fehler bei Jedi-Vim- und Multibyte-Charakteren?
Kausales Denken und kausale Suche von Python (für Anfänger)
(Windows) Ursachen und Problemumgehungen für UnicodeEncodeError in Python 3
Hinweise zur Installation von Python3 und zur Verwendung von pip unter Windows7
Entwickeln und Bereitstellen von Python-APIs mit Kubernetes und Docker
Python-Entwicklungsablauf mit Poetry, Git und Docker
Ich habe versucht, Objekte mit Python und OpenCV zu erkennen
Python-Pandas: Suchen Sie mit regulären Ausdrücken nach DataFrame
Mit dem Evernote SDK für Python 3 erhalten Sie Informationen zu Notizen
Erstellen Sie eine Webmap mit Python und GDAL
Verbesserte Suche nach Pokemon-Rennwerten mit Python
[Hikari-Python] Kapitel 09-02 Klassen (Erstellen und Instanziieren von Klassen)
[Python / Chrome] Grundeinstellungen und Operationen zum Scraping
[Python3] Automatische Texterzeugung mit janome und markovify
PDF-Dateien und Websites zum Erlernen von Python 3
Versuchen Sie es mit Tensorflow. ① Erstellen Sie eine Python-Umgebung und führen Sie Tensorflow ein
Lassen Sie uns mit SWIG ein Modul für Python erstellen
Generierung der Vorlagen-Netzwerkkonfiguration mit Python und Jinja2
Versuchen Sie, die ChatWork-API und die Qiita-API in Python zu verwenden
Installieren Sie Python und Bibliotheken für Python unter MacOS Catalina
Installieren Sie PyCall auf Raspberry PI und versuchen Sie, die GPIO-Bibliothek für Python von Ruby zu verwenden