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:
David Joy 2013-08-06 20:13:22 -04:00
commit b883ba7f3b
4 changed files with 315 additions and 77 deletions

206
pelican/tests/test_tools.py Normal file
View 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)

View file

@ -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'])

View file

@ -1,3 +1,5 @@
PY=$python_binary
PELICAN=$pelican
PELICANOPTS=$pelicanopts

View file

@ -2,6 +2,8 @@
##
# This section should match your Makefile
##
PY=$python_binary
PELICAN=$pelican
PELICANOPTS=$pelicanopts