fr en

Un shell pour pelican

Posted on 2015-01-09 in Programmation

Comme je l'ai déjà dit ici, mon blog est géré par Pelican. Il est également placé sous gestion de version. Pour régénérer automatiquement après chaque modification la version html des pages, il faut utiliser la commande make regenerate. Pour lancer le serveur de test pour voir les pages, on utilise make serve. Enfin, pour synchroniser le contenu sur le serveur, j'utilise make rsync_upload. J'en profite pour pousser les modifications de mon dépôt local vers mon serveur et vers Bitbucket pour en avoir une copie de sauvegarde.

Jusqu'à présent, je gérais tout ça à la main, dans un terminal. J'avais déjà écrit une petite fonction bash pour me déplacer dans le bon dossier et activer le venv python. Mais ce n'était pas très pratique : les commandes make serve et make regenerate doivent tourner en permanence en tâche de fond, il faut pusher sur deux dépôts différents et synchroniser les pages sur le serveur. J'ai donc décider de me créer un script pour améliorer tout ça.

Comme j'ai besoin de lancer plusieurs commandes bien définies dans un même dossier, sans sortir de l'environnement virtuel et que je veux pouvoir interagir facilement avec les processus en tâche de fond, j'ai décidé de créer un mini-shell. Je dois admettre que j'en suis très satisfait.

Quelques explications sur le script :

  • Tout est dans une boucle infinie pour imiter un vrai shell. echo -ne "> " permet d'afficher un prompt et de laisser le curseur sur cette ligne.
  • exec 3< <(make serve)
    • La syntaxe <(make serve) est remplacée par chemin vers un fichier virtuel qui est connecté à la sortie standard de la commande située entre les parenthèses. Ici make serve qui démarre le serveur.
    • Comme on veut que le processus soit en background, on lance la commande avec exec.
    • Enfin, comme on veut pouvoir récupérer facilement la sortie de la commande, on envoie le contenu du fichier vers une nouvelle sortie 3. On lit son contenu avec cat <&3. Tant que le fichier n'est pas fermé, cette commande ne se retourne pas.
    • Voir cette réponse sur stackoverflow pour plus de détails.
  • Sorties additionnelles : j'ai tenté de stocker le numéro des sorties dans des variables. Le code est plus clair et cela permet de le factoriser. Malheureusement, exec "${var}"< <(make serve) provoque une erreur. Idem pour cat <&"${var}".
  • [ -n "${serve_pid}" ] && has_died "${serve_pid}" : la fonction has_died ne doit pas être entre crochets (donc passée en argument à la fonction test) pour que le script fonctionne correctement.
  • echo ${pid} : on ne peut pas renvoyer autre chose que des codes d'erreur avec return. Pour tout le reste, il faut utiliser echo.

Ci-dessous le script complet. Vous pouvez aussi le télécharger ou le voir sur github pour avoir la version la plus à jour.

  1 #!/usr/bin/bash
  2 
  3 stop_command() {
  4     if [ "$1" ]; then
  5         kill "$1"
  6     fi
  7 }
  8 
  9 has_died() {
 10     if kill -0 "$1" > /dev/null 2>&1; then
 11         return 1
 12     else
 13         return 0
 14     fi
 15 }
 16 
 17 find_pid() {
 18     echo $(ps -elf | grep "$1" | grep -v '&&' | grep -v 'grep' | awk '{print $4}')
 19 }
 20 
 21 get_pid() {
 22     pid=$(find_pid "$1")
 23     until [ -n "${pid}" ]; do
 24         pid=$(find_pid "$1")
 25     done
 26     echo "${pid}"
 27 }
 28 
 29 # Activate venv
 30 cd ~/server/blog/
 31 source bin/activate
 32 cd pelican/jujens.eu
 33 
 34 regenerate_pid=''
 35 serve_pid=''
 36 # These variables cannot be used. If you try to use them, you will get an error like 3< not found.
 37 #regenerate_output=3
 38 #serve_output=4
 39 
 40 while true; do
 41     # Print errors for regenerate
 42     if [ -n "${regenerate_pid}" ] && has_died "${regenerate_pid}"; then
 43         echo -e "Regenerate has died with ouput:\n"
 44         cat <&3
 45     fi
 46     # Print errors for serve
 47     if [ -n "${serve_pid}" ] && has_died "${serve_pid}"; then
 48         echo -e "Serve has died with ouput:\n"
 49         cat <&4
 50     fi
 51 
 52     echo -en "(blog) > "
 53     read command
 54 
 55     case "${command}" in
 56         deploy)
 57             hg push > /dev/null
 58             hg push bitbucket >/dev/null
 59             stop_command "${serve_pid}"
 60             stop_command "${regenerate_pid}"
 61             serve_pid=''
 62             regenerate_pid=''
 63             cat <&3 > /dev/null 2>&1
 64             cat <&4 > /dev/null 2>&1
 65             make rsync_upload > /dev/null
 66             ;;
 67         push)
 68             hg push > /dev/null
 69             hg push bitbucket > /dev/null
 70             ;;
 71         st|status)
 72             hg st
 73             ;;
 74         add)
 75             echo "Enter the filename to add (. for all files)"
 76             read file_name
 77             hg add "${file_name}"
 78             ;;
 79         ci|commit)
 80             echo "Please enter the commit message:"
 81             read commit_msg
 82             hg ci -m "${commit_msg}"
 83             ;;
 84         serve)
 85             if [ -n "${serve_pid}" ] && ! has_died "${serve_pid}"; then
 86                 echo "Serve is already running."
 87             else
 88                 exec 4< <(make serve 2>&1)
 89                 serve_pid=$(get_pid 'python3 -m pelican.server')
 90             fi
 91             ;;
 92         regenerate)
 93             if [ -n "${regenerate_pid}" ] && ! has_died "${regenerate_pid}"; then
 94                 echo "Regenerate is already running."
 95             else
 96                 exec 3< <(make regenerate 2>&1)
 97                 regenerate_pid=$(get_pid 'make regenerate')
 98             fi
 99             ;;
100         "stop serve")
101             stop_command "${serve_pid}" > /dev/null
102             serve_pid=''
103             cat <&4 > /dev/null
104             ;;
105         "stop regenerate")
106             stop_command "${regenerate_pid}" > /dev/null
107             regenerate_pid=''
108             cat <&3 > /dev/null
109             ;;
110         stop)
111             echo "Stop requires an argument: serve or regenerate"
112             ;;
113         quit)
114             break
115             ;;
116         help)
117             echo "Available commands:"
118             echo -e "\tdeploy"
119             echo -e "\tpush"
120             echo -e "\tserve"
121             echo -e "\tregenerate"
122             echo -e "\tstop serve"
123             echo -e "\tstop regenerate"
124             echo -e "\thelp"
125             ;;
126         *)
127             if [ -n "${command}" ]; then
128                 echo -e "${command} is invalid."
129             fi
130     esac
131 done
132 
133 echo "Done"