diff --git a/pecan/scaffolds/__init__.py b/pecan/scaffolds/__init__.py index f2d65a9..1da3a44 100644 --- a/pecan/scaffolds/__init__.py +++ b/pecan/scaffolds/__init__.py @@ -10,10 +10,30 @@ _bad_chars_re = re.compile('[^a-zA-Z0-9_]') class PecanScaffold(object): + """ + A base Pecan scaffold. New scaffolded implementations should extend this + class and define a ``_scaffold_dir`` attribute, e.g., - def copy_to(self, dest, **kwargs): - output_dir = os.path.abspath(os.path.normpath(dest)) - pkg_name = _bad_chars_re.sub('', dest.lower()) + class CoolAddOnScaffold(PecanScaffold): + + _scaffold_dir = ('package', os.path.join('scaffolds', 'scaffold_name')) + + ...where... + + pkg_resources.resource_listdir(_scaffold_dir[0], _scaffold_dir[1])) + + ...points to some scaffold directory root. + """ + + def normalize_output_dir(self, dest): + return os.path.abspath(os.path.normpath(dest)) + + def normalize_pkg_name(self, dest): + return _bad_chars_re.sub('', dest.lower()) + + def copy_to(self, dest): + output_dir = self.normalize_output_dir(dest) + pkg_name = self.normalize_pkg_name(dest) copy_dir(self._scaffold_dir, output_dir, {'package': pkg_name}) @@ -25,14 +45,15 @@ def copy_dir(source, dest, variables, out_=sys.stdout, i=0): """ Copies the ``source`` directory to the ``dest`` directory, where ``source`` is some tuple representing an installed package and a - subdirectory, e.g., + subdirectory in the package, e.g., ('pecan', os.path.join('scaffolds', 'base')) - ('pecan_sqlalchemy', os.path.join('scaffolds', 'sqlalchemy')) + ('pecan_extension', os.path.join('scaffolds', 'scaffold_name')) ``variables``: A dictionary of variables to use in any substitutions. + Substitution is performed via ``string.Template``. - ``out_``: File object to write to + ``out_``: File object to write to (default is sys.stdout). """ def out(msg): out_.write('%s%s' % (' ' * (i * 2), msg)) @@ -77,6 +98,7 @@ def copy_dir(source, dest, variables, out_=sys.stdout, i=0): def makedirs(directory): + """ Resursively create a named directory. """ parent = os.path.dirname(os.path.abspath(directory)) if not os.path.exists(parent): makedirs(parent) @@ -84,14 +106,17 @@ def makedirs(directory): def substitute_filename(fn, variables): + """ Substitute +variables+ in file directory names. """ for var, value in variables.items(): fn = fn.replace('+%s+' % var, str(value)) return fn def render_template(content, variables): - """ Return a bytestring representing a templated file based on the - input (content) and the variable names defined (vars).""" + """ + Return a bytestring representing a templated file based on the + input (content) and the variable names defined (vars). + """ fsenc = sys.getfilesystemencoding() content = native_(content, fsenc) return bytes_(Template(content).substitute(variables), fsenc) diff --git a/pecan/tests/scaffold_fixtures/__init__.py b/pecan/tests/scaffold_fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl b/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl new file mode 100644 index 0000000..95a9c91 --- /dev/null +++ b/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl @@ -0,0 +1 @@ +Pecan ${package} diff --git a/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl b/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl new file mode 100644 index 0000000..25591f3 --- /dev/null +++ b/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl @@ -0,0 +1 @@ +YAR ${package} diff --git a/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt b/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt new file mode 100644 index 0000000..02c61ad --- /dev/null +++ b/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt @@ -0,0 +1 @@ +Pecan diff --git a/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ b/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ new file mode 100644 index 0000000..035599b --- /dev/null +++ b/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ @@ -0,0 +1 @@ +YAR diff --git a/pecan/tests/scaffold_fixtures/simple/bar/spam.txt b/pecan/tests/scaffold_fixtures/simple/bar/spam.txt new file mode 100644 index 0000000..02c61ad --- /dev/null +++ b/pecan/tests/scaffold_fixtures/simple/bar/spam.txt @@ -0,0 +1 @@ +Pecan diff --git a/pecan/tests/scaffold_fixtures/simple/foo b/pecan/tests/scaffold_fixtures/simple/foo new file mode 100644 index 0000000..035599b --- /dev/null +++ b/pecan/tests/scaffold_fixtures/simple/foo @@ -0,0 +1 @@ +YAR diff --git a/pecan/tests/test_scaffolds.py b/pecan/tests/test_scaffolds.py index ad13941..aa2e7bb 100644 --- a/pecan/tests/test_scaffolds.py +++ b/pecan/tests/test_scaffolds.py @@ -12,18 +12,155 @@ import pecan if sys.version_info < (2, 7): import unittest2 as unittest else: - import unittest + import unittest # noqa def has_internet(): try: - response = urllib2.urlopen('http://google.com', timeout=1) + urllib2.urlopen('http://google.com', timeout=1) return True except urllib2.URLError: pass # pragma: no cover return False +class TestPecanScaffold(unittest.TestCase): + + def test_normalize_pkg_name(self): + from pecan.scaffolds import PecanScaffold + s = PecanScaffold() + assert s.normalize_pkg_name('sam') == 'sam' + assert s.normalize_pkg_name('sam1') == 'sam1' + assert s.normalize_pkg_name('sam_') == 'sam_' + assert s.normalize_pkg_name('Sam') == 'sam' + assert s.normalize_pkg_name('SAM') == 'sam' + assert s.normalize_pkg_name('sam ') == 'sam' + assert s.normalize_pkg_name(' sam') == 'sam' + assert s.normalize_pkg_name('sam$') == 'sam' + assert s.normalize_pkg_name('sam-sam') == 'samsam' + + +class TestScaffoldUtils(unittest.TestCase): + + def setUp(self): + self.scaffold_destination = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.scaffold_destination) + + def test_copy_dir(self): + from pecan.scaffolds import PecanScaffold + + class SimpleScaffold(PecanScaffold): + _scaffold_dir = ('pecan', os.path.join( + 'tests', 'scaffold_fixtures', 'simple' + )) + + SimpleScaffold().copy_to(os.path.join( + self.scaffold_destination, + 'someapp' + )) + + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'foo' + )) + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'bar', 'spam.txt' + )) + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'foo' + ), 'r').read().strip() == 'YAR' + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'foo' + ), 'r').read().strip() == 'YAR' + + def test_destination_directory_levels_deep(self): + from pecan.scaffolds import copy_dir + from cStringIO import StringIO + f = StringIO() + copy_dir(('pecan', os.path.join( + 'tests', 'scaffold_fixtures', 'simple' + )), + os.path.join(self.scaffold_destination, 'some', 'app'), + {}, + out_=f + ) + + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'some', 'app', 'foo') + ) + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt') + ) + assert open(os.path.join( + self.scaffold_destination, 'some', 'app', 'foo' + ), 'r').read().strip() == 'YAR' + assert open(os.path.join( + self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt' + ), 'r').read().strip() == 'Pecan' + + def test_destination_directory_already_exists(self): + from pecan.scaffolds import copy_dir + from cStringIO import StringIO + f = StringIO() + copy_dir(('pecan', os.path.join( + 'tests', 'scaffold_fixtures', 'simple' + )), + os.path.join(self.scaffold_destination), + {}, + out_=f + ) + assert 'already exists' in f.getvalue() + + def test_copy_dir_with_filename_substitution(self): + from pecan.scaffolds import copy_dir + copy_dir(('pecan', os.path.join( + 'tests', 'scaffold_fixtures', 'file_sub' + )), + os.path.join( + self.scaffold_destination, 'someapp' + ), + {'package': 'thingy'} + ) + + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'foo_thingy') + ) + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt') + ) + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'foo_thingy' + ), 'r').read().strip() == 'YAR' + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt' + ), 'r').read().strip() == 'Pecan' + + def test_copy_dir_with_file_content_substitution(self): + from pecan.scaffolds import copy_dir + copy_dir(('pecan', os.path.join( + 'tests', 'scaffold_fixtures', 'content_sub' + )), + os.path.join( + self.scaffold_destination, 'someapp' + ), + {'package': 'thingy'} + ) + + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'foo') + ) + assert os.path.isfile(os.path.join( + self.scaffold_destination, 'someapp', 'bar', 'spam.txt') + ) + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'foo' + ), 'r').read().strip() == 'YAR thingy' + assert open(os.path.join( + self.scaffold_destination, 'someapp', 'bar', 'spam.txt' + ), 'r').read().strip() == 'Pecan thingy' + + class TestTemplateBuilds(unittest.TestCase): """ Used to build and test the templated quickstart project(s). @@ -86,7 +223,7 @@ class TestTemplateBuilds(unittest.TestCase): @unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.') @unittest.skipUnless( getattr(pecan, '__run_all_tests__', False) is True, - 'Skipping (really slow). To run, `$ python setup.py test --functional.`' + 'Skipping (slow). To run, `$ python setup.py test --functional.`' ) def test_project_pecan_serve_command(self): pecan_exe = os.path.join(self.install_dir, 'bin', 'pecan') @@ -113,10 +250,9 @@ class TestTemplateBuilds(unittest.TestCase): @unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.') @unittest.skipUnless( getattr(pecan, '__run_all_tests__', False) is True, - 'Skipping (really slow). To run, `$ python setup.py test --functional.`' + 'Skipping (slow). To run, `$ python setup.py test --functional.`' ) def test_project_pecan_shell_command(self): - from pecan.testing import load_test_app pecan_exe = os.path.join(self.install_dir, 'bin', 'pecan') # Start the server @@ -148,7 +284,7 @@ class TestTemplateBuilds(unittest.TestCase): @unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.') @unittest.skipUnless( getattr(pecan, '__run_all_tests__', False) is True, - 'Skipping (really slow). To run, `$ python setup.py test --functional.`' + 'Skipping (slow). To run, `$ python setup.py test --functional.`' ) def test_project_tests_command(self): py_exe = os.path.join(self.install_dir, 'bin', 'python')