Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

vasara: yet another static site generator
  • Loading branch information...
commit 5923c231eb09580e3b0f6d3727dd7bb52994ba5e 0 parents
Veeti Paananen authored
20 .gitignore
@@ -0,0 +1,20 @@
+*.py[co]
+
+# Distribute
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Virtualenv
+env
+
+# Test output
+vasara/tests/test_site/output
14 LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2012 rojekti
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 README.rst
@@ -0,0 +1,10 @@
+=========================================
+vasara: yet another static site generator
+=========================================
+
+**vasara** is a static site generator written in Python.
+
+License
+-------
+
+See ``LICENSE``.
18 setup.py
@@ -0,0 +1,18 @@
+from setuptools import setup
+
+setup(
+ name="vasara",
+ version="0.1dev",
+ license="MIT",
+ packages=["vasara"],
+ test_suite="vasara.tests",
+
+ install_requires=[],
+ tests_require=[],
+
+ entry_points={
+ "console_scripts": [
+ "vsra = vasara.cli:main",
+ ]
+ }
+)
7 vasara/__init__.py
@@ -0,0 +1,7 @@
+import compiler
+import site
+import item
+
+Compiler = compiler.Compiler
+Site = site.Site
+Item = item.Item
33 vasara/cli/__init__.py
@@ -0,0 +1,33 @@
+import sys
+import os
+import argparse
+
+def main():
+ compiler = get_compiler()
+ parser = argparse.ArgumentParser(description="A static site generator.")
+ subparsers = parser.add_subparsers(dest="command")
+
+ compile = subparsers.add_parser("compile")
+
+ #server = subparsers.add_parser("server")
+ #server.add_argument("--port", type=int, default=8000)
+ #server.add_argument("--listen", type=str, default="127.0.0.1")
+
+ args = parser.parse_args()
+ if args.command == "compile":
+ compiler.compile()
+ print "Compiled."
+
+# TODO: Hacky. Is there a better way to do this?
+def get_compiler():
+ """Attempts to import a function called get_vasara_compiler from the current working
+ directory. This will be used by the command-line tools."""
+ sys.path.append(os.getcwd())
+ try:
+ result = reload(__import__("__init__"))
+ if not hasattr(result, "get_vasara_compiler"):
+ raise ImportError
+ except ImportError as e:
+ print e
+ sys.exit("A site doesn't seem to exist in the working directory. Exiting.")
+ return result.get_vasara_compiler()
29 vasara/compiler.py
@@ -0,0 +1,29 @@
+from vasara.item import Item
+from vasara.site import Site
+
+import io
+import os
+
+class Compiler(object):
+
+ def __init__(self, site, output_path):
+ self.site = site
+ self.output_path = output_path
+
+ def compile(self):
+ if not os.path.exists(self.output_path):
+ os.makedirs(self.output_path)
+
+ for key, item in self.site.items.iteritems():
+ content = item.templated
+ route = item.route
+
+ path = os.path.join(self.output_path, route)
+ path_dir = os.path.dirname(path)
+
+ if not os.path.exists(path_dir):
+ os.makedirs(path_dir)
+
+ file = io.open(path, "w")
+ file.write(content)
+ file.close()
44 vasara/item.py
@@ -0,0 +1,44 @@
+import json
+import re
+
+# This regex pattern has been shamelessly lifted from Mynt, licensed under the
+# BSD license. Mynt is available at https://github.com/Anomareh/mynt
+MATCHER = re.compile(r"\A---\s+^(.+?)$\s+---\s*(.*)\Z", re.M | re.S)
+
+class Item(object):
+
+ def __init__(self, key, site, raw, route=None):
+ self.key = key
+ self.site = site
+ self.raw_content = raw
+ self.route = route
+ self.filters = []
+ self.filtered = False
+ self.templater = None
+
+ # Read data
+ matches = MATCHER.match(self.raw_content).groups()
+ self.metadata = json.loads(matches[0])
+ self.raw_content = matches[1]
+ self.filtered_content = self.raw_content
+
+ def filter(self):
+ """Runs all the specified filters on the item. For convenience, returns itself."""
+ if self.filtered is False:
+ for filter in self.filters:
+ filter(self)
+
+ self.filtered = True
+ return self
+
+ @property
+ def content(self):
+ return self.filter().filtered_content
+
+ @property
+ def templated(self):
+ # Make sure that the item has been filtered
+ self.filter()
+ if self.templater is None:
+ return self.content
+ return self.templater(self)
52 vasara/site.py
@@ -0,0 +1,52 @@
+import os
+import io
+import re
+import sys
+from vasara.item import Item
+
+class Site(object):
+
+ def __init__(self, base_path, items_path):
+ self.base_path = base_path
+ self.items_path = items_path
+ self.items = {}
+ self.scan()
+
+ def scan(self):
+ """Scans the site's items path for items."""
+ path = os.path.abspath(self.items_path)
+ for dirpath, dirnames, filenames in os.walk(path):
+ for file in filenames:
+ full = os.path.abspath(os.path.join(dirpath, file))
+ key = os.path.splitext(full[len(path):])[0][1:]
+
+ # If on Windows, fix path separators in the key
+ if sys.platform == "win32":
+ key = key.replace("\\", "/")
+
+ self.items[key] = Item(key=key, site=self, raw=io.open(full, "r").read())
+
+ def match(self, expression):
+ """Matches the site's items against the specified regular expression
+ and returns them in a list of tuples; the first item in a tuple being the
+ match object and the second being the item itself."""
+ exp = re.compile(expression)
+ keys = self.items.keys()
+ items = []
+ for key in keys:
+ match = exp.match(key)
+ if match:
+ items.append((match, self.items[key]))
+ return items
+
+ def route(self, expression, callable):
+ for match, item in self.match(expression):
+ item.route = callable(match, item)
+
+ def filter(self, expression, filter):
+ for match, item in self.match(expression):
+ item.filters.append(filter)
+
+ def template(self, expression, templater):
+ for match, item in self.match(expression):
+ item.templater = templater
0  vasara/tests/__init__.py
No changes.
9 vasara/tests/common.py
@@ -0,0 +1,9 @@
+from vasara.site import Site
+
+import os
+
+TEST_SITE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_site")
+
+def build_test_site():
+ site = Site(base_path=TEST_SITE, items_path=os.path.join(TEST_SITE, "items"))
+ return site
37 vasara/tests/test_compiler.py
@@ -0,0 +1,37 @@
+from unittest import TestCase
+from vasara.item import Item
+from vasara.site import Site
+from vasara.compiler import Compiler
+
+from common import build_test_site, TEST_SITE
+
+import os
+import shutil
+import sys
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+class TestCompiler(TestCase):
+
+ def setUp(self):
+ self.site = build_test_site()
+ self.compiler = Compiler(site=self.site, output_path=os.path.join(TEST_SITE, "output"))
+
+ if HERE not in self.compiler.output_path:
+ sys.exit("Something is terribly wrong. {}, {}".format(HERE, self.compiler.output_path))
+
+ if os.path.exists(self.compiler.output_path):
+ shutil.rmtree(self.compiler.output_path)
+
+ def test_routing(self):
+ """Ensures that compilation results are routed properly."""
+ self.site.route(r"(.*)", lambda match, item: "{}/index.html".format(match.group(1)))
+ self.site.route(r"index", lambda match, item: "index.html")
+ self.compiler.compile()
+
+ output = self.compiler.output_path
+ self.assertTrue(os.path.exists(os.path.join(output)))
+ self.assertTrue(os.path.exists(os.path.join(output, "index.html")))
+ self.assertTrue(os.path.exists(os.path.join(output, "test", "index.html")))
+ self.assertTrue(os.path.exists(os.path.join(output, "test", "test", "index.html")))
+ self.assertTrue(os.path.exists(os.path.join(output, "test", "test", "test", "index.html")))
53 vasara/tests/test_item.py
@@ -0,0 +1,53 @@
+from unittest import TestCase
+from vasara.item import Item
+
+TEST_ITEM = """---
+{
+ "name": "Test",
+ "list": [1, 2, 3]
+}
+---
+
+Hello, world! This is the actual content."""
+
+class TestItem(TestCase):
+
+ def setUp(self):
+ self.site = None # TODO
+ self.item = Item(key="test", site=self.site, raw=TEST_ITEM)
+
+ def test_content_matcher(self):
+ """Tests that all metadata and actual content is properly parsed from input."""
+ self.assertEqual(self.item.metadata["name"], "Test")
+ self.assertEqual(self.item.metadata["list"], [1, 2, 3])
+ self.assertEqual(self.item.raw_content, "Hello, world! This is the actual content.")
+
+ def test_filter_not_twice(self):
+ """Ensures that items don't filter themselves twice."""
+ def increment_filter(item):
+ if "counter" in item.metadata:
+ item.metadata["counter"] += 1
+ else:
+ item.metadata["counter"] = 1
+
+ self.item.filters.append(increment_filter)
+ self.item.filter()
+ self.item.filter() # Shouldn't do anything!
+
+ self.assertEqual(1, self.item.metadata["counter"])
+
+ def test_content_property(self):
+ """Ensures that the item is filtered if the content property is retrieved."""
+ def replacer_filter(item):
+ item.filtered_content = "Unit Testing!"
+
+ self.item.filters.append(replacer_filter)
+ self.assertEqual("Unit Testing!", self.item.content)
+
+ def test_templater_property(self):
+ """Ensure that the item is templated if the templated property is retrieved."""
+ def templater(item):
+ return "Hello, test_templater_property!"
+
+ self.item.templater = templater
+ self.assertEqual("Hello, test_templater_property!", self.item.templated)
60 vasara/tests/test_site.py
@@ -0,0 +1,60 @@
+from unittest import TestCase
+from vasara.site import Site
+from common import build_test_site
+
+import os
+
+class TestSite(TestCase):
+
+ def setUp(self):
+ self.site = build_test_site()
+
+ def test_scan(self):
+ """Ensures that the item structure of a site is properly scanned."""
+ self.assertIn("index", self.site.items)
+ self.assertIn("test/test", self.site.items)
+ self.assertIn("test/test/test", self.site.items)
+
+ def test_match_items(self):
+ """Ensures that the item matcher functions."""
+ # Returns a tuple
+ matches = self.site.match(r"(.*)")
+ self.assertIsInstance(matches[0], tuple)
+
+ # Returns match data and the item itself
+ for match, item in matches:
+ self.assertEqual(match.group(1), item.key)
+
+ def test_route(self):
+ """Ensures that routes are set properly on items."""
+
+ # Set a global route for all items
+ self.site.route(r"(.*)", lambda match, item: "{}/index.html".format(match.group(1)))
+ # Override the index item's route
+ self.site.route(r"index", lambda match, item: "index.html")
+
+ self.assertEqual("index.html", self.site.items["index"].route)
+ self.assertEqual("test/test/index.html", self.site.items["test/test"].route)
+
+ def test_filter(self):
+ """Ensures that filters are set properly on items."""
+
+ # Set a global filter for all items
+ self.site.filter(r"(.*)", lambda item: item)
+ # Set another filter on the index item
+ self.site.filter(r"index", lambda item: item)
+
+ self.assertEqual(2, len(self.site.items["index"].filters))
+ self.assertEqual(1, len(self.site.items["test/test"].filters))
+
+ def test_templater(self):
+ """Ensures that templaters are set properly on items."""
+
+ # Set a global templater for all items
+ self.site.template(r"(.*)", lambda item: "ALL")
+ # Set another templater on the index item
+ self.site.template(r"index", lambda item: "INDEX")
+
+ # Since an item can only have one templater, the index templater should have been overwritten
+ self.assertEqual("INDEX", self.site.items["index"].templated)
+ self.assertEqual("ALL", self.site.items["test/test"].templated)
6 vasara/tests/test_site/items/index.html
@@ -0,0 +1,6 @@
+---
+{
+}
+---
+
+<h1>Nothing to see here.</h1>
6 vasara/tests/test_site/items/test.html
@@ -0,0 +1,6 @@
+---
+{
+}
+---
+
+<h1>Nothing to see here.</h1>
6 vasara/tests/test_site/items/test/test.html
@@ -0,0 +1,6 @@
+---
+{
+}
+---
+
+<h1>Nothing to see here.</h1>
6 vasara/tests/test_site/items/test/test/test.html
@@ -0,0 +1,6 @@
+---
+{
+}
+---
+
+<h1>Nothing to see here.</h1>
Please sign in to comment.
Something went wrong with that request. Please try again.