"+title+"
\n" "\t\t"+article+"\n" "\t\t"+date_readable+"
\n" "\t#!/usr/bin/env python3
# Blogit script written by Phyks (Lucas Verney) for his personnal use. I
# distribute it with absolutely no warranty, except that it works for me on my
# blog :)
# This script is a pre-commit hook that should be placed in your .git/hooks
# folder to work. Read README file for more info.
# LICENSE :
# -----------------------------------------------------------------------------
# "THE NO-ALCOHOL BEER-WARE LICENSE" (Revision 42):
# Phyks (webmaster@phyks.me) wrote this file. As long as you retain this notice
# you can do whatever you want with this stuff (and you can also do whatever
# you want with this stuff without retaining it, but that's not cool...). If
# we meet some day, and you think this stuff is worth it, you can buy me a
# "+date_readable+"beer soda in return.
# Phyks
# ----------------------------------------------------------------------------
import sys
import getopt
import shutil
import os
import datetime
import subprocess
import re
import locale
from time import gmtime, strftime, mktime
# =========
# Functions
# =========
# Test if a variable exists (== isset function in PHP)
# ====================================================
def isset(variable):
return variable in locals() or variable in globals()
# Test wether a variable is an int or not
# =======================================
def isint(variable):
try:
int(variable)
return True
except ValueError:
return False
# List all files in path directory
# Works recursively
# Return files list with path relative to current dir
# ===================================================
def list_directory(path):
fichier = []
for root, dirs, files in os.walk(path):
for i in files:
fichier.append(os.path.join(root, i))
return fichier
# Return a list with the tags of a given article
# ==============================================
def get_tags(filename):
try:
with open(filename, 'r') as fh:
tag_line = ''
for line in fh.readlines():
if "@tags=" in line:
tag_line = line
break
if not tag_line:
return []
tags = [x.strip() for x in line[line.find("@tags=")+6:].split(",")]
return tags
except IOError:
sys.exit("[ERROR] Unable to open file "+filename+".")
#Return date of an article
# ========================
def get_date(filename):
try:
with open(filename, 'r') as fh:
for line in fh.readlines():
if "@date=" in line:
return line[line.find("@date=")+6:].strip()
sys.exit("[ERROR] Unable to determine date in article "+filename+".")
except IOError:
sys.exit("[ERROR] Unable to open file "+filename+".")
# Return the _number_ latest articles in _dir_ directory
# ======================================================
def latest_articles(directory, number):
try:
latest_articles = subprocess.check_output(["git",
"ls-files",
directory],
universal_newlines=True)
except:
sys.exit("[ERROR] An error occurred when fetching file changes "
"from git.")
latest_articles = latest_articles.strip().split("\n")
latest_articles = [x for x in latest_articles if isint(x[4:8])]
latest_articles.sort(key=lambda x: get_date(x),
reverse=True)
return latest_articles[:number]
# Auto create necessary directories to write a file
# =================================================
def auto_dir(path):
directory = os.path.dirname(path)
try:
if not os.path.exists(directory):
os.makedirs(directory)
except IOError:
sys.exit("[ERROR] An error occurred while creating "+path+" file "
"and parent dirs.")
# Replace some user specific syntax tags (to repplace smileys for example)
# ========================================================================
def replace_tags(article, search_list, replace_list):
return_string = article
for search, replace in zip(search_list, replace_list):
return_string = re.sub(search, replace, article)
return return_string
# Set locale
locale.set_locale(locale.LC_ALL, '')
# ========================
# Start of the main script
# ========================
try:
opts, args = getopt.gnu_getopt(sys.argv, "hf", ["help", "force-regen"])
except getopt.GetoptError:
sys.exit("[ERROR] Unable to parse command line arguments. "
"See pre-commit -h for more infos on how to use.")
force_regen = False
for opt, arg in opts:
if opt in ("-h", "--help"):
print("Usage :")
print("This should be called automatically as a pre-commit git hook. "
"You can also launch it manually right before commiting.\n")
print("This script generates static pages ready to be served behind "
"your webserver.\n")
print("Usage :")
print("-h \t --help \t displays this help message.")
print("-f \t --force-regen \t force complete rebuild of all pages.")
sys.exit(0)
elif opt in ("-f", "--force-regen"):
force_regen = True
# Set parameters with params file
search_list = []
replace_list = []
try:
with open("raw/params", "r") as params_fh:
params = {}
for line in params_fh.readlines():
if line.strip() == "" or line.strip().startswith("#"):
continue
option, value = line.split("=", 1)
if option == "SEARCH":
search_list = value.strip().split(",")
elif option == "REPLACE":
replace_list = value.strip().split(",")
else:
params[option.strip()] = value.strip()
print("[INFO] Parameters set from raw/params file.")
except IOError:
sys.exit("[ERROR] Unable to load raw/params file which defines important "
"parameters. Does such a file exist ? See doc for more info "
"on this file.")
# Fill lists for modified, deleted and added files
modified_files = []
deleted_files = []
added_files = []
#Lists of years and months with modified files
years_list = []
months_list = []
if not force_regen:
# Find the changes to be committed
try:
changes = subprocess.check_output(["git",
"diff",
"--cached",
"--name-status"],
universal_newlines=True)
except:
sys.exit("[ERROR] An error occurred when fetching file changes "
"from git.")
changes = changes.strip().split("\n")
if changes == [""]:
sys.exit("[ERROR] Nothing to do... Did you add new files with "
"\"git add\" before ?")
for changed_file in changes:
if changed_file[0].startswith("A"):
added_files.append(changed_file[changed_file.index("\t")+1:])
elif changed_file[0].startswith("M"):
modified_files.append(changed_file[changed_file.index("\t")+1:])
elif changed_file[0].startswith("D"):
deleted_files.append(changed_file[changed_file.index("\t")+1:])
else:
sys.exit("[ERROR] An error occurred when running git diff.")
else:
shutil.rmtree("blog/")
shutil.rmtree("gen/")
added_files = list_directory("raw")
if not added_files and not modified_files and not deleted_files:
sys.exit("[ERROR] Nothing to do... Did you add new files with "
"\"git add\" before ?")
# Only keep modified raw articles files
for filename in list(added_files):
direct_copy = False
if not filename.startswith("raw/"):
added_files.remove(filename)
continue
try:
int(filename[4:8])
if filename[4:8] not in years_list:
years_list.append(filename[4:8])
except ValueError:
direct_copy = True
try:
int(filename[9:11])
if filename[9:11] not in months_list:
months_list.append(filename[9:11])
except ValueError:
pass
if ((not filename.endswith(".html") and not filename.endswith(".ignore"))
or direct_copy):
# Note : this deal with CSS, images or footer file
print("[INFO] (Direct copy) Copying directly the file "
+ filename[4:]+" to blog dir.")
auto_dir("blog/"+filename[4:])
shutil.copy(filename, "blog/"+filename[4:])
added_files.remove(filename)
continue
if filename.endswith(".ignore"):
print("[INFO] (Not published) Found not published article "
+ filename[4:-7]+".")
added_files.remove(filename)
continue
for filename in list(modified_files):
direct_copy = False
if not filename.startswith("raw/"):
modified_files.remove(filename)
continue
try:
int(filename[4:8])
if filename[4:8] not in years_list:
years_list.append(filename[4:8])
except ValueError:
direct_copy = True
try:
int(filename[9:11])
if filename[9:11] not in months_list:
months_list.append(filename[9:11])
except ValueError:
pass
if ((not filename.endswith("html") and not filename.endswith("ignore"))
or direct_copy):
print("[INFO] (Direct copy) Updating directly the file "
+ filename[4:]+" in blog dir.")
auto_dir("blog/"+filename[4:])
shutil.copy(filename, "blog/"+filename[4:])
modified_files.remove(filename)
continue
if filename.endswith("ignore"):
print("[INFO] (Not published) Found not published article "
+ filename[4:-7]+".")
added_files.remove(filename)
continue
for filename in list(deleted_files):
direct_copy = False
if not filename.startswith("raw/"):
deleted_files.remove(filename)
continue
try:
int(filename[4:8])
if filename[4:8] not in years_list:
years_list.append(filename[4:8])
except ValueError:
direct_delete = True
try:
int(filename[9:11])
if filename[9:11] not in months_list:
months_list.append(filename[9:11])
except ValueError:
pass
if ((not filename.endswith("html") and not filename.endswith("ignore"))
or direct_delete):
print("[INFO] (Deleted file) Delete directly copied file "
+ filename[4:]+" in blog dir.")
os.unlink(filename)
deleted_files.remove(filename)
continue
print("[INFO] Added files : "+", ".join(added_files))
print("[INFO] Modified files : "+", ".join(modified_files))
print("[INFO] Deleted filed : "+", ".join(deleted_files))
print("[INFO] Updating tags for added and modified files.")
for filename in added_files:
tags = get_tags(filename)
if not tags:
sys.exit("[ERROR] (TAGS) In added article "+filename[4:]+" : "
"No tags found !")
for tag in tags:
try:
auto_dir("gen/tags/"+tag+".tmp")
with open("gen/tags/"+tag+".tmp", 'a+') as tag_file:
tag_file.seek(0)
if filename[4:] not in tag_file.read():
tag_file.write(filename[4:]+"\n")
print("[INFO] (TAGS) Found tag "+tag+" in article "
+ filename[4:])
except IOError:
sys.exit("[ERROR] (TAGS) New tag found but an error "
"occurred in article "+filename[4:]+": "+tag+".")
for filename in modified_files:
try:
with open(filename, 'r') as fh:
tags = get_tags(fh)
except IOError:
sys.exit("[ERROR] Unable to open file "+filename[4:]+".")
if not tags:
sys.exit("[ERROR] (TAGS) In modified article "+filename[4:]+" : "
" No tags found !")
for tag in list_directory("gen/tags/"):
try:
with open(tag, 'r+') as tag_file:
if (tag[tag.index("tags/") + 5:tag.index(".tmp")] in tags
and filename[4:] not in tag_file.read()):
tag_file.seek(0, 2) # Append to end of file
tag_file.write(filename[4:]+"\n")
print("[INFO] (TAGS) Found new tag "
+ tag[:tag.index(".tmp")]+" for modified article "
+ filename[4:]+".")
tags.remove(tag_file[9:])
if (tag[tag.index("tags/") + 5:tag.index(".tmp")] not in tags
and filename[4:] in tag_file.read()):
tag_old = tag_file.read()
tag_file.truncate()
# Delete file in tag
tag_file_write = tag_old.replace(filename[4:]+"\n", "")
if tag_file_write:
tag_file.write(tag_file_write)
print("[INFO] (TAGS) Deleted tag " +
tag[:tag.index(".tmp")]+" in modified article " +
filename[4:]+".")
tags.remove(tag_file[9:])
except IOError:
sys.exit("[ERROR] (TAGS) An error occurred when parsing tags "
" of article "+filename[4:]+".")
if not tag_file_write:
try:
os.unlink(tag)
print("[INFO] (TAGS) No more article with tag " +
tag[8:-4]+", deleting it.")
except FileNotFoundError:
print("[INFO] (TAGS) "+tag+" was found to be empty "
"but there was an error during deletion. "
"You should check manually.")
for tag in tags: # New tags created
try:
auto_dir("gen/tags/"+tag+".tmp")
with open("gen/tags/"+tag+".tmp", "a+") as tag_file:
# Delete tag file here if empty after deletion
tag_file.write(filename[4:]+"\n")
print("[INFO] (TAGS) Found new tag "+tag+" for "
"modified article "+filename[4:]+".")
except IOError:
sys.exit("[ERROR] (TAGS) An error occurred when parsing tags "
"of article "+filename[4:]+".")
# Delete tags for deleted files and delete all generated files
for filename in deleted_files:
tags = get_tags(filename)
if not tags:
sys.exit("[ERROR] In deleted article "+filename[4:]+" : "
"No tags found !")
for tag in tags:
try:
with open("gen/tags/"+tag+".tmp", 'r+') as tag_file:
tag_old = tag_file.read()
tag_file.truncate()
# Delete file in tag
tag_file_write = tag_old.replace(filename[4:]+"\n", "")
if tag_file_write:
tag_file.write(tag_file_write)
print("[INFO] (TAGS) Deleted tag " +
tag[:tag.index(".tmp")]+" in deleted article " +
filename[4:]+".")
except IOError:
sys.exit("[ERROR] An error occurred while deleting article" +
filename[4:]+" from tags files.")
if not tag_file_write:
try:
os.unlink(tag)
print("[INFO] (TAGS) No more article with tag " +
tag[8:-4]+", deleting it.")
except FileNotFoundError:
print("[INFO] (TAGS) "+tag+" was found to be empty "
"but there was an error during deletion. "
"You should check manually.")
# Delete generated files
try:
os.unlink("gen/"+filename[4:-5]+".gen")
os.unlink("blog/"+filename[4:])
except FileNotFoundError:
print("[INFO] (DELETION) Article "+filename[4:]+" seems "
"to not have already been generated. "
"You should check manually.")
print("[INFO] (DELETION) Deleted article "+filename[4:] +
" in both gen and blog directories")
# Common lists that are used multiple times
last_articles = latest_articles("raw/", int(params["NB_ARTICLES_INDEX"]))
tags_full_list = list_directory("gen/tags")
# Generate html for each article (gen/ dir)
for filename in added_files+modified_files:
try:
with open(filename, 'r') as fh:
article, title, date, author, tags = "", "", "", "", ""
for line in fh.readlines():
article += line
if "@title=" in line:
title = line[line.find("@title=")+7:].strip()
continue
if "@date=" in line:
date = line[line.find("@date=")+6:].strip()
continue
if "@author=" in line:
author = line[line.find("@author=")+7:].strip()
continue
if "@tags=" in line:
tags = line[line.find("@tags=")+6:].strip()
continue
except IOError:
print("[ERROR] An error occurred while generating article " +
filename[4:]+".")
if not isset("tags") or not isset("title") or not isset("author"):
sys.exit("[ERROR] Missing parameters (title, author, date, tags) "
"in article "+filename[4:]+".")
date_readable = ("Le "+date[0:2]+"/"+date[2:4]+"/"+date[4:8] +
" à "+date[9:11]+":"+date[11:13])
# Write generated HTML for this article in gen /
article = replace_tags(article, search_list, replace_list)
try:
auto_dir("gen/"+filename[4:-5]+".gen")
with open("gen/"+filename[4:-5]+".gen", 'w') as article_file:
article_file.write(""+title+"
\n"
"\t\t"+article+"\n"
"\t\t