forked from github/pelican
401 lines
12 KiB
Python
Executable file
401 lines
12 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
import argparse
|
|
import locale
|
|
import os
|
|
from typing import Mapping
|
|
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
try:
|
|
import zoneinfo
|
|
except ModuleNotFoundError:
|
|
from backports import zoneinfo
|
|
|
|
try:
|
|
import readline # NOQA
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
import tzlocal
|
|
|
|
if hasattr(tzlocal.get_localzone(), "zone"):
|
|
_DEFAULT_TIMEZONE = tzlocal.get_localzone().zone
|
|
else:
|
|
_DEFAULT_TIMEZONE = tzlocal.get_localzone_name()
|
|
except ModuleNotFoundError:
|
|
_DEFAULT_TIMEZONE = "Europe/Rome"
|
|
|
|
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,
|
|
keep_trailing_newline=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): # noqa: SLOT000
|
|
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(f"> {question} [{default}] ")
|
|
else:
|
|
r = input(f"> {question} ")
|
|
|
|
r = r.strip()
|
|
|
|
if len(r) <= 0:
|
|
if default:
|
|
r = default
|
|
break
|
|
else:
|
|
print("You must enter something")
|
|
elif length and len(r) != length:
|
|
print(f"Entry must be {length} characters long")
|
|
else:
|
|
break
|
|
|
|
return r
|
|
|
|
elif answer == bool:
|
|
r = None
|
|
while True:
|
|
if default is True:
|
|
r = input(f"> {question} (Y/n) ")
|
|
elif default is False:
|
|
r = input(f"> {question} (y/N) ")
|
|
else:
|
|
r = input(f"> {question} (y/n) ")
|
|
|
|
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(f"> {question} [{default}] ")
|
|
else:
|
|
r = input(f"> {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"""
|
|
tz_dict = {tz.lower(): tz for tz in zoneinfo.available_timezones()}
|
|
while True:
|
|
r = ask(question, str, default)
|
|
r = r.strip().replace(" ", "_").lower()
|
|
if r in tz_dict.keys():
|
|
r = tz_dict[r]
|
|
break
|
|
else:
|
|
print(f"Please enter a valid time zone:\n (check [{tzurl}])")
|
|
return r
|
|
|
|
|
|
def render_jinja_template(tmpl_name: str, tmpl_vars: Mapping, target_path: str):
|
|
try:
|
|
with open(
|
|
os.path.join(CONF["basedir"], target_path), "w", encoding="utf-8"
|
|
) as fd:
|
|
_template = _jinja_env.get_template(tmpl_name)
|
|
fd.write(_template.render(**tmpl_vars))
|
|
except OSError as e:
|
|
print(f"Error: {e}")
|
|
|
|
|
|
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(
|
|
f"""Welcome to pelican-quickstart v{__version__}.
|
|
|
|
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.
|
|
|
|
"""
|
|
)
|
|
|
|
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{}\n".format(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(f"Error: {e}")
|
|
|
|
try:
|
|
os.makedirs(os.path.join(CONF["basedir"], "output"))
|
|
except OSError as e:
|
|
print(f"Error: {e}")
|
|
|
|
conf_python = {}
|
|
for key, value in CONF.items():
|
|
conf_python[key] = repr(value)
|
|
render_jinja_template("pelicanconf.py.jinja2", conf_python, "pelicanconf.py")
|
|
|
|
render_jinja_template("publishconf.py.jinja2", CONF, "publishconf.py")
|
|
|
|
if automation:
|
|
render_jinja_template("tasks.py.jinja2", CONF, "tasks.py")
|
|
render_jinja_template("Makefile.jinja2", CONF, "Makefile")
|
|
|
|
print("Done. Your new project is available at {}".format(CONF["basedir"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|