@@ 15,6 15,8 @@ from docutils.core import publish_parts
# TODO: ie http-equiv tag
# TODO: generator meta tag?
+# TODO: header with home link
+# TODO: footer with mod date info
default_template = string.Template('''<!DOCTYPE html>
<html><head>
$favicon
@@ 170,6 172,7 @@ class DeadSimpleSite(object):
shutil.copy2(source, target)
def serve(self, port=8000):
+ SocketServer.TCPServer.allow_reuse_address = True
server = SocketServer.TCPServer(('', port), LiveSiteHandler)
server.site = self
server.serve_forever()
@@ 189,6 192,7 @@ class DeadSimpleSite(object):
if not origin:
raise GitError('Origin required to clone remote repository')
try:
+ # TODO: if the github repo is not initialized, clone fails, so make a first commit
self._git('clone', origin, '.')
except subprocess.CalledProcessError as e:
raise GitError(e)
@@ 196,6 200,7 @@ class DeadSimpleSite(object):
try:
self._git('--version')
except subprocess.CalledProcessError as e:
+ # TODO: on nix, this raises OSError
raise GitError('No git command found. Is git installed and on the path?')
self._clean()
if not path.exists(self.target):
@@ 221,6 226,7 @@ class DeadSimpleSite(object):
self.compile()
self._git('add', '-A')
self._git('commit', '-m', 'Dead Simple Site auto publish')
+ # TODO: revert commit if push fails
self._git('push', '-u', 'origin', 'gh-pages')
except subprocess.CalledProcessError as e:
raise GitError(e)
@@ 1,291 1,292 @@
-import os
-from os import path
-import re
-import shutil
-import subprocess
-import unittest
-
-import dss
-from dss import DeadSimpleSite
-
-def page_contain_assertion(testcase, filepath, invert=False):
- with open(filepath) as f:
- filetext = f.read()
- def page_contains(pattern):
- testcase.assertTrue(invert != bool(re.search(pattern, filetext, re.DOTALL)))
- return page_contains
-
-def clean(directory):
- if path.exists(directory):
- shutil.rmtree(directory)
-
-class TestCompile(unittest.TestCase):
-
- def setUp(self):
- clean('test/_site')
-
- def test_canary(self):
- ''' Do nothing '''
-
- def test_copy(self):
- self.assertFalse(path.exists('test/_site'))
- DeadSimpleSite('test').compile()
- self.assertTrue(path.exists('test/_site'))
- self.assertTrue(path.exists('test/_site/favicon.ico'))
- self.assertTrue(path.exists('test/_site/subdir/plain.html'))
-
- def test_render_default(self):
- DeadSimpleSite('test').compile()
- self.assertFalse(path.exists('test/_site/index.rst'))
- page_contains = page_contain_assertion(self, 'test/_site/index.html')
- # TODO: inline markup in title?
- page_contains(r'<head>.*<title>Becomes Title</title>.*</head>')
- page_contains(r'<body>.*Becomes content.*</body>')
- page_contains(r'<link rel="stylesheet" href="style/global\.css">')
- # TODO: html escape js paths
- page_contains(r'<script src="js/first/foo\.js"></script>')
- page_contains(r'<script src="js/bar\.js"></script>')
- page_contains(r'<link rel="shortcut icon" href="favicon.ico">')
-
- def test_render_default_subdir(self):
- DeadSimpleSite('test').compile()
- page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html')
- page_contains(r'<link rel="stylesheet" href="\.\./style/global\.css">')
- # TODO: test for script alphabetical ordering
- # Notice this tests script depth ordering:
- page_contains(r'<script src="\.\./js/first/foo\.js"></script>.*<script src="\.\./js/bar\.js"></script>.*')
- # TODO: rst includes
- DeadSimpleSite('test').compile()
- not_page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html', invert=True)
- # Tests that recompiling does not pick up scripts from the output directory
- not_page_contains(r'<script src=.*_site')
-
- def test_render_custom(self):
- DeadSimpleSite('test').compile()
- page_contains = page_contain_assertion(self, 'test/_site/custom.html')
- page_contains(r'<link rel="stylesheet" href="style/global\.css">')
- page_contains(r'<script src="js/first/foo\.js"></script>')
- page_contains(r'<script src="js/bar\.js"></script>')
- page_contains(r'Title From Content')
- page_contains(r'ReStructured Text content')
- page_contains(r'<p>Some html content</p>')
-
- def test_cleanup(self):
- ''' Compiling removes files and directories that do not exist in the source '''
- os.makedirs('test/_site/empty')
- open('test/_site/empty/bar', 'w').close()
- DeadSimpleSite('test').compile()
- self.assertFalse(path.exists('test/_site/empty/bar'))
- self.assertFalse(path.exists('test/_site/empty'))
-
- def test_ignore_dotfiles(self):
- ''' Ignores any files that begin with "." except .htaccess '''
- # TODO: worry about hidden files and such on windows?
- os.makedirs('test/_site/.ignored-empty-target-folder')
- os.makedirs('test/_site/.ignored-full-target-folder')
- open('test/_site/.ignored-target-file', 'w').close()
- open('test/_site/.ignored-full-target-folder/non-dotfile', 'w').close()
- DeadSimpleSite('test').compile()
- self.assertTrue(path.exists('test/_site/.htaccess'))
- self.assertTrue(path.exists('test/_site/subdir/.htaccess'))
- self.assertFalse(path.exists('test/_site/.ignored'))
- self.assertFalse(path.exists('test/_site/.ignored-folder'))
- self.assertTrue(path.exists('test/_site/.ignored-empty-target-folder'))
- self.assertTrue(path.exists('test/_site/.ignored-target-file'))
- self.assertTrue(path.exists('test/_site/.ignored-full-target-folder/non-dotfile'))
- not_page_contains = page_contain_assertion(self, 'test/_site/index.html', invert=True)
- not_page_contains(r'<script src=.*anything\.js')
- not_page_contains(r'\.ignored\.js')
-
-class TestBarrenCompile(unittest.TestCase):
-
- def setUp(self):
- clean('test-barren/_site')
-
- def test_render_barren(self):
- DeadSimpleSite('test-barren').compile()
- not_contains = page_contain_assertion(self, 'test-barren/_site/index.html', True)
- not_contains('<link rel="stylesheet"')
- not_contains('<script')
- not_contains('favicon')
-
- def test_cleans_htaccess(self):
- os.makedirs('test-barren/_site')
- open('test-barren/_site/.htaccess', 'w').close()
- DeadSimpleSite('test-barren').compile()
- self.assertFalse(path.exists('test-barren/_site/.htaccess'))
-
-class TestPublish(unittest.TestCase):
-
- prep_commands = [
- ('--version',),
- ('rev-parse', '--git-dir')]
- update_commands = [
- ('checkout', 'gh-pages'),
- ('pull',),
- ('add', '-A'),
- ('commit', '-m', 'Dead Simple Site auto publish'),
- ('push', '-u', 'origin', 'gh-pages',)]
- clone_commands = prep_commands \
- + [('clone', 'https://example.com/user/repo.git', '.'), ('reset', '--hard')] \
- + update_commands
-
- remote = 'https://example.com/user/repo.git'
-
- def setUp(self):
- self._real_git = DeadSimpleSite._git
-
- self.fail_git = None
- self.gitdir = '.git'
- self.site = DeadSimpleSite('test')
- self.git_invocations = []
-
- clean('test/_site')
- self.site.compile() # make sure the output dir does not start clean, to test cleanup
-
- def git(site, *gitargs):
- self.git_invocations.append(gitargs)
- if (gitargs[0] == self.fail_git):
- self.fail_git = None
- raise subprocess.CalledProcessError('foo', 'bar', 'baz')
- if (gitargs == ('rev-parse', '--git-dir')):
- self.assertTrue(path.exists(self.site.target))
- return self.gitdir
- if (gitargs[0] == 'clone'):
- self.assertTrue(gitargs[1] != None)
- if (gitargs[0] == 'reset'):
- self.assertFalse(path.exists('test/_site/index.html'))
- if (gitargs[0] == 'add'):
- self.assertTrue(path.exists('test/_site/index.html'))
- DeadSimpleSite._git = git
-
- def tearDown(self):
- DeadSimpleSite._git = self._real_git
-
- def test_creates_target(self):
- clean('test/_site')
- self.site.publish()
-
- def test_no_git(self):
- self.fail_git = '--version'
- with self.assertRaisesRegexp(dss.GitError, 'No git command found'):
- self.site.publish()
-
- def test_arbitrary_git_error(self):
- self.fail_git = 'reset'
- with self.assertRaisesRegexp(dss.GitError, 'baz'):
- self.site.publish()
-
- def test_existing_repo(self):
- self.site.publish()
- self.assertEqual(self.prep_commands + [('reset', '--hard')] + self.update_commands,
- self.git_invocations)
-
- def test_clone_when_source_in_git(self):
- self.gitdir = self.site.source + '/.git'
- self.site.publish(self.remote)
- self.assertEqual(self.clone_commands, self.git_invocations)
-
- def test_clone_when_source_not_in_git(self):
- self.fail_git = 'rev-parse'
- self.site.publish(self.remote)
- self.assertEqual(self.clone_commands, self.git_invocations)
-
- def test_clone_error_no_origin(self):
- self.gitdir = self.site.source + '/.git'
- with self.assertRaisesRegexp(dss.GitError, 'Origin required to clone remote repository'):
- self.site.publish()
-
- def test_clone_error_wrapping(self):
- self.gitdir = self.site.source + '/.git'
- self.fail_git = 'clone'
- with self.assertRaisesRegexp(dss.GitError, 'baz'):
- self.site.publish(self.remote)
-
- def test_no_gh_pages_branch(self):
- self.fail_git = 'checkout'
- self.site.publish()
- self.assertEqual(self.prep_commands
- + [ ('reset', '--hard'),
- ('checkout', 'gh-pages'),
- ('branch', 'gh-pages')]
- + [c for c in self.update_commands if c != ('pull',)],
- self.git_invocations)
-
-class TestTemplate(unittest.TestCase):
-
- def setUp(self):
- clean('test-templates/_site')
-
- def test_template_nest(self):
- DeadSimpleSite('test-templates').compile()
- self.assertFalse(path.exists('test-templates/_site/__template__.html'))
- self.assertFalse(path.exists('test-templates/_site/subdir/subsubdir/__template__.html'))
- index_contains = page_contain_assertion(self, 'test-templates/_site/index.html')
- index_contains(r'<a href="index\.html">Home</a>')
- sub_contains = page_contain_assertion(self, 'test-templates/_site/subdir/root-template.html')
- sub_contains(r'<a href="\.\./index\.html">Home</a>')
- not_sub_sub_contains = page_contain_assertion(self,
- 'test-templates/_site/subdir/subsubdir/nested.html', invert=True)
- not_sub_sub_contains(r'index\.html')
-
-class TestCli(unittest.TestCase):
-
- def setUp(self):
- self.compiled = None
- self.served = None
- self.served_on = None
- self.origin = None
-
- def dummy_compile(site):
- self.compiled = site.source
-
- def dummy_serve(site, port):
- self.served = site.source
- self.served_on = port
-
- def dummy_publish(site, origin):
- self.origin = origin
-
- self._real_compile = DeadSimpleSite.compile
- self._real_serve = DeadSimpleSite.serve
- self._real_publish = DeadSimpleSite.publish
- DeadSimpleSite.serve = dummy_serve
- DeadSimpleSite.compile = dummy_compile
- DeadSimpleSite.publish = dummy_publish
-
- def tearDown(self):
- DeadSimpleSite.serve = self._real_serve
- DeadSimpleSite.compile = self._real_compile
- DeadSimpleSite.publish = self._real_publish
-
- def test_compile(self):
- dss.cli(['compile'])
- self.assertTrue(self.compiled == path.abspath('.'))
-
- def test_serve(self):
- dss.cli(['serve'])
- self.assertTrue(self.served == path.abspath('.'))
- self.assertTrue(self.served_on == 8000)
-
- def test_path_arg(self):
- # TODO: SimpleHTTPRequestHandler still thinks its root is pwd
- dss.cli(['--directory', 'test', 'serve'])
- self.assertTrue(self.served == path.abspath('test'))
- dss.cli(['-d', 'test', 'serve'])
- self.assertTrue(self.served == path.abspath('test'))
-
- def test_port_arg(self):
- dss.cli(['serve', '-p', '8001'])
- self.assertTrue(self.served_on == 8001)
-
- def test_publish(self):
- dss.cli(['publish'])
- self.assertTrue(self.origin == None)
- dss.cli(['publish', 'http://example.com/user/repo'])
- self.assertTrue(self.origin == 'http://example.com/user/repo')
-
- # TODO: tests for errors
-
-if __name__ == '__main__':
- unittest.main()
+#!/usr/bin/env python
+import os
+from os import path
+import re
+import shutil
+import subprocess
+import unittest
+
+import dss
+from dss import DeadSimpleSite
+
+def page_contain_assertion(testcase, filepath, invert=False):
+ with open(filepath) as f:
+ filetext = f.read()
+ def page_contains(pattern):
+ testcase.assertTrue(invert != bool(re.search(pattern, filetext, re.DOTALL)))
+ return page_contains
+
+def clean(directory):
+ if path.exists(directory):
+ shutil.rmtree(directory)
+
+class TestCompile(unittest.TestCase):
+
+ def setUp(self):
+ clean('test/_site')
+
+ def test_canary(self):
+ ''' Do nothing '''
+
+ def test_copy(self):
+ self.assertFalse(path.exists('test/_site'))
+ DeadSimpleSite('test').compile()
+ self.assertTrue(path.exists('test/_site'))
+ self.assertTrue(path.exists('test/_site/favicon.ico'))
+ self.assertTrue(path.exists('test/_site/subdir/plain.html'))
+
+ def test_render_default(self):
+ DeadSimpleSite('test').compile()
+ self.assertFalse(path.exists('test/_site/index.rst'))
+ page_contains = page_contain_assertion(self, 'test/_site/index.html')
+ # TODO: inline markup in title?
+ page_contains(r'<head>.*<title>Becomes Title</title>.*</head>')
+ page_contains(r'<body>.*Becomes content.*</body>')
+ page_contains(r'<link rel="stylesheet" href="style/global\.css">')
+ # TODO: html escape js paths
+ page_contains(r'<script src="js/first/foo\.js"></script>')
+ page_contains(r'<script src="js/bar\.js"></script>')
+ page_contains(r'<link rel="shortcut icon" href="favicon.ico">')
+
+ def test_render_default_subdir(self):
+ DeadSimpleSite('test').compile()
+ page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html')
+ page_contains(r'<link rel="stylesheet" href="\.\./style/global\.css">')
+ # TODO: test for script alphabetical ordering
+ # Notice this tests script depth ordering:
+ page_contains(r'<script src="\.\./js/first/foo\.js"></script>.*<script src="\.\./js/bar\.js"></script>.*')
+ # TODO: rst includes
+ DeadSimpleSite('test').compile()
+ not_page_contains = page_contain_assertion(self, 'test/_site/subdir/page.html', invert=True)
+ # Tests that recompiling does not pick up scripts from the output directory
+ not_page_contains(r'<script src=.*_site')
+
+ def test_render_custom(self):
+ DeadSimpleSite('test').compile()
+ page_contains = page_contain_assertion(self, 'test/_site/custom.html')
+ page_contains(r'<link rel="stylesheet" href="style/global\.css">')
+ page_contains(r'<script src="js/first/foo\.js"></script>')
+ page_contains(r'<script src="js/bar\.js"></script>')
+ page_contains(r'Title From Content')
+ page_contains(r'ReStructured Text content')
+ page_contains(r'<p>Some html content</p>')
+
+ def test_cleanup(self):
+ ''' Compiling removes files and directories that do not exist in the source '''
+ os.makedirs('test/_site/empty')
+ open('test/_site/empty/bar', 'w').close()
+ DeadSimpleSite('test').compile()
+ self.assertFalse(path.exists('test/_site/empty/bar'))
+ self.assertFalse(path.exists('test/_site/empty'))
+
+ def test_ignore_dotfiles(self):
+ ''' Ignores any files that begin with "." except .htaccess '''
+ # TODO: worry about hidden files and such on windows?
+ os.makedirs('test/_site/.ignored-empty-target-folder')
+ os.makedirs('test/_site/.ignored-full-target-folder')
+ open('test/_site/.ignored-target-file', 'w').close()
+ open('test/_site/.ignored-full-target-folder/non-dotfile', 'w').close()
+ DeadSimpleSite('test').compile()
+ self.assertTrue(path.exists('test/_site/.htaccess'))
+ self.assertTrue(path.exists('test/_site/subdir/.htaccess'))
+ self.assertFalse(path.exists('test/_site/.ignored'))
+ self.assertFalse(path.exists('test/_site/.ignored-folder'))
+ self.assertTrue(path.exists('test/_site/.ignored-empty-target-folder'))
+ self.assertTrue(path.exists('test/_site/.ignored-target-file'))
+ self.assertTrue(path.exists('test/_site/.ignored-full-target-folder/non-dotfile'))
+ not_page_contains = page_contain_assertion(self, 'test/_site/index.html', invert=True)
+ not_page_contains(r'<script src=.*anything\.js')
+ not_page_contains(r'\.ignored\.js')
+
+class TestBarrenCompile(unittest.TestCase):
+
+ def setUp(self):
+ clean('test-barren/_site')
+
+ def test_render_barren(self):
+ DeadSimpleSite('test-barren').compile()
+ not_contains = page_contain_assertion(self, 'test-barren/_site/index.html', True)
+ not_contains('<link rel="stylesheet"')
+ not_contains('<script')
+ not_contains('favicon')
+
+ def test_cleans_htaccess(self):
+ os.makedirs('test-barren/_site')
+ open('test-barren/_site/.htaccess', 'w').close()
+ DeadSimpleSite('test-barren').compile()
+ self.assertFalse(path.exists('test-barren/_site/.htaccess'))
+
+class TestPublish(unittest.TestCase):
+
+ prep_commands = [
+ ('--version',),
+ ('rev-parse', '--git-dir')]
+ update_commands = [
+ ('checkout', 'gh-pages'),
+ ('pull',),
+ ('add', '-A'),
+ ('commit', '-m', 'Dead Simple Site auto publish'),
+ ('push', '-u', 'origin', 'gh-pages',)]
+ clone_commands = prep_commands \
+ + [('clone', 'https://example.com/user/repo.git', '.'), ('reset', '--hard')] \
+ + update_commands
+
+ remote = 'https://example.com/user/repo.git'
+
+ def setUp(self):
+ self._real_git = DeadSimpleSite._git
+
+ self.fail_git = None
+ self.gitdir = '.git'
+ self.site = DeadSimpleSite('test')
+ self.git_invocations = []
+
+ clean('test/_site')
+ self.site.compile() # make sure the output dir does not start clean, to test cleanup
+
+ def git(site, *gitargs):
+ self.git_invocations.append(gitargs)
+ if (gitargs[0] == self.fail_git):
+ self.fail_git = None
+ raise subprocess.CalledProcessError('foo', 'bar', 'baz')
+ if (gitargs == ('rev-parse', '--git-dir')):
+ self.assertTrue(path.exists(self.site.target))
+ return self.gitdir
+ if (gitargs[0] == 'clone'):
+ self.assertTrue(gitargs[1] != None)
+ if (gitargs[0] == 'reset'):
+ self.assertFalse(path.exists('test/_site/index.html'))
+ if (gitargs[0] == 'add'):
+ self.assertTrue(path.exists('test/_site/index.html'))
+ DeadSimpleSite._git = git
+
+ def tearDown(self):
+ DeadSimpleSite._git = self._real_git
+
+ def test_creates_target(self):
+ clean('test/_site')
+ self.site.publish()
+
+ def test_no_git(self):
+ self.fail_git = '--version'
+ with self.assertRaisesRegexp(dss.GitError, 'No git command found'):
+ self.site.publish()
+
+ def test_arbitrary_git_error(self):
+ self.fail_git = 'reset'
+ with self.assertRaisesRegexp(dss.GitError, 'baz'):
+ self.site.publish()
+
+ def test_existing_repo(self):
+ self.site.publish()
+ self.assertEqual(self.prep_commands + [('reset', '--hard')] + self.update_commands,
+ self.git_invocations)
+
+ def test_clone_when_source_in_git(self):
+ self.gitdir = self.site.source + '/.git'
+ self.site.publish(self.remote)
+ self.assertEqual(self.clone_commands, self.git_invocations)
+
+ def test_clone_when_source_not_in_git(self):
+ self.fail_git = 'rev-parse'
+ self.site.publish(self.remote)
+ self.assertEqual(self.clone_commands, self.git_invocations)
+
+ def test_clone_error_no_origin(self):
+ self.gitdir = self.site.source + '/.git'
+ with self.assertRaisesRegexp(dss.GitError, 'Origin required to clone remote repository'):
+ self.site.publish()
+
+ def test_clone_error_wrapping(self):
+ self.gitdir = self.site.source + '/.git'
+ self.fail_git = 'clone'
+ with self.assertRaisesRegexp(dss.GitError, 'baz'):
+ self.site.publish(self.remote)
+
+ def test_no_gh_pages_branch(self):
+ self.fail_git = 'checkout'
+ self.site.publish()
+ self.assertEqual(self.prep_commands
+ + [ ('reset', '--hard'),
+ ('checkout', 'gh-pages'),
+ ('branch', 'gh-pages')]
+ + [c for c in self.update_commands if c != ('pull',)],
+ self.git_invocations)
+
+class TestTemplate(unittest.TestCase):
+
+ def setUp(self):
+ clean('test-templates/_site')
+
+ def test_template_nest(self):
+ DeadSimpleSite('test-templates').compile()
+ self.assertFalse(path.exists('test-templates/_site/__template__.html'))
+ self.assertFalse(path.exists('test-templates/_site/subdir/subsubdir/__template__.html'))
+ index_contains = page_contain_assertion(self, 'test-templates/_site/index.html')
+ index_contains(r'<a href="index\.html">Home</a>')
+ sub_contains = page_contain_assertion(self, 'test-templates/_site/subdir/root-template.html')
+ sub_contains(r'<a href="\.\./index\.html">Home</a>')
+ not_sub_sub_contains = page_contain_assertion(self,
+ 'test-templates/_site/subdir/subsubdir/nested.html', invert=True)
+ not_sub_sub_contains(r'index\.html')
+
+class TestCli(unittest.TestCase):
+
+ def setUp(self):
+ self.compiled = None
+ self.served = None
+ self.served_on = None
+ self.origin = None
+
+ def dummy_compile(site):
+ self.compiled = site.source
+
+ def dummy_serve(site, port):
+ self.served = site.source
+ self.served_on = port
+
+ def dummy_publish(site, origin):
+ self.origin = origin
+
+ self._real_compile = DeadSimpleSite.compile
+ self._real_serve = DeadSimpleSite.serve
+ self._real_publish = DeadSimpleSite.publish
+ DeadSimpleSite.serve = dummy_serve
+ DeadSimpleSite.compile = dummy_compile
+ DeadSimpleSite.publish = dummy_publish
+
+ def tearDown(self):
+ DeadSimpleSite.serve = self._real_serve
+ DeadSimpleSite.compile = self._real_compile
+ DeadSimpleSite.publish = self._real_publish
+
+ def test_compile(self):
+ dss.cli(['compile'])
+ self.assertTrue(self.compiled == path.abspath('.'))
+
+ def test_serve(self):
+ dss.cli(['serve'])
+ self.assertTrue(self.served == path.abspath('.'))
+ self.assertTrue(self.served_on == 8000)
+
+ def test_path_arg(self):
+ # TODO: SimpleHTTPRequestHandler still thinks its root is pwd
+ dss.cli(['--directory', 'test', 'serve'])
+ self.assertTrue(self.served == path.abspath('test'))
+ dss.cli(['-d', 'test', 'serve'])
+ self.assertTrue(self.served == path.abspath('test'))
+
+ def test_port_arg(self):
+ dss.cli(['serve', '-p', '8001'])
+ self.assertTrue(self.served_on == 8001)
+
+ def test_publish(self):
+ dss.cli(['publish'])
+ self.assertTrue(self.origin == None)
+ dss.cli(['publish', 'http://example.com/user/repo'])
+ self.assertTrue(self.origin == 'http://example.com/user/repo')
+
+ # TODO: tests for errors
+
+if __name__ == '__main__':
+ unittest.main()