1
0
Fork 0
forked from github/pelican
pelican-theme/pelican/tools/pelican_quickstart.py
Johannes 'josch' Schauer a5edbf8546 Remove develop_server.sh in favour of pelican serving static files itself
Competing static site generators integrate the functionality of regenerating
content and serving it into their main executable. In pelican this
functionality used to be in an external script `develop_server.sh` which
resides in the blog base directory. This has the disadvantage that changes in
pelican can break the `develop_server.sh` scripts which will not automatically
be upgraded together with pelican by package managers. Thus, pelican should
integrate this functionality into its main executable.

To this end, this commit removes `develop_server.sh` and adds three command
line options to the pelican executable:

 * `-l/--listen` starts the HTTP server (`-s/--serve` was already taken)
 * `-p/--port` specifies the port to listen at
 * `-b/--bind` specifies the IP to bind to

`--listen` and `--autoreload` can be used together to achieve the same
effect that other static site generators offer: Serve files via HTTP
while at the same time auto-generating the content.

Since the `develop_server.sh` script was removed, pelican-quickstart looses the
`develop` option.

Since the `develop_server.sh` script was removed, the Makefile looses the
`stopserver` target and the `devserver` target is replaced by running `pelican
-l` in the foreground.

Since pelican now offers the `--listen` option, the fabfile uses that instead
of starting the socketserver itself.
2018-06-22 19:22:38 +02:00

