Squelettes extensibles pour Vim utilisant Python, Click et 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

Squelettes extensibles pour Vim utilisant Python, Click et Jinja2
Paramètres initiaux pour l'utilisation de Python3.8 et pip sur CentOS8
Recherche de balises pixiv et enregistrement d'illustrations à l'aide de Python
[Python] Accès et recadrage des pixels d'image à l'aide d'OpenCV (pour les débutants)
Authentification à l'aide de l'authentification des utilisateurs tweepy et de l'authentification d'application (Python)
[TouchDesigner] Conseils pour la déclaration par python
Clustering et visualisation à l'aide de Python et CytoScape
[Python] Raison du remplacement à l'aide de super ()
[Python] Quatre-vingt-dix-neuf tables utilisant des instructions for
Vider, restaurer et rechercher des requêtes d'instances de classe Python à l'aide de mongodb
Créer une lecture de feuille de notes avec Python OpenCV (Conseils pour bien lire)
Remarques sur l'utilisation d'OpenCV avec Windows10 Python 3.8.3.
Notes utilisant cChardet et python3-chardet dans Python 3.3.1.
De Python à l'utilisation de MeCab (et CaboCha)
Environnement de développement Python pour macOS utilisant venv 2016
[50 comptes] Transmission de clé à l'aide de Python pour Windows
Utilisation de Python et MeCab avec Azure Databricks
[python, multitraitement] Comportement des exceptions lors de l'utilisation du multitraitement
6 bibliothèques Python pour un développement et un débogage plus rapides
Conseils pour utiliser python + caffe avec TSUBAME
Implémentation et description à l'aide de XGBoost pour les débutants
Remarques sur l'utilisation de python (pydev) avec eclipse
J'utilise tox et Python 3.3 avec Travis-CI
SublimeText2 et SublimeLinter - Vérification de la syntaxe pour Python3 -
Python Jinja2
Élément de note Python efficace 12 Évitez d'utiliser des blocs else après les boucles for et while
Grammaire de méthode instantanée pour Python et Ruby (étude)
Estimation de l'orientation de la tête avec Python et OpenCV + dlib
vprof - J'ai essayé d'utiliser le profileur pour Python
J'ai essayé le web scraping en utilisant python et sélénium
[Vim] [Python] Des bugs autour des caractères jedi-vim et multi-octets?
Raisonnement causal et recherche causale par Python (pour les débutants)
(Windows) Causes et solutions de contournement pour UnicodeEncodeError dans Python 3
Remarques sur l'installation de Python3 et l'utilisation de pip sous Windows7
Développer et déployer des API Python à l'aide de Kubernetes et Docker
Flux de développement Python avec Poetry, Git et Docker
J'ai essayé la détection d'objets en utilisant Python et OpenCV
Pandas Python: recherchez DataFrame à l'aide d'expressions régulières
Obtenir des informations sur les notes à l'aide du SDK Evernote pour Python 3
Créer une carte Web en utilisant Python et GDAL
Recherche affinée des valeurs de race Pokemon à l'aide de Python
[Hikari-Python] Chapitre 09-02 Classes (Création et instanciation de classes)
[Python / Chrome] Paramètres de base et opérations de scraping
[Python3] Génération automatique de texte avec janome et markovify
Fichiers PDF et sites utiles pour apprendre Python 3
Essayez d'utiliser tensorflow ① Créez un environnement python et introduisez tensorflow
Faisons un module pour Python en utilisant SWIG
Génération de configuration de réseau de modèles avec Python et Jinja2
Essayez d'utiliser l'API ChatWork et l'API Qiita en Python
Installer Python et les bibliothèques pour Python sur MacOS Catalina
Installez PyCall sur Raspberry PI et essayez d'utiliser la bibliothèque GPIO pour Python de Ruby