aboutsummaryrefslogtreecommitdiff
path: root/cli/management.py
diff options
context:
space:
mode:
Diffstat (limited to 'cli/management.py')
-rw-r--r--cli/management.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/cli/management.py b/cli/management.py
new file mode 100644
index 00000000..9d35c643
--- /dev/null
+++ b/cli/management.py
@@ -0,0 +1,221 @@
+#/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