385 lines
13 KiB
Python
Executable file

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
import argparse
import codecs
import locale
import os
import sys
from jinja2 import Environment, FileSystemLoader
import pytz
try:
import tzlocal
_DEFAULT_TIMEZONE = tzlocal.get_localzone().zone
except ImportError:
_DEFAULT_TIMEZONE = 'Europe/Paris'
import six
from pelican import __version__
locale.setlocale(locale.LC_ALL, '')
try:
_DEFAULT_LANGUAGE = locale.getlocale()[0]
except ValueError:
# Don't fail on macosx: "unknown locale: UTF-8"
_DEFAULT_LANGUAGE = None
if _DEFAULT_LANGUAGE is None:
_DEFAULT_LANGUAGE = 'English'
else:
_DEFAULT_LANGUAGE = _DEFAULT_LANGUAGE.split('_')[0]
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"templates")
_jinja_env = Environment(
loader=FileSystemLoader(_TEMPLATES_DIR),
trim_blocks=True,
)
_GITHUB_PAGES_BRANCHES = {
'personal': 'master',
'project': 'gh-pages'
}
CONF = {
'pelican': 'pelican',
'pelicanopts': '',
'basedir': os.curdir,
'ftp_host': 'localhost',
'ftp_user': 'anonymous',
'ftp_target_dir': '/',
'ssh_host': 'localhost',
'ssh_port': 22,
'ssh_user': 'root',
'ssh_target_dir': '/var/www',
's3_bucket': 'my_s3_bucket',
'cloudfiles_username': 'my_rackspace_username',
'cloudfiles_api_key': 'my_rackspace_api_key',
'cloudfiles_container': 'my_cloudfiles_container',
'dropbox_dir': '~/Dropbox/Public/',
'github_pages_branch': _GITHUB_PAGES_BRANCHES['project'],
'default_pagination': 10,
'siteurl': '',
'lang': _DEFAULT_LANGUAGE,
'timezone': _DEFAULT_TIMEZONE
}
# url for list of valid timezones
_TZ_URL = 'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones'
def _input_compat(prompt):
if six.PY3:
r = input(prompt)
else:
r = raw_input(prompt)
return r
if six.PY3:
str_compat = str
else:
str_compat = unicode
# Create a 'marked' default path, to determine if someone has supplied
# a path on the command-line.
class _DEFAULT_PATH_TYPE(str_compat):
is_default_path = True
_DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir)
def decoding_strings(f):
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if isinstance(out, six.string_types) and not six.PY3:
# todo: make encoding configurable?
if six.PY3:
return out
else:
return out.decode(sys.stdin.encoding)
return out
return wrapper
@decoding_strings
def ask(question, answer=str_compat, default=None, length=None):
if answer == str_compat:
r = ''
while True:
if default:
r = _input_compat('> {0} [{1}] '.format(question, default))
else:
r = _input_compat('> {0} '.format(question, default))
r = r.strip()
if len(r) <= 0:
if default:
r = default
break
else:
print('You must enter something')
else:
if length and len(r) != length:
print('Entry must be {0} characters long'.format(length))
else:
break
return r
elif answer == bool:
r = None
while True:
if default is True:
r = _input_compat('> {0} (Y/n) '.format(question))
elif default is False:
r = _input_compat('> {0} (y/N) '.format(question))
else:
r = _input_compat('> {0} (y/n) '.format(question))
r = r.strip().lower()
if r in ('y', 'yes'):
r = True
break
elif r in ('n', 'no'):
r = False
break
elif not r:
r = default
break
else:
print("You must answer 'yes' or 'no'")
return r
elif answer == int:
r = None
while True:
if default:
r = _input_compat('> {0} [{1}] '.format(question, default))
else:
r = _input_compat('> {0} '.format(question))
r = r.strip()
if not r:
r = default
break
try:
r = int(r)
break
except ValueError:
print('You must enter an integer')
return r
else:
raise NotImplemented(
'Argument `answer` must be str_compat, bool, or integer')
def ask_timezone(question, default, tzurl):
"""Prompt for time zone and validate input"""
lower_tz = [tz.lower() for tz in pytz.all_timezones]
while True:
r = ask(question, str_compat, default)
r = r.strip().replace(' ', '_').lower()
if r in lower_tz:
r = pytz.all_timezones[lower_tz.index(r)]
break
else:
print('Please enter a valid time zone:\n'
' (check [{0}])'.format(tzurl))
return r
def main():
parser = argparse.ArgumentParser(
description="A kickstarter for Pelican",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-p', '--path', default=_DEFAULT_PATH,
help="The path to generate the blog into")
parser.add_argument('-t', '--title', metavar="title",
help='Set the title of the website')
parser.add_argument('-a', '--author', metavar="author",
help='Set the author name of the website')
parser.add_argument('-l', '--lang', metavar="lang",
help='Set the default web site language')
args = parser.parse_args()
print('''Welcome to pelican-quickstart v{v}.
This script will help you create a new Pelican-based website.
Please answer the following questions so this script can generate the files
needed by Pelican.
'''.format(v=__version__))
project = os.path.join(
os.environ.get('VIRTUAL_ENV', os.curdir), '.project')
no_path_was_specified = hasattr(args.path, 'is_default_path')
if os.path.isfile(project) and no_path_was_specified:
CONF['basedir'] = open(project, 'r').read().rstrip("\n")
print('Using project associated with current virtual environment.'
'Will save to:\n%s\n' % CONF['basedir'])
else:
CONF['basedir'] = os.path.abspath(os.path.expanduser(
ask('Where do you want to create your new web site?',
answer=str_compat, default=args.path)))
CONF['sitename'] = ask('What will be the title of this web site?',
answer=str_compat, default=args.title)
CONF['author'] = ask('Who will be the author of this web site?',
answer=str_compat, default=args.author)
CONF['lang'] = ask('What will be the default language of this web site?',
str_compat, args.lang or CONF['lang'], 2)
if ask('Do you want to specify a URL prefix? e.g., https://example.com ',
answer=bool, default=True):
CONF['siteurl'] = ask('What is your URL prefix? (see '
'above example; no trailing slash)',
str_compat, CONF['siteurl'])
CONF['with_pagination'] = ask('Do you want to enable article pagination?',
bool, bool(CONF['default_pagination']))
if CONF['with_pagination']:
CONF['default_pagination'] = ask('How many articles per page '
'do you want?',
int, CONF['default_pagination'])
else:
CONF['default_pagination'] = False
CONF['timezone'] = ask_timezone('What is your time zone?',
CONF['timezone'], _TZ_URL)
automation = ask('Do you want to generate a Fabfile/Makefile '
'to automate generation and publishing?', bool, True)
if automation:
if ask('Do you want to upload your website using FTP?',
answer=bool, default=False):
CONF['ftp'] = True,
CONF['ftp_host'] = ask('What is the hostname of your FTP server?',
str_compat, CONF['ftp_host'])
CONF['ftp_user'] = ask('What is your username on that server?',
str_compat, CONF['ftp_user'])
CONF['ftp_target_dir'] = ask('Where do you want to put your '
'web site on that server?',
str_compat, CONF['ftp_target_dir'])
if ask('Do you want to upload your website using SSH?',
answer=bool, default=False):
CONF['ssh'] = True,
CONF['ssh_host'] = ask('What is the hostname of your SSH server?',
str_compat, CONF['ssh_host'])
CONF['ssh_port'] = ask('What is the port of your SSH server?',
int, CONF['ssh_port'])
CONF['ssh_user'] = ask('What is your username on that server?',
str_compat, CONF['ssh_user'])
CONF['ssh_target_dir'] = ask('Where do you want to put your '
'web site on that server?',
str_compat, CONF['ssh_target_dir'])
if ask('Do you want to upload your website using Dropbox?',
answer=bool, default=False):
CONF['dropbox'] = True,
CONF['dropbox_dir'] = ask('Where is your Dropbox directory?',
str_compat, CONF['dropbox_dir'])
if ask('Do you want to upload your website using S3?',
answer=bool, default=False):
CONF['s3'] = True,
CONF['s3_bucket'] = ask('What is the name of your S3 bucket?',
str_compat, CONF['s3_bucket'])
if ask('Do you want to upload your website using '
'Rackspace Cloud Files?', answer=bool, default=False):
CONF['cloudfiles'] = True,
CONF['cloudfiles_username'] = ask('What is your Rackspace '
'Cloud username?', str_compat,
CONF['cloudfiles_username'])
CONF['cloudfiles_api_key'] = ask('What is your Rackspace '
'Cloud API key?', str_compat,
CONF['cloudfiles_api_key'])
CONF['cloudfiles_container'] = ask('What is the name of your '
'Cloud Files container?',
str_compat,
CONF['cloudfiles_container'])
if ask('Do you want to upload your website using GitHub Pages?',
answer=bool, default=False):
CONF['github'] = True,
if ask('Is this your personal page (username.github.io)?',
answer=bool, default=False):
CONF['github_pages_branch'] = \
_GITHUB_PAGES_BRANCHES['personal']
else:
CONF['github_pages_branch'] = \
_GITHUB_PAGES_BRANCHES['project']
try:
os.makedirs(os.path.join(CONF['basedir'], 'content'))
except OSError as e:
print('Error: {0}'.format(e))
try:
os.makedirs(os.path.join(CONF['basedir'], 'output'))
except OSError as e:
print('Error: {0}'.format(e))
try:
with codecs.open(os.path.join(CONF['basedir'], 'pelicanconf.py'),
'w', 'utf-8') as fd:
conf_python = dict()
for key, value in CONF.items():
conf_python[key] = repr(value)
_template = _jinja_env.get_template('pelicanconf.py.jinja2')
fd.write(_template.render(**conf_python))
fd.close()
except OSError as e:
print('Error: {0}'.format(e))
try:
with codecs.open(os.path.join(CONF['basedir'], 'publishconf.py'),
'w', 'utf-8') as fd:
_template = _jinja_env.get_template('publishconf.py.jinja2')
fd.write(_template.render(**CONF))
fd.close()
except OSError as e:
print('Error: {0}'.format(e))
if automation:
try:
with codecs.open(os.path.join(CONF['basedir'], 'fabfile.py'),
'w', 'utf-8') as fd:
_template = _jinja_env.get_template('fabfile.py.jinja2')
fd.write(_template.render(**CONF))
fd.close()
except OSError as e:
print('Error: {0}'.format(e))
try:
with codecs.open(os.path.join(CONF['basedir'], 'Makefile'),
'w', 'utf-8') as fd:
py_v = 'python'
if six.PY3:
py_v = 'python3'
_template = _jinja_env.get_template('Makefile.jinja2')
fd.write(_template.render(py_v=py_v, **CONF))
fd.close()
except OSError as e:
print('Error: {0}'.format(e))
print('Done. Your new project is available at %s' % CONF['basedir'])
if __name__ == "__main__":
main()