en fr

A basic shell for pelican

Posted on 2015-01-09 in Programmation

My blog uses Pelican a static website generator. All my articles are under version control. To generate after each modification the new html version of the pages, you must use the make regenerate. To launch the test web server, you use make serve. Finally to synchronize the content on the server, you use make rsync_upload. Each time I do this, I also push my modifications to my server and to Bitbucket in order to have a backup.

Until now, I managed all that in a standard terminal. I already had a bash function to switch to the proper directory and enable python's venv. However, I did not find that very practical. make serve and make regenerate must always stay in background, I have to push to two different repositories and synchronize with the server. So I decided to create a script to improve that.

Since I need to launch the same commands in the same folder without leaving the venv and since I want to easily interact with the background commands, I decided to create a small shell. I am very happy with it.

Some explanations on the script (available below):

  • Everything is in an infinite loop to simulate a true shell. echo -ne "> " print a prompt and let the cursor on the same line.
  • exec 3< <(make serve)
    • <(make serve) is replaced by a path to a virtual file connected to the standard output of the command.
    • Since we want the process to be in background, we launch it with exec.
    • Finally, since we want to fetch the output of the command, we connect the file to a new output named 3. We then read its content with cat <&3. While the file is not closed, this command won't stop.
    • Got to this thread for more details.
  • Additional output: I tried to store the name of the ouputs in variables. That would have allowed me to refactor and clarify the code. However, that makes exec "${var}"< <(make serve) and cat <&"${var}" crash.
  • [ -n "${serve_pid}" ] && has_died "${serve_pid}": has_died must not be surrounded by brackets (that means it must not be pass to the test command) in order for the script to work properly.
  • echo ${pid}: you can only send error code with return. For everything else, you must use echo.

You will find the whole script below. You can also download it ou le see it on github if you want the latest version

  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"