355 lines
12 KiB
Python
355 lines
12 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import argparse
|
||
|
import locale
|
||
|
import os
|
||
|
|
||
|
from jinja2 import Environment, FileSystemLoader
|
||
|
|
||
|
import pytz
|
||
|
|
||
|
try:
|
||
|
import readline # NOQA
|
||
|
except ImportError:
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
import tzlocal
|
||
|
_DEFAULT_TIMEZONE = tzlocal.get_localzone().zone
|
||
|
except ImportError:
|
||
|
_DEFAULT_TIMEZONE = 'Europe/Paris'
|
||
|
|
||
|
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 = 'en'
|
||
|
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': 'main',
|
||
|
'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 = 'https://en.wikipedia.org/wiki/List_of_tz_database_time_zones'
|
||
|
|
||
|
|
||
|
# Create a 'marked' default path, to determine if someone has supplied
|
||
|
# a path on the command-line.
|
||
|
class _DEFAULT_PATH_TYPE(str):
|
||
|
is_default_path = True
|
||
|
|
||
|
|
||
|
_DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir)
|
||
|
|
||
|
|
||
|
def ask(question, answer=str, default=None, length=None):
|
||
|
if answer == str:
|
||
|
r = ''
|
||
|
while True:
|
||
|
if default:
|
||
|
r = input('> {} [{}] '.format(question, default))
|
||
|
else:
|
||
|
r = input('> {} '.format(question))
|
||
|
|
||
|
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 {} characters long'.format(length))
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
return r
|
||
|
|
||
|
elif answer == bool:
|
||
|
r = None
|
||
|
while True:
|
||
|
if default is True:
|
||
|
r = input('> {} (Y/n) '.format(question))
|
||
|
elif default is False:
|
||
|
r = input('> {} (y/N) '.format(question))
|
||
|
else:
|
||
|
r = input('> {} (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('> {} [{}] '.format(question, default))
|
||
|
else:
|
||
|
r = input('> {} '.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 NotImplementedError(
|
||
|
'Argument `answer` must be str, 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, 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 [{}])'.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).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, default=args.path)))
|
||
|
|
||
|
CONF['sitename'] = ask('What will be the title of this web site?',
|
||
|
answer=str, default=args.title)
|
||
|
CONF['author'] = ask('Who will be the author of this web site?',
|
||
|
answer=str, default=args.author)
|
||
|
CONF['lang'] = ask('What will be the default language of this web site?',
|
||
|
str, 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, 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 tasks.py/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, CONF['ftp_host'])
|
||
|
CONF['ftp_user'] = ask('What is your username on that server?',
|
||
|
str, CONF['ftp_user'])
|
||
|
CONF['ftp_target_dir'] = ask('Where do you want to put your '
|
||
|
'web site on that server?',
|
||
|
str, 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, 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, CONF['ssh_user'])
|
||
|
CONF['ssh_target_dir'] = ask('Where do you want to put your '
|
||
|
'web site on that server?',
|
||
|
str, 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, 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, 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,
|
||
|
CONF['cloudfiles_username'])
|
||
|
CONF['cloudfiles_api_key'] = ask('What is your Rackspace '
|
||
|
'Cloud API key?', str,
|
||
|
CONF['cloudfiles_api_key'])
|
||
|
CONF['cloudfiles_container'] = ask('What is the name of your '
|
||
|
'Cloud Files container?',
|
||
|
str,
|
||
|
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: {}'.format(e))
|
||
|
|
||
|
try:
|
||
|
os.makedirs(os.path.join(CONF['basedir'], 'output'))
|
||
|
except OSError as e:
|
||
|
print('Error: {}'.format(e))
|
||
|
|
||
|
try:
|
||
|
with open(os.path.join(CONF['basedir'], 'pelicanconf.py'),
|
||
|
'w', encoding='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: {}'.format(e))
|
||
|
|
||
|
try:
|
||
|
with open(os.path.join(CONF['basedir'], 'publishconf.py'),
|
||
|
'w', encoding='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: {}'.format(e))
|
||
|
|
||
|
if automation:
|
||
|
try:
|
||
|
with open(os.path.join(CONF['basedir'], 'tasks.py'),
|
||
|
'w', encoding='utf-8') as fd:
|
||
|
_template = _jinja_env.get_template('tasks.py.jinja2')
|
||
|
fd.write(_template.render(**CONF))
|
||
|
fd.close()
|
||
|
except OSError as e:
|
||
|
print('Error: {}'.format(e))
|
||
|
try:
|
||
|
with open(os.path.join(CONF['basedir'], 'Makefile'),
|
||
|
'w', encoding='utf-8') as fd:
|
||
|
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: {}'.format(e))
|
||
|
|
||
|
print('Done. Your new project is available at %s' % CONF['basedir'])
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|