mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge 8e09bc778c into 39f74280b3
This commit is contained in:
commit
6b90e17991
4 changed files with 149 additions and 216 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue