noumenon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +85 -0
- data/Rakefile +9 -0
- data/lib/noumenon.rb +53 -0
- data/lib/noumenon/configuration.rb +26 -0
- data/lib/noumenon/core.rb +93 -0
- data/lib/noumenon/spec.rb +8 -0
- data/lib/noumenon/spec/fixtures.rb +34 -0
- data/lib/noumenon/template.rb +88 -0
- data/lib/noumenon/version.rb +3 -0
- data/noumenon.gemspec +27 -0
- data/spec/fixtures/fixture_specs/test +1 -0
- data/spec/fixtures/static_example/directory_with_index/index.yml +1 -0
- data/spec/fixtures/static_example/found.html +1 -0
- data/spec/fixtures/static_example/liquid_example.html +1 -0
- data/spec/fixtures/static_example/template_with_substitutions.html +1 -0
- data/spec/fixtures/static_example/templates/basic_example.yml +1 -0
- data/spec/fixtures/static_example/templates/with_fields.yml +2 -0
- data/spec/fixtures/static_example_dependencies/themes/example/assets/example.txt +1 -0
- data/spec/fixtures/static_example_dependencies/themes/example/layout.html +3 -0
- data/spec/fixtures/static_example_dependencies/themes/example_without_layout/basic_template.html +1 -0
- data/spec/fixtures/static_example_dependencies/themes/example_without_layout/fields.html +1 -0
- data/spec/fixtures/static_example_dependencies/themes/example_without_layout/template_with_fields.html +23 -0
- data/spec/noumenon/config_spec.rb +29 -0
- data/spec/noumenon/core_spec.rb +105 -0
- data/spec/noumenon/spec/fixtures_spec.rb +41 -0
- data/spec/noumenon/spec_spec.rb +7 -0
- data/spec/noumenon/template_spec.rb +118 -0
- data/spec/noumenon_spec.rb +61 -0
- data/spec/spec_helper.rb +19 -0
- data/watchr.rb +2 -0
- metadata +236 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Noumenon
|
2
|
+
|
3
|
+
Noumenon: Noun. The intellectual conception of a thing as it is in itself, not as it is known through perception.
|
4
|
+
|
5
|
+
## What is This Thing?
|
6
|
+
|
7
|
+
Noumenon is a web application based on Sinatra for constructing dynamic websites which use a Git repository
|
8
|
+
for storing all content. It's designed to allow technical and non-technical teams to collaborate on development and
|
9
|
+
populate of a content management system using the tools they are most comfortable with.
|
10
|
+
|
11
|
+
In the case of developers (and some designers) that's version control and text editors, while in the case of content
|
12
|
+
editors thats more likely to be a web interface.
|
13
|
+
|
14
|
+
## How it Works
|
15
|
+
|
16
|
+
The URL structure and content of a Noumenon site is defined by a Git repository, similar to the structure below:
|
17
|
+
|
18
|
+
/
|
19
|
+
- /config.rb
|
20
|
+
- /index.html
|
21
|
+
- /about
|
22
|
+
- /people.html
|
23
|
+
- /company.html
|
24
|
+
- /contact
|
25
|
+
- /config.rb
|
26
|
+
- /blog
|
27
|
+
- /config.rb
|
28
|
+
- /posts/2011-04-18-an-example-post.md
|
29
|
+
- /posts/2011-04-10-another-example.md
|
30
|
+
|
31
|
+
This git repository is then provided to Noumenon as it's data source. On startup it loads any file called "config.rb"
|
32
|
+
and uses it to determine how that directory should behave:
|
33
|
+
|
34
|
+
# /config.rb
|
35
|
+
domain "example.org"
|
36
|
+
application "git://github.com/noumenon/apps-static"
|
37
|
+
theme "git://github.com/noumenon/themes-example"
|
38
|
+
|
39
|
+
That example configures Noumenon to use the "Noumenon::Static" application to serve any templates below that point as
|
40
|
+
a static page.
|
41
|
+
|
42
|
+
# /contact/config.rb
|
43
|
+
application "git://github.com/noumenon/apps-contact"
|
44
|
+
contact.email_address "info@example.org"
|
45
|
+
|
46
|
+
While the one in /contact specifies that "Noumenon::Contact" should be used to provide the URL tree below /contact, in this
|
47
|
+
case a contact form which emails any responses to the specified address.
|
48
|
+
|
49
|
+
Finally /blogs/config.rb might look something like this:
|
50
|
+
|
51
|
+
application "git://github.com/noumenon/apps-blog"
|
52
|
+
blog.comments true
|
53
|
+
|
54
|
+
## Hosting a Noumenon Site
|
55
|
+
|
56
|
+
*This won't work yet: I havn't implemented automatic check out of a content repository.*
|
57
|
+
|
58
|
+
To host a site you will need to have Ruby and an application server such as Passenger, Unicorn or Thin. Due it's interactions
|
59
|
+
with git as a data store Heroku is not a supported platform for hosting, although I'm sure someone will find a way around that.
|
60
|
+
|
61
|
+
Install Noumenon: `gem install noumenon`
|
62
|
+
|
63
|
+
Create a directory, and put the following config.ru in it:
|
64
|
+
|
65
|
+
require 'noumenon'
|
66
|
+
Noumenon::Core.set :content_repository, "git@github.com:Noumenon/example.git"
|
67
|
+
|
68
|
+
run Noumenon.boot
|
69
|
+
|
70
|
+
And then set up your application server to provide that application. On startup it will first attempt to check out the content
|
71
|
+
repository, then it will parse any configuration files, and install any required dependencies. Finally, it will start hosting
|
72
|
+
the site.
|
73
|
+
|
74
|
+
To update the content repository restart your application server, which will cause it to update the repository, and any dependencies.
|
75
|
+
|
76
|
+
### Hosting on Heroku
|
77
|
+
|
78
|
+
The someone who worked out the way around it was me it seems. This will only work if you're deploying an entirely static site,
|
79
|
+
if you need the admin section to work then you'll have to host somewhere else, but otherwise, use a config.ru like this, and make
|
80
|
+
sure you have Noumenon in your Gemfile:
|
81
|
+
|
82
|
+
require 'noumenon'
|
83
|
+
Noumenon::Core.set :content_repository_path, File.expand_path("..", __FILE__)
|
84
|
+
|
85
|
+
run Noumenon.boot
|
data/Rakefile
ADDED
data/lib/noumenon.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'noumenon/configuration'
|
2
|
+
require 'noumenon/template'
|
3
|
+
require 'noumenon/core'
|
4
|
+
|
5
|
+
module Noumenon
|
6
|
+
class TemplateNotFoundError < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Returns a configured Noumenon application. Intended for use in a Rack configuration:
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# require 'noumenon'
|
14
|
+
# Noumenon::Core.set :content_repository_path, "/srv/sites/example.org"
|
15
|
+
#
|
16
|
+
# run Noumenon.boot
|
17
|
+
def boot
|
18
|
+
Rack::Builder.new do
|
19
|
+
Dir.glob(File.join(Noumenon.config.dependencies_path, "themes/*/assets")).each do |path|
|
20
|
+
theme = path.split("/")[-2]
|
21
|
+
url = "/themes/#{theme}"
|
22
|
+
|
23
|
+
map(url) { run Rack::File.new(path) }
|
24
|
+
end
|
25
|
+
|
26
|
+
map("/") { run Core.new }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Provides access to the configuration of this application.
|
31
|
+
#
|
32
|
+
# If passed a block then the current configuration will be yielded in the form
|
33
|
+
# of a Noumenon::Configuration instance, allowing it to be changed.
|
34
|
+
#
|
35
|
+
# Noumenon.config do |c|
|
36
|
+
# c.theme = "example_theme"
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def config
|
40
|
+
@config ||= Configuration.new
|
41
|
+
yield @config if block_given?
|
42
|
+
@config
|
43
|
+
end
|
44
|
+
alias :configure :config
|
45
|
+
|
46
|
+
# Set the application configuration
|
47
|
+
#
|
48
|
+
# Should be provided with something that implements the same interface as Noumenon::Configuration.
|
49
|
+
def config=(config)
|
50
|
+
@config = config
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Noumenon
|
2
|
+
# Handles configuration for a Noumenon application.
|
3
|
+
class Configuration
|
4
|
+
class ThemeNotFoundError < StandardError; end
|
5
|
+
|
6
|
+
# The path to load content from.
|
7
|
+
attr_accessor :content_repository_path
|
8
|
+
|
9
|
+
# The path to load any depdencies such as themes and applications from.
|
10
|
+
attr_accessor :dependencies_path
|
11
|
+
|
12
|
+
# The theme to use.
|
13
|
+
attr_accessor :theme
|
14
|
+
|
15
|
+
# Sets the current theme.
|
16
|
+
#
|
17
|
+
# If the theme could not be found under dependencies_path then a ThemeNotFoundError will be raised.
|
18
|
+
def theme=(value)
|
19
|
+
unless File.exist?(File.join(dependencies_path, "themes", value))
|
20
|
+
raise ThemeNotFoundError.new("The theme #{value} could not be found in #{dependencies_path}/themes.")
|
21
|
+
end
|
22
|
+
|
23
|
+
@theme = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'liquid'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# :nodoc:
|
6
|
+
module Noumenon
|
7
|
+
# The base of all Noumenon sites, responsible for building the URL structure, and providing
|
8
|
+
# access to the content repository.
|
9
|
+
#
|
10
|
+
# By default any request will be routed to the content repository to find an item of content
|
11
|
+
# using #locate_content, which will then be rendered using the template specified by the content
|
12
|
+
# file.
|
13
|
+
#
|
14
|
+
# A piece of content takes the form of a YAML file, which should provide any fields required by
|
15
|
+
# template specified. If any required fields are missing then a MissingContentError will be raised.
|
16
|
+
#
|
17
|
+
class Core < Sinatra::Base
|
18
|
+
def config
|
19
|
+
Noumenon.config
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get the path to a file in the content repository.
|
23
|
+
def content_path(path)
|
24
|
+
File.join(config.content_repository_path, path)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the path to a file provided by the current theme.
|
28
|
+
def theme_path(path)
|
29
|
+
File.join(config.dependencies_path, "themes", config.theme, path)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Render a template, surrounding it with the theme layout if a theme has been set, and
|
33
|
+
# it provides a layout.
|
34
|
+
#
|
35
|
+
# Any locals provided will be passed onto Liquid for use in the template.
|
36
|
+
#
|
37
|
+
# Valid options:
|
38
|
+
#
|
39
|
+
# layout: If set to a string, an attempt will be made to use that layout.
|
40
|
+
# If set to false no layout will be used.
|
41
|
+
# If set to true (the default) the default layout will be used.
|
42
|
+
def render_template(path, locals = {}, options = {})
|
43
|
+
options[:layout] = "layout" if options[:layout] == true || options[:layout].nil?
|
44
|
+
|
45
|
+
template = Template.from_file(path)
|
46
|
+
body = template.render(locals)
|
47
|
+
|
48
|
+
if options[:layout]
|
49
|
+
begin
|
50
|
+
body = render_template(theme_path("#{options[:layout]}.html"), { 'body' => body }, :layout => false)
|
51
|
+
rescue Noumenon::Template::NotFoundError => ignore_missing_layouts
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
body
|
56
|
+
end
|
57
|
+
|
58
|
+
# Render a piece of content from the content repository, using the template specified within
|
59
|
+
# the content item.
|
60
|
+
#
|
61
|
+
# If no template was specified then the template "default" will be used.
|
62
|
+
def render_content(path)
|
63
|
+
content = File.read(content_path(path))
|
64
|
+
item = YAML.load( content )
|
65
|
+
|
66
|
+
[ 200, render_template( theme_path("#{item["template"]}.html"), item ) ]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Locate a piece of content in the content repository based on the URL it is being accessed from.
|
70
|
+
#
|
71
|
+
# The following files will satisfy a request for "/example", in the order shown:
|
72
|
+
#
|
73
|
+
# 1. "repository/example.yml
|
74
|
+
# 2. "repository/example/index.yml
|
75
|
+
#
|
76
|
+
def locate_content_for(path)
|
77
|
+
if File.exists?(content_path("#{path}.yml"))
|
78
|
+
return "#{path}.yml"
|
79
|
+
else
|
80
|
+
if File.exists?(content_path(path)) && File.exists?(content_path("#{path}/index.yml"))
|
81
|
+
return "#{path}/index.yml"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
get '*' do |path|
|
89
|
+
path = locate_content_for(path)
|
90
|
+
path ? render_content(path) : [ 404, "Not Found" ]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Noumenon
|
4
|
+
module Spec
|
5
|
+
# Provides a simple way of mocking a content repository for specs.
|
6
|
+
module Fixtures
|
7
|
+
# The current path that fixtures are looked up from.
|
8
|
+
def fixture_path(path = nil)
|
9
|
+
@fixture_path ||= 'spec/fixtures'
|
10
|
+
path.nil? ? @fixture_path : File.join(@fixture_path, path)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the path that fixtures will be looked up from.
|
14
|
+
#
|
15
|
+
# Unless set to an absolute path the provided path will be assumed to be a sub-directory
|
16
|
+
# of "spec/fixtures".
|
17
|
+
def fixture_path=(value)
|
18
|
+
value = File.join("spec/fixtures", value) unless Pathname.new(value).absolute?
|
19
|
+
@fixture_path = value
|
20
|
+
end
|
21
|
+
|
22
|
+
# Loads a fixture from the specified path.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# File.write(File.join(fixture_path, "index.html"), "<h1>Hello, world!</h1>")
|
27
|
+
# fixture("index.html")
|
28
|
+
# # => "<h1>Hello, world!</h1>"
|
29
|
+
def fixture(path)
|
30
|
+
File.read File.join(fixture_path, path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
|
3
|
+
module Noumenon
|
4
|
+
# Templates specify the structure and presentation of a particular piece of content in
|
5
|
+
# a Noumenon site, and are usually provided by a theme.
|
6
|
+
#
|
7
|
+
# A template is split into two parts, the metadata, which defines what data is needed for
|
8
|
+
# the template, and a Liquid template, which defines how the data should be presented. When
|
9
|
+
# loaded from a file, a template will look like this:
|
10
|
+
#
|
11
|
+
# title:
|
12
|
+
# label: Page title
|
13
|
+
# required: true
|
14
|
+
# type: string
|
15
|
+
# help: The title displayed at the top of the page
|
16
|
+
# author:
|
17
|
+
# label: Author
|
18
|
+
# required: false
|
19
|
+
# type: string
|
20
|
+
# help: The author of the article. If not provided, no byline will be shown.
|
21
|
+
# body:
|
22
|
+
# label: Body text
|
23
|
+
# required: true
|
24
|
+
# type: text
|
25
|
+
# help: The main article body. Will be processed with Textile for formatting.
|
26
|
+
# ---
|
27
|
+
# <h1>{{ title }}</h1>
|
28
|
+
# {% if author %}
|
29
|
+
# <p class="byline">By {{ author }}</p>
|
30
|
+
# {% endif %}
|
31
|
+
# {{ body | textilize }}
|
32
|
+
#
|
33
|
+
# And can be rendered like this:
|
34
|
+
#
|
35
|
+
# Template.from_file("/path/to/template").render("title" => "An Example Page", "author" => "Jon Wood", "body" => "This is an article...")
|
36
|
+
#
|
37
|
+
# If any required fields are missing from the data provided then a MissingContentError will be raised.
|
38
|
+
class Template
|
39
|
+
class MissingContentError < StandardError; end
|
40
|
+
class NotFoundError < StandardError; end
|
41
|
+
|
42
|
+
# The location this template was loaded from.
|
43
|
+
attr_accessor :source
|
44
|
+
|
45
|
+
# The template view.
|
46
|
+
attr_accessor :content
|
47
|
+
|
48
|
+
# The fields used by this template.
|
49
|
+
attr_accessor :fields
|
50
|
+
|
51
|
+
# Loads a template from the specified path.
|
52
|
+
#
|
53
|
+
# If the file does not exist then a NotFoundError will be raised.
|
54
|
+
def self.from_file(path)
|
55
|
+
raise NotFoundError.new("No template could be found at #{path}.") unless File.exists?(path)
|
56
|
+
|
57
|
+
content = File.read(path)
|
58
|
+
|
59
|
+
parts = content.split("\n---\n")
|
60
|
+
if parts.size == 1
|
61
|
+
fields = {}
|
62
|
+
else
|
63
|
+
fields = YAML.load(parts.first)
|
64
|
+
content = parts.last
|
65
|
+
end
|
66
|
+
|
67
|
+
self.new(path, content, fields)
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(source = nil, content = nil, fields = {})
|
71
|
+
@source = source
|
72
|
+
@content = content
|
73
|
+
@fields = fields
|
74
|
+
end
|
75
|
+
|
76
|
+
# Renders the template. If any fields are missing from page_content then a MissingContentError will be raised.
|
77
|
+
def render(page_content = {})
|
78
|
+
missing_fields = []
|
79
|
+
fields.each do |key, field|
|
80
|
+
missing_fields << key if field["required"] && !page_content.key?(key)
|
81
|
+
end
|
82
|
+
|
83
|
+
raise MissingContentError.new("The following fields were missing from your content: #{missing_fields.sort.join(", ")}") unless missing_fields.empty?
|
84
|
+
|
85
|
+
Liquid::Template.parse(content).render(page_content)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/noumenon.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "noumenon/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "noumenon"
|
7
|
+
s.version = Noumenon::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Jon Wood"]
|
10
|
+
s.email = ["jon@blankpad.net"]
|
11
|
+
s.homepage = "https://github.com/Noumenon"
|
12
|
+
s.summary = %q{An content management system backed by Git}
|
13
|
+
s.description = File.read("README.md")
|
14
|
+
|
15
|
+
s.rubyforge_project = "noumenon"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "sinatra", ">= 1.2.3"
|
23
|
+
s.add_dependency "liquid", ">= 2.2"
|
24
|
+
|
25
|
+
s.add_development_dependency "rspec", ">= 2.5.0"
|
26
|
+
s.add_development_dependency "rack-test", ">= 0.5"
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello, fixtures.
|
@@ -0,0 +1 @@
|
|
1
|
+
template: "basic_template"
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hello</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello liquid.
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello, {{ name }}
|
@@ -0,0 +1 @@
|
|
1
|
+
template: "basic_template"
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello, world.
|
data/spec/fixtures/static_example_dependencies/themes/example_without_layout/basic_template.html
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<h1>This is a Basic Template</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
Field: {{ example_field }}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
title:
|
2
|
+
type: string
|
3
|
+
label: Page title
|
4
|
+
help: Appears at the top of the page.
|
5
|
+
required: true
|
6
|
+
|
7
|
+
body:
|
8
|
+
type: text
|
9
|
+
label: Body text
|
10
|
+
help: The main body of the page
|
11
|
+
required: true
|
12
|
+
|
13
|
+
author:
|
14
|
+
type: string
|
15
|
+
label: Author name
|
16
|
+
help: If not provided no author will be shown
|
17
|
+
required: false
|
18
|
+
---
|
19
|
+
<h1>{{ title }}</h1>
|
20
|
+
<div id="content">{{ body }}</div>
|
21
|
+
{% if author %}
|
22
|
+
<p class="byline">Written by {{ author }}.</p>
|
23
|
+
{% endif %}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Noumenon::Configuration do
|
4
|
+
it { should_not be_nil }
|
5
|
+
|
6
|
+
it { should respond_to(:content_repository_path) }
|
7
|
+
it { should respond_to(:content_repository_path=) }
|
8
|
+
|
9
|
+
it { should respond_to(:dependencies_path) }
|
10
|
+
it { should respond_to(:dependencies_path=) }
|
11
|
+
|
12
|
+
it { should respond_to(:theme) }
|
13
|
+
it { should respond_to(:theme=) }
|
14
|
+
|
15
|
+
describe "setting the theme" do
|
16
|
+
before(:each) do
|
17
|
+
subject.dependencies_path = fixture_path("static_example_dependencies")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "succeeds if the theme exists" do
|
21
|
+
subject.theme = "example"
|
22
|
+
subject.theme.should == "example"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an exception if the theme could not be found" do
|
26
|
+
lambda { subject.theme = "none_existant" }.should raise_error Noumenon::Configuration::ThemeNotFoundError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Noumenon::Core do
|
4
|
+
def app
|
5
|
+
Noumenon::Core.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "accessing configuration" do
|
9
|
+
it { should respond_to(:config) }
|
10
|
+
it "returns the global configuration when calling config" do
|
11
|
+
app.config.should == Noumenon.config
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "retrieving paths" do
|
16
|
+
it { should respond_to(:content_path) }
|
17
|
+
|
18
|
+
it "locate a path in the content repository" do
|
19
|
+
app.content_path("example").should == "#{app.config.content_repository_path}/example"
|
20
|
+
end
|
21
|
+
|
22
|
+
it { should respond_to(:theme_path) }
|
23
|
+
|
24
|
+
it "can locate a path in the theme directory" do
|
25
|
+
app.theme_path("file").should == "#{app.config.dependencies_path}/themes/example/file"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "rendering a template" do
|
30
|
+
it { should respond_to(:render_template) }
|
31
|
+
|
32
|
+
context "when the theme has no layout" do
|
33
|
+
before(:each) { app.config.theme = "example_without_layout" }
|
34
|
+
|
35
|
+
it "renders the requested template if it does exist" do
|
36
|
+
app.render_template( app.content_path("found.html") ).should == fixture("static_example/found.html")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "uses Liquid to replace any tags in the template" do
|
40
|
+
app.render_template( app.content_path("template_with_substitutions.html"), 'name' => 'Jon' ).should == "Hello, Jon\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when the theme has a layout" do
|
45
|
+
before(:each) { app.config.theme = "example" }
|
46
|
+
|
47
|
+
it "renders the template within the theme's layout by default" do
|
48
|
+
app.render_template( app.content_path("found.html") ).should == %Q{<div id="layout">\n#{fixture("static_example/found.html")}\n</div>\n}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "ignores the layout if the option :layout => false is passed" do
|
52
|
+
app.render_template( app.content_path("found.html"), {}, :layout => false ).should == fixture("static_example/found.html")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "serving content from the content repository" do
|
58
|
+
before(:each) { app.config.theme = "example_without_layout" }
|
59
|
+
|
60
|
+
context "when the requested item does not exist" do
|
61
|
+
context "and an index file exists in a directory of the same name" do
|
62
|
+
it "renders the index file instead" do
|
63
|
+
get "/directory_with_index"
|
64
|
+
|
65
|
+
last_response.should be_ok
|
66
|
+
last_response.body.should == File.read(app.theme_path("basic_template.html"))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "and no index file exists in a directory of the same name" do
|
71
|
+
it "responds with a status of 404" do
|
72
|
+
get "/empty_directory"
|
73
|
+
|
74
|
+
last_response.should_not be_ok
|
75
|
+
last_response.status.should == 404
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "and no directory of the same name exists" do
|
80
|
+
it "responds with a status of 404" do
|
81
|
+
get "/not_found"
|
82
|
+
|
83
|
+
last_response.should_not be_ok
|
84
|
+
last_response.status.should == 404
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when the requested item exists" do
|
90
|
+
it "renders the specified template from the theme" do
|
91
|
+
get "/templates/basic_example"
|
92
|
+
|
93
|
+
last_response.should be_ok
|
94
|
+
last_response.body.should == File.read(app.theme_path("basic_template.html"))
|
95
|
+
end
|
96
|
+
|
97
|
+
it "makes substitutions as specified in the template" do
|
98
|
+
get "/templates/with_fields"
|
99
|
+
|
100
|
+
last_response.should be_ok
|
101
|
+
last_response.body.should =~ /Field: value/
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'noumenon/spec/fixtures'
|
2
|
+
|
3
|
+
class FixturedSpec
|
4
|
+
include Noumenon::Spec::Fixtures
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "a class with Noumenon::Spec::Fixtures mixed in" do
|
8
|
+
subject { FixturedSpec.new }
|
9
|
+
|
10
|
+
describe "the fixture path" do
|
11
|
+
it { should respond_to(:fixture_path) }
|
12
|
+
it { should respond_to(:fixture_path=) }
|
13
|
+
|
14
|
+
it "defaults the fixture path to 'spec/fixtures'" do
|
15
|
+
subject.fixture_path.should == 'spec/fixtures'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "appends the provided path to spec/fixtures if relative" do
|
19
|
+
subject.fixture_path = "test"
|
20
|
+
subject.fixture_path.should == "spec/fixtures/test"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "sets the path provided if absolute" do
|
24
|
+
subject.fixture_path = "/tmp"
|
25
|
+
subject.fixture_path.should == "/tmp"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the full path to the fixture if fixture_path is passed an argument" do
|
29
|
+
subject.fixture_path = "/tmp"
|
30
|
+
subject.fixture_path("example").should == "/tmp/example"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "loading a fixture" do
|
35
|
+
it { should respond_to(:fixture) }
|
36
|
+
|
37
|
+
it "returns the contents of the specified file" do
|
38
|
+
subject.fixture("fixture_specs/test").should eq "Hello, fixtures.\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Noumenon::Template do
|
4
|
+
it { should_not be_nil }
|
5
|
+
|
6
|
+
def template_path(name)
|
7
|
+
Noumenon::Core.new.theme_path(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
Noumenon.config.theme = "example_without_layout"
|
12
|
+
end
|
13
|
+
|
14
|
+
it { should respond_to(:fields) }
|
15
|
+
it { should respond_to(:source) }
|
16
|
+
it { should respond_to(:content) }
|
17
|
+
|
18
|
+
describe "loading a template from disk" do
|
19
|
+
it "raises a TemplateNotFoundError if the template does not exist" do
|
20
|
+
lambda { Noumenon::Template.from_file "dummy" }.should raise_error Noumenon::Template::NotFoundError
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the template has no fields specified" do
|
24
|
+
subject do
|
25
|
+
Noumenon::Template.from_file template_path("basic_template.html")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "leaves #fields empty" do
|
29
|
+
subject.fields.should be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "populates #source with the path to the template" do
|
33
|
+
subject.source.should == template_path("basic_template.html")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "populates #content with the template body" do
|
37
|
+
subject.content.should == File.read(template_path("basic_template.html"))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when the template has some fields specified" do
|
42
|
+
subject do
|
43
|
+
Noumenon::Template.from_file template_path("template_with_fields.html")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "populates the field data for the title" do
|
47
|
+
subject.fields["title"].should == {
|
48
|
+
"type" => "string",
|
49
|
+
"label" => "Page title",
|
50
|
+
"help" => "Appears at the top of the page.",
|
51
|
+
"required" => true
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "populates the field data for the body" do
|
56
|
+
subject.fields["body"].should == {
|
57
|
+
"type" => "text",
|
58
|
+
"label" => "Body text",
|
59
|
+
"help" => "The main body of the page",
|
60
|
+
"required" => true
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
it "populates the field data for the author" do
|
65
|
+
subject.fields["author"].should == {
|
66
|
+
"type" => "string",
|
67
|
+
"label" => "Author name",
|
68
|
+
"help" => "If not provided no author will be shown",
|
69
|
+
"required" => false
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "populates #content with the template body" do
|
74
|
+
subject.content.should eq File.read(template_path("template_with_fields.html")).split("\n---\n").last
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "rendering a template" do
|
80
|
+
let(:template) { Noumenon::Template.from_file template_path("template_with_fields.html") }
|
81
|
+
|
82
|
+
it { should respond_to(:render) }
|
83
|
+
|
84
|
+
context "when missing required fields" do
|
85
|
+
it "raises a Noumenon::Template::MissingContentError" do
|
86
|
+
lambda { template.render }.should raise_error Noumenon::Template::MissingContentError
|
87
|
+
end
|
88
|
+
|
89
|
+
it "lists the missing fields in the exception" do
|
90
|
+
begin
|
91
|
+
template.render
|
92
|
+
rescue Noumenon::Template::MissingContentError => e
|
93
|
+
e.to_s.should eq "The following fields were missing from your content: body, title"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when all required fields were provided" do
|
99
|
+
it "renders the template, replacing any fields" do
|
100
|
+
content = template.render("title" => "Example Page", "body" => "This is an example, isn't it lovely.", "author" => "Jon Wood")
|
101
|
+
|
102
|
+
content.should =~ %r{<h1>Example Page</h1>}
|
103
|
+
content.should =~ %r{<div id="content">This is an example, isn't it lovely.</div>}
|
104
|
+
content.should =~ %r{<p class="byline">Written by Jon Wood.</p>}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when provided with fields that are not specified for the template" do
|
109
|
+
it "renders the template anyway" do
|
110
|
+
content = template.render("title" => "Example Page", "body" => "This is an example, isn't it lovely.", "author" => "Jon Wood", "unknown" => "is here")
|
111
|
+
|
112
|
+
content.should =~ %r{<h1>Example Page</h1>}
|
113
|
+
content.should =~ %r{<div id="content">This is an example, isn't it lovely.</div>}
|
114
|
+
content.should =~ %r{<p class="byline">Written by Jon Wood.</p>}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Noumenon do
|
4
|
+
before(:each) do
|
5
|
+
Noumenon.config.dependencies_path = fixture_path("static_example_dependencies")
|
6
|
+
end
|
7
|
+
|
8
|
+
specify { Noumenon.should respond_to(:boot) }
|
9
|
+
|
10
|
+
def app
|
11
|
+
Noumenon.boot
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "serving theme assets" do
|
15
|
+
it "serves the asset specified at /themes/theme_name/example.txt if the file exists" do
|
16
|
+
get '/themes/example/example.txt'
|
17
|
+
|
18
|
+
last_response.status.should == 200
|
19
|
+
last_response.body.should == fixture("static_example_dependencies/themes/example/assets/example.txt")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns a status of 404 if the file does not exist" do
|
23
|
+
get '/themes/example/not_found.png'
|
24
|
+
|
25
|
+
last_response.status.should == 404
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "setting configuration" do
|
30
|
+
before(:each) do
|
31
|
+
Noumenon.config = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
specify { Noumenon.should respond_to(:configure) }
|
35
|
+
specify { Noumenon.should respond_to(:config) }
|
36
|
+
specify { Noumenon.should respond_to(:config=) }
|
37
|
+
|
38
|
+
it "sets config to the provided object on config=" do
|
39
|
+
config = Noumenon::Configuration.new
|
40
|
+
Noumenon.config = config
|
41
|
+
Noumenon.config.should == config
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns a new config instance if no config was previously set" do
|
45
|
+
Noumenon.config.should be_instance_of Noumenon::Configuration
|
46
|
+
end
|
47
|
+
|
48
|
+
it "yields the config instance if a block is provided" do
|
49
|
+
yielded = false
|
50
|
+
|
51
|
+
config = Noumenon::Configuration.new
|
52
|
+
Noumenon.config = config
|
53
|
+
Noumenon.configure do |c|
|
54
|
+
c.should == config
|
55
|
+
yielded = true
|
56
|
+
end
|
57
|
+
|
58
|
+
yielded.should be_true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'noumenon'
|
2
|
+
require 'noumenon/spec'
|
3
|
+
require 'rack/test'
|
4
|
+
|
5
|
+
RSpec.configure do |spec|
|
6
|
+
spec.include Rack::Test::Methods
|
7
|
+
spec.include Noumenon::Spec
|
8
|
+
include Noumenon::Spec::Fixtures
|
9
|
+
|
10
|
+
Noumenon::Core.set :environment, :test
|
11
|
+
|
12
|
+
spec.before(:each) do
|
13
|
+
Noumenon.configure do |c|
|
14
|
+
c.content_repository_path = fixture_path("static_example")
|
15
|
+
c.dependencies_path = fixture_path("static_example_dependencies")
|
16
|
+
c.theme = "example"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/watchr.rb
ADDED
metadata
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: noumenon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jon Wood
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-19 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: sinatra
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.2.3
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: liquid
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "2.2"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.5.0
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rack-test
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0.5"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
description: |
|
61
|
+
# Noumenon
|
62
|
+
|
63
|
+
Noumenon: Noun. The intellectual conception of a thing as it is in itself, not as it is known through perception.
|
64
|
+
|
65
|
+
## What is This Thing?
|
66
|
+
|
67
|
+
Noumenon is a web application based on Sinatra for constructing dynamic websites which use a Git repository
|
68
|
+
for storing all content. It's designed to allow technical and non-technical teams to collaborate on development and
|
69
|
+
populate of a content management system using the tools they are most comfortable with.
|
70
|
+
|
71
|
+
In the case of developers (and some designers) that's version control and text editors, while in the case of content
|
72
|
+
editors thats more likely to be a web interface.
|
73
|
+
|
74
|
+
## How it Works
|
75
|
+
|
76
|
+
The URL structure and content of a Noumenon site is defined by a Git repository, similar to the structure below:
|
77
|
+
|
78
|
+
/
|
79
|
+
- /config.rb
|
80
|
+
- /index.html
|
81
|
+
- /about
|
82
|
+
- /people.html
|
83
|
+
- /company.html
|
84
|
+
- /contact
|
85
|
+
- /config.rb
|
86
|
+
- /blog
|
87
|
+
- /config.rb
|
88
|
+
- /posts/2011-04-18-an-example-post.md
|
89
|
+
- /posts/2011-04-10-another-example.md
|
90
|
+
|
91
|
+
This git repository is then provided to Noumenon as it's data source. On startup it loads any file called "config.rb"
|
92
|
+
and uses it to determine how that directory should behave:
|
93
|
+
|
94
|
+
# /config.rb
|
95
|
+
domain "example.org"
|
96
|
+
application "git://github.com/noumenon/apps-static"
|
97
|
+
theme "git://github.com/noumenon/themes-example"
|
98
|
+
|
99
|
+
That example configures Noumenon to use the "Noumenon::Static" application to serve any templates below that point as
|
100
|
+
a static page.
|
101
|
+
|
102
|
+
# /contact/config.rb
|
103
|
+
application "git://github.com/noumenon/apps-contact"
|
104
|
+
contact.email_address "info@example.org"
|
105
|
+
|
106
|
+
While the one in /contact specifies that "Noumenon::Contact" should be used to provide the URL tree below /contact, in this
|
107
|
+
case a contact form which emails any responses to the specified address.
|
108
|
+
|
109
|
+
Finally /blogs/config.rb might look something like this:
|
110
|
+
|
111
|
+
application "git://github.com/noumenon/apps-blog"
|
112
|
+
blog.comments true
|
113
|
+
|
114
|
+
## Hosting a Noumenon Site
|
115
|
+
|
116
|
+
*This won't work yet: I havn't implemented automatic check out of a content repository.*
|
117
|
+
|
118
|
+
To host a site you will need to have Ruby and an application server such as Passenger, Unicorn or Thin. Due it's interactions
|
119
|
+
with git as a data store Heroku is not a supported platform for hosting, although I'm sure someone will find a way around that.
|
120
|
+
|
121
|
+
Install Noumenon: `gem install noumenon`
|
122
|
+
|
123
|
+
Create a directory, and put the following config.ru in it:
|
124
|
+
|
125
|
+
require 'noumenon'
|
126
|
+
Noumenon::Core.set :content_repository, "git@github.com:Noumenon/example.git"
|
127
|
+
|
128
|
+
run Noumenon.boot
|
129
|
+
|
130
|
+
And then set up your application server to provide that application. On startup it will first attempt to check out the content
|
131
|
+
repository, then it will parse any configuration files, and install any required dependencies. Finally, it will start hosting
|
132
|
+
the site.
|
133
|
+
|
134
|
+
To update the content repository restart your application server, which will cause it to update the repository, and any dependencies.
|
135
|
+
|
136
|
+
### Hosting on Heroku
|
137
|
+
|
138
|
+
The someone who worked out the way around it was me it seems. This will only work if you're deploying an entirely static site,
|
139
|
+
if you need the admin section to work then you'll have to host somewhere else, but otherwise, use a config.ru like this, and make
|
140
|
+
sure you have Noumenon in your Gemfile:
|
141
|
+
|
142
|
+
require 'noumenon'
|
143
|
+
Noumenon::Core.set :content_repository_path, File.expand_path("..", __FILE__)
|
144
|
+
|
145
|
+
run Noumenon.boot
|
146
|
+
|
147
|
+
email:
|
148
|
+
- jon@blankpad.net
|
149
|
+
executables: []
|
150
|
+
|
151
|
+
extensions: []
|
152
|
+
|
153
|
+
extra_rdoc_files: []
|
154
|
+
|
155
|
+
files:
|
156
|
+
- .gitignore
|
157
|
+
- .travis.yml
|
158
|
+
- Gemfile
|
159
|
+
- README.md
|
160
|
+
- Rakefile
|
161
|
+
- lib/noumenon.rb
|
162
|
+
- lib/noumenon/configuration.rb
|
163
|
+
- lib/noumenon/core.rb
|
164
|
+
- lib/noumenon/spec.rb
|
165
|
+
- lib/noumenon/spec/fixtures.rb
|
166
|
+
- lib/noumenon/template.rb
|
167
|
+
- lib/noumenon/version.rb
|
168
|
+
- noumenon.gemspec
|
169
|
+
- spec/fixtures/fixture_specs/test
|
170
|
+
- spec/fixtures/static_example/directory_with_index/index.yml
|
171
|
+
- spec/fixtures/static_example/found.html
|
172
|
+
- spec/fixtures/static_example/liquid_example.html
|
173
|
+
- spec/fixtures/static_example/template_with_substitutions.html
|
174
|
+
- spec/fixtures/static_example/templates/basic_example.yml
|
175
|
+
- spec/fixtures/static_example/templates/with_fields.yml
|
176
|
+
- spec/fixtures/static_example_dependencies/themes/example/assets/example.txt
|
177
|
+
- spec/fixtures/static_example_dependencies/themes/example/layout.html
|
178
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/basic_template.html
|
179
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/fields.html
|
180
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/template_with_fields.html
|
181
|
+
- spec/noumenon/config_spec.rb
|
182
|
+
- spec/noumenon/core_spec.rb
|
183
|
+
- spec/noumenon/spec/fixtures_spec.rb
|
184
|
+
- spec/noumenon/spec_spec.rb
|
185
|
+
- spec/noumenon/template_spec.rb
|
186
|
+
- spec/noumenon_spec.rb
|
187
|
+
- spec/spec_helper.rb
|
188
|
+
- watchr.rb
|
189
|
+
has_rdoc: true
|
190
|
+
homepage: https://github.com/Noumenon
|
191
|
+
licenses: []
|
192
|
+
|
193
|
+
post_install_message:
|
194
|
+
rdoc_options: []
|
195
|
+
|
196
|
+
require_paths:
|
197
|
+
- lib
|
198
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
199
|
+
none: false
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: "0"
|
204
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
|
+
none: false
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: "0"
|
210
|
+
requirements: []
|
211
|
+
|
212
|
+
rubyforge_project: noumenon
|
213
|
+
rubygems_version: 1.6.2
|
214
|
+
signing_key:
|
215
|
+
specification_version: 3
|
216
|
+
summary: An content management system backed by Git
|
217
|
+
test_files:
|
218
|
+
- spec/fixtures/fixture_specs/test
|
219
|
+
- spec/fixtures/static_example/directory_with_index/index.yml
|
220
|
+
- spec/fixtures/static_example/found.html
|
221
|
+
- spec/fixtures/static_example/liquid_example.html
|
222
|
+
- spec/fixtures/static_example/template_with_substitutions.html
|
223
|
+
- spec/fixtures/static_example/templates/basic_example.yml
|
224
|
+
- spec/fixtures/static_example/templates/with_fields.yml
|
225
|
+
- spec/fixtures/static_example_dependencies/themes/example/assets/example.txt
|
226
|
+
- spec/fixtures/static_example_dependencies/themes/example/layout.html
|
227
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/basic_template.html
|
228
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/fields.html
|
229
|
+
- spec/fixtures/static_example_dependencies/themes/example_without_layout/template_with_fields.html
|
230
|
+
- spec/noumenon/config_spec.rb
|
231
|
+
- spec/noumenon/core_spec.rb
|
232
|
+
- spec/noumenon/spec/fixtures_spec.rb
|
233
|
+
- spec/noumenon/spec_spec.rb
|
234
|
+
- spec/noumenon/template_spec.rb
|
235
|
+
- spec/noumenon_spec.rb
|
236
|
+
- spec/spec_helper.rb
|