3 """A script to schedule Pelican posts in advance, appropriate for a Git
4 post-receive hook. Requires the `at` job-scheduling utility."""
11 WORKING_REPO = "/home/mtsw/working"
12 INPUT_DIR = os.path.join(WORKING_REPO, "content")
13 OUTPUT_DIR = "/var/www/html"
14 PUBLISH_CONF = os.path.join(WORKING_REPO, "publishconf.py")
15 SITEGEN_COMMAND = "bash -c 'cd {} && source bin/activate && pelican {} -o {} -s {}'".format(
16 WORKING_REPO, INPUT_DIR, OUTPUT_DIR, PUBLISH_CONF)
18 DATELINE_REGEX = re.compile(r"^Date: *(\d{4}-\d{2}-\d{2} \d{2}:\d{2}) *$",
20 JOBLINE_REGEX = re.compile(r"\d+\s\w{3} (\w{3} +\d{1,2} \d{2}:\d{2}:\d{2} \d{4})")
22 def get_future_publication_times():
23 now = datetime.datetime.now()
25 for path, _dirnames, filenames in os.walk(INPUT_DIR):
26 if path.endswith("drafts"):
28 for filename in filenames:
29 if not filename.endswith(".md"):
31 with open(os.path.join(path, filename)) as post_file:
32 match = DATELINE_REGEX.search(post_file.read())
34 time = datetime.datetime.strptime(match.group(1),
41 def get_extant_at_job_times():
43 result = subprocess.run(["atq"], stdout=subprocess.PIPE)
44 job_lines = result.stdout.decode('utf8').split('\n')
45 for job_line in job_lines:
46 match = JOBLINE_REGEX.match(job_line)
48 times.add(datetime.datetime.strptime(match.group(1),
53 def schedule(command, when):
54 timestamp = when.strftime("%H:%M %Y-%m-%d")
55 at_command = ['at', timestamp]
56 at = subprocess.Popen(
58 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
60 at.communicate(command.encode())
64 # sync our "working" repo with the bare one
66 # but first, don't let the fact that this is running as a hook on the bare
68 our_env = os.environ.copy()
69 del our_env['GIT_DIR']
71 for git_cmd in [["git", "fetch", "origin"],
72 ["git", "reset", "--hard", "origin/master"]]:
73 subprocess.run(git_cmd, cwd=WORKING_REPO, env=our_env)
75 # sitegen now! (even if there are no posts to queue, we can at least update
76 # /drafts/) and make any back-edits to published posts live
77 subprocess.run(SITEGEN_COMMAND,
78 # XXX: `shell=True` is contrary to the moral law
81 # look for scheduled future posts
82 future_publication_times = get_future_publication_times()
85 extant_at_job_times = get_extant_at_job_times()
87 # if there are future posts that don't have an atq entry, schedule a
88 # site-regen at that time
89 to_schedule = future_publication_times - extant_at_job_times
90 for time in to_schedule:
91 schedule(SITEGEN_COMMAND, time)
94 if __name__ == "__main__":