mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Move the quickstart generation into a function
Quickstart files/dirs were generated with a short script at the end of quickstart.main(), but this makes things really difficult to use from a script because of the interactive dialog. Move the generation of files into quickstart.quickstart() so that a script can just pass in a config and get a quickstart directory.
This commit is contained in:
parent
1b904ae767
commit
b883ba7f3b
4 changed files with 315 additions and 77 deletions
206
pelican/tests/test_tools.py
Normal file
206
pelican/tests/test_tools.py
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
# Standard lib
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from io import StringIO
|
||||
from tempfile import mkdtemp
|
||||
import contextlib
|
||||
|
||||
# Pelican
|
||||
from pelican.tools import pelican_quickstart
|
||||
from .support import unittest
|
||||
|
||||
# python2 doesn't have mock in the standard lib
|
||||
try:
|
||||
mock = unittest.mock
|
||||
except AttributeError:
|
||||
mock = None
|
||||
|
||||
if mock is None:
|
||||
try:
|
||||
import mock
|
||||
except ImportError:
|
||||
mock = None
|
||||
|
||||
|
||||
# Helper classes
|
||||
class FileSystemTest(unittest.TestCase):
|
||||
""" Make a temporary dir to test file I/O """
|
||||
|
||||
def setUp(self):
|
||||
prev_self = super(FileSystemTest, self)
|
||||
if hasattr(prev_self, 'setUp'):
|
||||
prev_self.setUp()
|
||||
|
||||
self.tempdir = mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
if os.path.isdir(self.tempdir):
|
||||
shutil.rmtree(self.tempdir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
prev_self = super(FileSystemTest, self)
|
||||
if hasattr(prev_self, 'tearDown'):
|
||||
prev_self.tearDown()
|
||||
|
||||
def assertIsFile(self, path):
|
||||
|
||||
msg = 'File not found: {}'.format(path)
|
||||
self.assertTrue(os.path.isfile(path), msg=msg)
|
||||
|
||||
def assertIsDir(self, path):
|
||||
|
||||
msg = 'Directory not found: {}'.format(path)
|
||||
self.assertTrue(os.path.isdir(path), msg=msg)
|
||||
|
||||
def assertPathDoesntExist(self, path):
|
||||
|
||||
msg = 'Path found unexpectedly: {}'.format(path)
|
||||
self.assertFalse(os.path.exists(path), msg=msg)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def record_output():
|
||||
""" Record the standard output of a command
|
||||
|
||||
Usage::
|
||||
|
||||
>>> with record_output() as out:
|
||||
>>> print('hiiiiiii')
|
||||
>>> out.getvalue()
|
||||
'hiiiiiii\n'
|
||||
"""
|
||||
|
||||
old_stdout = sys.stdout
|
||||
try:
|
||||
sys.stdout = StringIO()
|
||||
yield sys.stdout
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
|
||||
|
||||
# Tests
|
||||
class TestMakeDirs(FileSystemTest):
|
||||
|
||||
dirnames = ['content', 'output']
|
||||
|
||||
def test_make_dir_okay(self):
|
||||
|
||||
for dirname in self.dirnames:
|
||||
|
||||
dirpath = os.path.join(self.tempdir, dirname)
|
||||
|
||||
self.assertPathDoesntExist(dirpath)
|
||||
|
||||
pelican_quickstart.makedirs(dirpath)
|
||||
|
||||
self.assertIsDir(dirpath)
|
||||
|
||||
@unittest.skipIf(mock is None, "need to install mock")
|
||||
def test_make_dir_error(self):
|
||||
|
||||
dirpath = os.path.join(self.tempdir, 'evil')
|
||||
|
||||
with mock.patch('os.makedirs') as makedirs:
|
||||
makedirs.side_effect = OSError
|
||||
with record_output() as out:
|
||||
|
||||
pelican_quickstart.makedirs(dirpath)
|
||||
|
||||
self.assertTrue(out.getvalue().startswith('Error:'))
|
||||
|
||||
|
||||
class TestChmod(FileSystemTest):
|
||||
|
||||
def test_make_dir_okay(self):
|
||||
|
||||
filepath = os.path.join(self.tempdir, 'foo.txt')
|
||||
|
||||
with open(filepath, 'wt') as fp:
|
||||
fp.write('')
|
||||
|
||||
pelican_quickstart.chmod(filepath, 493) # 0o755
|
||||
|
||||
self.assertTrue(os.access(filepath, os.R_OK))
|
||||
self.assertTrue(os.access(filepath, os.W_OK))
|
||||
self.assertTrue(os.access(filepath, os.X_OK))
|
||||
|
||||
@unittest.skipIf(mock is None, "need to install mock")
|
||||
def test_make_dir_error(self):
|
||||
|
||||
with mock.patch('os.chmod') as chmod:
|
||||
chmod.side_effect = OSError
|
||||
with record_output() as out:
|
||||
pelican_quickstart.chmod('bad.txt', 493)
|
||||
|
||||
self.assertTrue(out.getvalue().startswith('Error:'))
|
||||
|
||||
|
||||
class TestMakeTemplate(FileSystemTest):
|
||||
|
||||
template_files = [
|
||||
'pelicanconf.py', 'publishconf.py', 'fabfile.py',
|
||||
'Makefile', 'develop_server.sh',
|
||||
]
|
||||
|
||||
def test_make_template(self):
|
||||
|
||||
for filename in self.template_files:
|
||||
|
||||
filepath = os.path.join(self.tempdir, filename)
|
||||
|
||||
self.assertPathDoesntExist(filepath)
|
||||
|
||||
pelican_quickstart.make_template(filepath, {})
|
||||
|
||||
self.assertIsFile(filepath)
|
||||
|
||||
@unittest.skipIf(mock is None, "need to install mock")
|
||||
def test_make_template_fails(self):
|
||||
|
||||
filepath = os.path.join(self.tempdir, 'pelicanconf.py')
|
||||
|
||||
with mock.patch('codecs.open') as codecs_open:
|
||||
codecs_open.side_effect = OSError
|
||||
with record_output() as out:
|
||||
|
||||
pelican_quickstart.make_template(filepath, {})
|
||||
|
||||
self.assertTrue(out.getvalue().startswith('Error:'))
|
||||
|
||||
|
||||
class TestShellEscape(unittest.TestCase):
|
||||
""" Shell escape things """
|
||||
|
||||
def test_escapes_boring_strings(self):
|
||||
|
||||
conf = {'foo': 'something',
|
||||
'bar': '"something"'}
|
||||
|
||||
res = pelican_quickstart.escape_shell(conf)
|
||||
|
||||
self.assertEqual(res, {'foo': 'something', 'bar': '"something"'})
|
||||
self.assertIsNot(res, conf)
|
||||
|
||||
def test_escapes_important_strings(self):
|
||||
|
||||
conf = {'foo': 'something with spaces'}
|
||||
|
||||
res = pelican_quickstart.escape_shell(conf)
|
||||
|
||||
self.assertEqual(res, {'foo': '"something with spaces"'})
|
||||
self.assertIsNot(res, conf)
|
||||
|
||||
def test_escapes_double_quotes(self):
|
||||
|
||||
conf = {'foo': 'something with "quotes"'}
|
||||
|
||||
res = pelican_quickstart.escape_shell(conf)
|
||||
|
||||
self.assertEqual(res, {'foo': r'"something with \"quotes\""'})
|
||||
self.assertIsNot(res, conf)
|
||||
|
|
@ -72,6 +72,73 @@ def get_template(name, as_encoding='utf-8'):
|
|||
fd.close()
|
||||
|
||||
|
||||
def makedirs(dirpath):
|
||||
""" Makedirs ignoring errors
|
||||
|
||||
:param dirpath:
|
||||
The path to the directory to create
|
||||
"""
|
||||
|
||||
try:
|
||||
os.makedirs(dirpath)
|
||||
except OSError as e:
|
||||
print('Error: {0}'.format(e))
|
||||
|
||||
|
||||
def chmod(path, mode):
|
||||
""" Chmod, ignoring errors
|
||||
|
||||
:param path:
|
||||
Path to change the mode on
|
||||
:param mode:
|
||||
Mode to change
|
||||
"""
|
||||
|
||||
try:
|
||||
os.chmod(path, mode)
|
||||
except OSError as e:
|
||||
print('Error: {0}'.format(e))
|
||||
|
||||
|
||||
def make_template(filepath, conf, as_encoding='utf-8'):
|
||||
""" Make a template in the output dir
|
||||
|
||||
:param filepath:
|
||||
The path to the output file
|
||||
:param conf:
|
||||
The dictionary of config parameters
|
||||
:param as_encoding:
|
||||
The file encoding to write (default: 'utf-8')
|
||||
"""
|
||||
|
||||
try:
|
||||
with codecs.open(filepath, 'w', as_encoding) as fd:
|
||||
filename = os.path.basename(filepath)
|
||||
for line in get_template(filename, as_encoding=as_encoding):
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(conf))
|
||||
fd.close()
|
||||
except OSError as e:
|
||||
print('Error: {0}'.format(e))
|
||||
|
||||
|
||||
def escape_shell(conf):
|
||||
""" Shell escape the keys in the config
|
||||
|
||||
:param conf:
|
||||
The config dictionary
|
||||
:returns:
|
||||
A copy of the dictionary with strings escaped
|
||||
"""
|
||||
|
||||
new_conf = {}
|
||||
for key, value in conf.items():
|
||||
if isinstance(value, six.string_types) and ' ' in value:
|
||||
value = '"' + value.replace('"', '\\"') + '"'
|
||||
new_conf[key] = value
|
||||
return new_conf
|
||||
|
||||
|
||||
@decoding_strings
|
||||
def ask(question, answer=str_compat, default=None, l=None):
|
||||
if answer == str_compat:
|
||||
|
|
@ -146,6 +213,43 @@ def ask(question, answer=str_compat, default=None, l=None):
|
|||
raise NotImplemented('Argument `answer` must be str_compat, bool, or integer')
|
||||
|
||||
|
||||
def quickstart(conf, automation=True, develop=True):
|
||||
""" Generate the templates
|
||||
|
||||
:param conf:
|
||||
The config to generate the templates with
|
||||
"""
|
||||
|
||||
if six.PY3:
|
||||
python_binary = 'python3'
|
||||
else:
|
||||
python_binary = 'python'
|
||||
|
||||
makedirs(os.path.join(conf['basedir'], 'content'))
|
||||
makedirs(os.path.join(conf['basedir'], 'output'))
|
||||
|
||||
conf_python = {key: repr(value) for key, value in conf.items()}
|
||||
|
||||
make_template(os.path.join(conf['basedir'], 'pelicanconf.py'), conf_python)
|
||||
make_template(os.path.join(conf['basedir'], 'publishconf.py'), conf)
|
||||
|
||||
if automation:
|
||||
|
||||
make_template(os.path.join(conf['basedir'], 'fabfile.py'), conf)
|
||||
|
||||
makefile_conf = dict(conf)
|
||||
makefile_conf['python_binary'] = python_binary
|
||||
|
||||
make_template(os.path.join(conf['basedir'], 'Makefile'), makefile_conf)
|
||||
|
||||
if develop:
|
||||
shell_conf = escape_shell(conf)
|
||||
shell_conf['python_binary'] = python_binary
|
||||
|
||||
make_template(os.path.join(conf['basedir'], 'develop_server.sh'), shell_conf)
|
||||
chmod(os.path.join(conf['basedir'], 'develop_server.sh'), 493) # mode 0o755
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="A kickstarter for Pelican",
|
||||
|
|
@ -211,83 +315,7 @@ needed by Pelican.
|
|||
if ask('Do you want to upload your website using S3?', answer=bool, default=False):
|
||||
CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', str_compat, CONF['s3_bucket'])
|
||||
|
||||
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)
|
||||
|
||||
for line in get_template('pelicanconf.py'):
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(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:
|
||||
for line in get_template('publishconf.py'):
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(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:
|
||||
for line in get_template('fabfile.py'):
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(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:
|
||||
mkfile_template_name = 'Makefile'
|
||||
py_v = 'PY=python'
|
||||
if six.PY3:
|
||||
py_v = 'PY=python3'
|
||||
template = string.Template(py_v)
|
||||
fd.write(template.safe_substitute(CONF))
|
||||
fd.write('\n')
|
||||
for line in get_template(mkfile_template_name):
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(CONF))
|
||||
fd.close()
|
||||
except OSError as e:
|
||||
print('Error: {0}'.format(e))
|
||||
|
||||
if develop:
|
||||
conf_shell = dict()
|
||||
for key, value in CONF.items():
|
||||
if isinstance(value, six.string_types) and ' ' in value:
|
||||
value = '"' + value.replace('"', '\\"') + '"'
|
||||
conf_shell[key] = value
|
||||
try:
|
||||
with codecs.open(os.path.join(CONF['basedir'], 'develop_server.sh'), 'w', 'utf-8') as fd:
|
||||
lines = list(get_template('develop_server.sh'))
|
||||
py_v = 'PY=python\n'
|
||||
if six.PY3:
|
||||
py_v = 'PY=python3\n'
|
||||
lines = lines[:4] + [py_v] + lines[4:]
|
||||
for line in lines:
|
||||
template = string.Template(line)
|
||||
fd.write(template.safe_substitute(conf_shell))
|
||||
fd.close()
|
||||
os.chmod((os.path.join(CONF['basedir'], 'develop_server.sh')), 493) # mode 0o755
|
||||
except OSError as e:
|
||||
print('Error: {0}'.format(e))
|
||||
quickstart(CONF, automation=automation, develop=develop)
|
||||
|
||||
print('Done. Your new project is available at %s' % CONF['basedir'])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
PY=$python_binary
|
||||
|
||||
PELICAN=$pelican
|
||||
PELICANOPTS=$pelicanopts
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
##
|
||||
# This section should match your Makefile
|
||||
##
|
||||
PY=$python_binary
|
||||
|
||||
PELICAN=$pelican
|
||||
PELICANOPTS=$pelicanopts
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue