#/bin/python3 import subprocess import sys import re import time import signal from config.config import * from utils import utils from generators import generators def generate(args): """Wrapper for generate_blog(fresh=False).""" # pylint: disable=unused-argument exit(generators.generate_blog(fresh=False)) def regenerate(args): """Wrapper for generate_blog(fresh=True).""" # pylint: disable=unused-argument exit(generators.generate_blog(fresh=True)) def deploy(args): """Deploys build directory to origin/master without regenerating. Returns ------- 0 On success. Exit early with nonzero status otherwise. """ # pylint: disable=unused-argument,too-many-statements # check whether root is dirty os.chdir(ROOTDIR) dirty = subprocess.check_output(["git", "status", "--porcelain"]) if dirty: sys.stderr.write(YELLOW) sys.stderr.write("Project root is dirty.\n") sys.stderr.write("You may want to commit in your changes " "to the source branch, since the SHA and title " "of the latest commit on the source branch will be " "incorporated into the commit message on " "the deployment branch. Type s[hell] on the " "next prompt to open an interactive shell.\n") sys.stderr.write(RESET) while True: sys.stderr.write("Continue? [yNs] ") answer = input() if not answer: # default abort = True break elif answer.startswith(('y', 'Y')): abort = False break elif answer.startswith(('n', 'N')): abort = True break elif answer.startswith(('s', 'S')): shell = (os.environ['SHELL'] if 'SHELL' in os.environ and os.environ['SHELL'] else 'zsh') subprocess.call(shell) stilldirty = subprocess.check_output(["git", "status", "--porcelain"]) if stilldirty: sys.stderr.write(YELLOW) sys.stderr.write("Project root is still dirty.\n") sys.stderr.write(RESET) else: sys.stderr.write("Please answer yes or no.\n") if abort: sys.stderr.write("%saborting deployment%s\n" % (RED, RESET)) return 1 # extract latest commit on the source branch source_commit = subprocess.check_output( ["git", "log", "-1", "--pretty=oneline", "source", "--"]).decode('utf-8').strip() # cd into BUILDDIR and assemble commit message sys.stderr.write("%scommand: cd '%s'%s\n" % (BLUE, BUILDDIR, RESET)) os.chdir(BUILDDIR) # extract updated time from atom.xml if not os.path.exists("atom.xml"): sys.stderr.write("atom.xml not found, cannot deploy\naborting\n") return 1 atomxml = ET.parse("atom.xml").getroot() updated = atomxml.find('{http://www.w3.org/2005/Atom}updated').text commit_message = ("Site updated at %s\n\nsource branch was at:\n%s\n" % (updated, source_commit)) # commit changes in BUILDDIR sys.stderr.write("%scommand: git add --all%s\n" % (BLUE, RESET)) subprocess.check_call(["git", "add", "--all"]) sys.stderr.write("%scommand: git commit --no-verify --gpg-sign --message='%s'%s\n" % (BLUE, commit_message, RESET)) try: subprocess.check_call(["git", "commit", "--gpg-sign", "--message=%s" % commit_message]) except subprocess.CalledProcessError: sys.stderr.write("\n%serror: git commit failed%s\n" % (RED, RESET)) return 1 # check dirty status dirty = subprocess.check_output(["git", "status", "--porcelain"]) if dirty: sys.stderr.write(RED) sys.stderr.write("error: failed to commit all changes; " "build directory still dirty\n") sys.stderr.write("error: please manually inspect what was left out\n") sys.stderr.write(RESET) return 1 # push to origin/master sys.stderr.write("%scommand: git push origin master%s\n" % (BLUE, RESET)) try: subprocess.check_call(["git", "push", "origin", "master"]) except subprocess.CalledProcessError: sys.stderr.write("\n%serror: git push failed%s\n" % (RED, RESET)) return 1 return 0 def gen_deploy(args): """Regenerate and deploy.""" # pylint: disable=unused-argument,too-many-branches # try to smartly determine the latest post, and prompt to touch it current_time = time.time() latest_post = None latest_postdate = 0 latest_mtime = 0 for name in os.listdir(POSTSDIR): matchobj = re.match(r"^([0-9]{4})-([0-9]{2})-([0-9]{2})-.*\.md", name) if not matchobj: continue fullpath = os.path.join(POSTSDIR, name) mtime = os.path.getmtime(fullpath) # get post date from the date metadata field of the post postdate = 0 with open(fullpath) as postobj: for line in postobj: dateregex = r"^date: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-\d{2}:?\d{2})" datematch = re.match(dateregex, line.rstrip()) if datematch: postdate = dateutil.parser.parse(datematch.group(1)).timestamp() break # skip the post if it is dated more than three days ago if current_time - postdate > 3 * 24 * 3600: continue if mtime > latest_mtime: latest_post = name latest_postdate = postdate latest_mtime = mtime # prompt for touching if the latest post determined above was # modified within the last hour but the date registered in the post # isn't within the last ten minutes if ((latest_post is not None and current_time - latest_mtime < 3600 and current_time - latest_postdate > 600)): sys.stderr.write("%sIt appears that %s might be a new post.\n" "Do you want to touch its timestamp?%s\n" % (GREEN, latest_post, RESET)) while True: yesnoquit = input("[ynq]: ") if yesnoquit.startswith(("Y", "y")): yesno = True break elif yesnoquit.startswith(("N", "n")): yesno = False break elif yesnoquit.startswith(("Q", "q")): sys.stderr.write("%saborting gen_deploy%s\n" % (RED, RESET)) return 1 else: sys.stderr.write("Please answer yes, no, or quit.\n") if yesno: sys.stderr.write("%stouching %s%s\n" % (BLUE, latest_post, RESET)) touch(latest_post) sys.stderr.write("\n") generators.generate_blog(fresh=True) deploy(None) def preview(args): """Serve the blog and auto regenerate upon changes.""" # pylint: disable=unused-argument server_process = utils.HTTPServerProcess(BUILDDIR) server_process.start() sys.stderr.write("watching for changes\n") sys.stderr.write("send SIGINT to stop\n") # install a SIGINT handler only for this process sigint_raised = False def sigint_mitigator(signum, frame): """Translate SIGINT to setting the sigint_raised flag.""" nonlocal sigint_raised sigint_raised = True signal.signal(signal.SIGINT, sigint_mitigator) # Watch and auto-regen. # No need to actually implement watch separately, since # generate_blog(fresh=False, report_total_errors=False) already # watches for modifications and only regens upon changes, and it is # completely silent when there's no change. while not sigint_raised: generators.generate_blog(fresh=False, report_total_errors=False) time.sleep(0.5) sys.stderr.write("\nSIGINT received, cleaning up...\n") server_process.join() return 0