diff piecrust/publishing/base.py @ 621:8f9c0bdb3724

publish: Polish/refactor the publishing workflows. * Add a `--preview` option. * The `--list` option gives a nicer output, and prints warnings/errors for incorrect configuration. * Moved most of the `shell` code into a base class that's reusable. * Simplified the code to log publishing to a file. * Nicer output overall, with times.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 08 Feb 2016 20:44:26 -0800
parents e2e955a3bb25
children 6abb436fea5b
line wrap: on
line diff
--- a/piecrust/publishing/base.py	Sat Feb 06 21:49:50 2016 -0800
+++ b/piecrust/publishing/base.py	Mon Feb 08 20:44:26 2016 -0800
@@ -1,23 +1,103 @@
+import os.path
+import shlex
+import logging
+import threading
+import subprocess
+
+
+logger = logging.getLogger(__name__)
 
 
 class PublishingContext(object):
     def __init__(self):
-        self.custom_logging_file = None
+        self.bake_out_dir = None
+        self.preview = False
 
 
 class Publisher(object):
     def __init__(self, app, target):
         self.app = app
         self.target = target
-        self.is_using_custom_logging = False
+        self.parsed_url = None
         self.log_file_path = None
 
+    @property
+    def has_url_config(self):
+        return self.parsed_url is not None
+
+    @property
+    def url_config(self):
+        if self.parsed_url is not None:
+            return self.getConfig()
+        raise Exception("This publisher has a full configuration.")
+
     def getConfig(self):
         return self.app.config.get('publish/%s' % self.target)
 
     def getConfigValue(self, name):
+        if self.has_url_config:
+            raise Exception("This publisher only has a URL configuration.")
         return self.app.config.get('publish/%s/%s' % (self.target, name))
 
     def run(self, ctx):
         raise NotImplementedError()
 
+
+class ShellCommandPublisherBase(Publisher):
+    def __init__(self, app, target):
+        super(ShellCommandPublisherBase, self).__init__(app, target)
+        self.expand_user_args = True
+
+    def run(self, ctx):
+        args = self._getCommandArgs(ctx)
+        if self.expand_user_args:
+            args = [os.path.expanduser(i) for i in args]
+
+        if ctx.preview:
+            preview_args = ' '.join([shlex.quote(i) for i in args])
+            logger.info(
+                    "Would run shell command: %s" % preview_args)
+            return True
+
+        logger.debug(
+                "Running shell command: %s" % args)
+
+        proc = subprocess.Popen(
+                args, cwd=self.app.root_dir, bufsize=0,
+                stdout=subprocess.PIPE)
+
+        logger.debug("Running publishing monitor for PID %d" % proc.pid)
+        thread = _PublishThread(proc)
+        thread.start()
+        proc.wait()
+        thread.join()
+
+        if proc.returncode != 0:
+            logger.error(
+                    "Publish process returned code %d" % proc.returncode)
+        else:
+            logger.debug("Publish process returned successfully.")
+
+        return proc.returncode == 0
+
+    def _getCommandArgs(self, ctx):
+        raise NotImplementedError()
+
+
+class _PublishThread(threading.Thread):
+    def __init__(self, proc):
+        super(_PublishThread, self).__init__(
+                name='publish_monitor', daemon=True)
+        self.proc = proc
+        self.root_logger = logging.getLogger()
+
+    def run(self):
+        for line in iter(self.proc.stdout.readline, b''):
+            line_str = line.decode('utf8')
+            logger.info(line_str.rstrip('\r\n'))
+            for h in self.root_logger.handlers:
+                h.flush()
+
+        self.proc.communicate()
+        logger.debug("Publish monitor exiting.")
+