This commit is contained in:
Lionel Panhaleux 2015-08-21 11:39:28 +00:00
commit 182f9c47b4
4 changed files with 149 additions and 216 deletions

View file

@ -5,6 +5,7 @@ import six
import os
import re
import sys
import threading
import time
import logging
import argparse
@ -15,6 +16,7 @@ import collections
# because logging.setLoggerClass has to be called before logging.getLogger
from pelican.log import init
from pelican import server
from pelican import signals
from pelican.generators import (ArticlesGenerator, PagesGenerator,
@ -152,7 +154,7 @@ class Pelican(object):
context = self.settings.copy()
# Share these among all the generators and content objects:
context['filenames'] = {} # maps source path to Content object or None
context['localsiteurl'] = self.settings['SITEURL']
context['localsiteurl'] = self.settings['SITEURL']
generators = [
cls(
@ -254,7 +256,7 @@ class Pelican(object):
logger.debug('Found writer: %s', writer)
else:
logger.warning(
'%s writers found, using only first one: %s',
'%s writers found, using only first one: %s',
writers_found, writer)
return writer(self.output_path, settings=self.settings)
@ -309,6 +311,14 @@ def parse_arguments():
help='Relaunch pelican each time a modification occurs'
' on the content files.')
parser.add_argument('-S', '--serve', dest='serve',
action='store_true',
help='Serve on localhost')
parser.add_argument('-p', '--port', dest='port', type=int, default=8000,
help='Port to serve on. '
'Only used with the -S/--serve option')
parser.add_argument('--relative-urls', dest='relative_paths',
action='store_true',
help='Use relative urls in output, '
@ -380,106 +390,112 @@ def get_instance(args):
return cls(settings), settings
class Generator(threading.Thread):
def __init__(self, args):
super(Generator, self).__init__()
self.args = args
self.settings = None
self.pelican = None
self.watchers = None
self.setup()
def setup(self):
config_file = self.args.settings
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
config_file = DEFAULT_CONFIG_NAME
self.settings = read_settings(config_file,
override=get_config(self.args))
cls = self.settings['PELICAN_CLASS']
if isinstance(cls, six.string_types):
module, cls_name = cls.rsplit('.', 1)
module = __import__(module)
cls = getattr(module, cls_name)
self.pelican = cls(self.settings)
readers = Readers(self.settings)
self.watchers = {'content': folder_watcher(self.pelican.path,
readers.extensions,
self.pelican.ignore_files),
'theme': folder_watcher(self.pelican.theme,
[''],
self.pelican.ignore_files),
'settings': file_watcher(self.args.settings)}
for static_path in self.settings.get("STATIC_PATHS", []):
# use a prefix to avoid overriding standard watchers above
self.watchers['[static]%s' % static_path] = folder_watcher(
os.path.join(self.pelican.path, static_path),
[''],
self.pelican.ignore_files)
if next(self.watchers['content']) is None:
logger.warning('No valid files found in content.')
if next(self.watchers['theme']) is None:
logger.warning('Empty theme folder. Using `basic` theme.')
def generate(self):
list(map(next, self.watchers.values()))
self.pelican.run()
def run(self):
logger.info(' --- AutoReload Mode: Monitoring `content`, `theme` and'
' `settings` for changes. ---')
while True:
try:
# Check source dir for changed files ending with the given
# extension in the settings. In the theme dir is no such
# restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames
# have.
modified = {k: next(v) for k, v in self.watchers.items()}
if modified['settings']:
self.setup()
if any(modified.values()):
print('\n-> Modified: {}. re-generating...'.format(
', '.join(k for k, v in modified.items() if v)))
self.generate()
except Exception as e:
logger.warning('Caught exception "%s". Reloading.', e)
finally:
time.sleep(.5) # sleep to avoid cpu load
def main():
args = parse_arguments()
init(args.verbosity)
pelican, settings = get_instance(args)
readers = Readers(settings)
watchers = {'content': folder_watcher(pelican.path,
readers.extensions,
pelican.ignore_files),
'theme': folder_watcher(pelican.theme,
[''],
pelican.ignore_files),
'settings': file_watcher(args.settings)}
old_static = settings.get("STATIC_PATHS", [])
for static_path in old_static:
# use a prefix to avoid possible overriding of standard watchers above
watchers['[static]%s' % static_path] = folder_watcher(
os.path.join(pelican.path, static_path),
[''],
pelican.ignore_files)
try:
generator = Generator(args)
generator.generate()
if args.serve:
serve = server.ServeThread(
generator.settings.get('OUTPUT_PATH', '.'),
'localhost',
args.port)
serve.daemon = True
serve.start()
if args.autoreload:
print(' --- AutoReload Mode: Monitoring `content`, `theme` and'
' `settings` for changes. ---')
generator.daemon = True
generator.start()
generator.join()
elif args.serve:
serve.join()
while True:
try:
# Check source dir for changed files ending with the given
# extension in the settings. In the theme dir is no such
# restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames
# have.
modified = {k: next(v) for k, v in watchers.items()}
if modified['settings']:
pelican, settings = get_instance(args)
# Adjust static watchers if there are any changes
new_static = settings.get("STATIC_PATHS", [])
# Added static paths
# Add new watchers and set them as modified
for static_path in set(new_static).difference(old_static):
static_key = '[static]%s' % static_path
watchers[static_key] = folder_watcher(
os.path.join(pelican.path, static_path),
[''],
pelican.ignore_files)
modified[static_key] = next(watchers[static_key])
# Removed static paths
# Remove watchers and modified values
for static_path in set(old_static).difference(new_static):
static_key = '[static]%s' % static_path
watchers.pop(static_key)
modified.pop(static_key)
# Replace old_static with the new one
old_static = new_static
if any(modified.values()):
print('\n-> Modified: {}. re-generating...'.format(
', '.join(k for k, v in modified.items() if v)))
if modified['content'] is None:
logger.warning('No valid files found in content.')
if modified['theme'] is None:
logger.warning('Empty theme folder. Using `basic` '
'theme.')
pelican.run()
except KeyboardInterrupt:
logger.warning("Keyboard interrupt, quitting.")
break
except Exception as e:
if (args.verbosity == logging.DEBUG):
raise
logger.warning(
'Caught exception "%s". Reloading.', e)
finally:
time.sleep(.5) # sleep to avoid cpu load
else:
if next(watchers['content']) is None:
logger.warning('No valid files found in content.')
if next(watchers['theme']) is None:
logger.warning('Empty theme folder. Using `basic` theme.')
pelican.run()
except KeyboardInterrupt:
logger.info("Keyboard interrupt, quitting.")
except Exception as e:
logger.critical('%s', e)
if args.verbosity == logging.DEBUG:
raise
else:

View file

@ -1,8 +1,14 @@
from __future__ import print_function
import os
import os.path
import posixpath
import sys
import logging
import threading
from six.moves.urllib import parse as url_parse
from six.moves import SimpleHTTPServer as srvmod
from six.moves import socketserver
@ -14,18 +20,23 @@ except ImportError:
class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
SUFFIXES = ['', '.html', '/index.html']
SERVPATH = ''
def translate_path(self, path):
"""Add the ability to serve a file from a given directory (not cwd)
"""
path = super(ComplexHTTPRequestHandler, self).translate_path(path)
return os.path.join(self.SERVPATH, os.path.relpath(path, os.getcwd()))
def do_GET(self):
self.original_path = self.path
# Try to detect file by applying various suffixes
for suffix in self.SUFFIXES:
if not hasattr(self, 'original_path'):
self.original_path = self.path
self.path = self.original_path + suffix
path = self.translate_path(self.path)
if os.path.exists(path):
srvmod.SimpleHTTPRequestHandler.do_GET(self)
super(ComplexHTTPRequestHandler, self).do_GET()
logging.info("Found `%s`." % self.path)
break
@ -48,21 +59,36 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
return mimetype
if __name__ == '__main__':
PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000
SERVER = len(sys.argv) == 3 and sys.argv[2] or ""
socketserver.TCPServer.allow_reuse_address = True
def serve(path, server, port):
ComplexHTTPRequestHandler.SERVPATH = path
try:
httpd = socketserver.TCPServer((SERVER, PORT), ComplexHTTPRequestHandler)
httpd = socketserver.TCPServer((server, port), ComplexHTTPRequestHandler)
httpd.allow_reuse_address = True
except OSError as e:
logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
logging.error("Could not listen on port %s, server %s.", port, server)
sys.exit(getattr(e, 'exitcode', 1))
logging.info("Serving at port %s, server %s.", PORT, SERVER)
logging.info("Serving %s at port %s, server %s.", path, port, server)
try:
httpd.serve_forever()
except KeyboardInterrupt as e:
logging.info("Shutting down server.")
httpd.socket.close()
raise
class ServeThread(threading.Thread):
def __init__(self, path, server, port):
super(ServeThread, self).__init__()
self.path = path
self.server = server
self.port = port
def run(self):
serve(self.path, self.server, self.port)
if __name__ == '__main__':
PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000
SERVER = len(sys.argv) == 3 and sys.argv[2] or ""
serve('', SERVER, PORT)

View file

@ -46,8 +46,7 @@ help:
@echo ' make publish generate using production settings '
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
@echo ' make devserver [PORT=8000] start/restart develop_server.sh '
@echo ' make stopserver stop local server '
@echo ' make devserver [PORT=8000] start serve with regenerate '
@echo ' make ssh_upload upload the web site via SSH '
@echo ' make rsync_upload upload the web site via rsync+ssh '
@echo ' make dropbox_upload upload the web site via Dropbox '
@ -86,17 +85,11 @@ endif
devserver:
ifdef PORT
$$(BASEDIR)/develop_server.sh restart $$(PORT)
$$(PELICAN) -S -o $$(OUTPUTDIR) -s $$(PUBLISHCONF) $$(PELICANOPTS) -p $$(PORT) $$(INPUTDIR)
else
$$(BASEDIR)/develop_server.sh restart
$$(PELICAN) -S -o $$(OUTPUTDIR) -s $$(PUBLISHCONF) $$(PELICANOPTS) $$(INPUTDIR)
endif
stopserver:
$(BASEDIR)/develop_server.sh stop
@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
publish:
$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(PUBLISHCONF) $$(PELICANOPTS)
ssh_upload: publish
scp -P $$(SSH_PORT) -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR)

View file

@ -1,102 +0,0 @@
#!/usr/bin/env bash
##
# This section should match your Makefile
##
PELICAN=$${PELICAN:-$pelican}
PELICANOPTS=$pelicanopts
BASEDIR=$$(pwd)
INPUTDIR=$$BASEDIR/content
OUTPUTDIR=$$BASEDIR/output
CONFFILE=$$BASEDIR/pelicanconf.py
###
# Don't change stuff below here unless you are sure
###
SRV_PID=$$BASEDIR/srv.pid
PELICAN_PID=$$BASEDIR/pelican.pid
function usage(){
echo "usage: $$0 (stop) (start) (restart) [port]"
echo "This starts Pelican in debug and reload mode and then launches"
echo "an HTTP server to help site development. It doesn't read"
echo "your Pelican settings, so if you edit any paths in your Makefile"
echo "you will need to edit your settings as well."
exit 3
}
function alive() {
kill -0 $$1 >/dev/null 2>&1
}
function shut_down(){
PID=$$(cat $$SRV_PID)
if [[ $$? -eq 0 ]]; then
if alive $PID; then
echo "Stopping HTTP server"
kill $$PID
else
echo "Stale PID, deleting"
fi
rm $$SRV_PID
else
echo "HTTP server PIDFile not found"
fi
PID=$$(cat $$PELICAN_PID)
if [[ $$? -eq 0 ]]; then
if alive $$PID; then
echo "Killing Pelican"
kill $$PID
else
echo "Stale PID, deleting"
fi
rm $$PELICAN_PID
else
echo "Pelican PIDFile not found"
fi
}
function start_up(){
local port=$$1
echo "Starting up Pelican and HTTP server"
shift
$$PELICAN --debug --autoreload -r $$INPUTDIR -o $$OUTPUTDIR -s $$CONFFILE $$PELICANOPTS &
pelican_pid=$$!
echo $$pelican_pid > $$PELICAN_PID
cd $$OUTPUTDIR
$PY -m pelican.server $$port &
srv_pid=$$!
echo $$srv_pid > $$SRV_PID
cd $$BASEDIR
sleep 1
if ! alive $$pelican_pid ; then
echo "Pelican didn't start. Is the Pelican package installed?"
return 1
elif ! alive $$srv_pid ; then
echo "The HTTP server didn't start. Is there another service using port" $$port "?"
return 1
fi
echo 'Pelican and HTTP server processes now running in background.'
}
###
# MAIN
###
[[ ($$# -eq 0) || ($$# -gt 2) ]] && usage
port=''
[[ $$# -eq 2 ]] && port=$$2
if [[ $$1 == "stop" ]]; then
shut_down
elif [[ $$1 == "restart" ]]; then
shut_down
start_up $$port
elif [[ $$1 == "start" ]]; then
if ! start_up $$port; then
shut_down
fi
else
usage
fi