noumenon 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +15 -2
- data/README.md +15 -4
- data/Rakefile +5 -0
- data/features/asset_serving/from_content_repositories.feature +32 -0
- data/features/{theme_assets.feature → asset_serving/from_themes.feature} +1 -1
- data/features/step_definitions/asset_steps.rb +13 -0
- data/features/support/env.rb +6 -3
- data/features/template_tags/asset_paths.feature +30 -0
- data/features/template_tags/nav_menu.feature +19 -0
- data/lib/noumenon/core.rb +8 -1
- data/lib/noumenon/repository/file_system.rb +43 -6
- data/lib/noumenon/repository.rb +58 -4
- data/lib/noumenon/template/core_tags.rb +25 -2
- data/lib/noumenon/version.rb +1 -1
- data/lib/noumenon.rb +17 -1
- data/noumenon.gemspec +11 -11
- data/spec/noumenon/repository/file_system_spec.rb +37 -2
- data/spec/noumenon/repository_spec.rb +9 -43
- data/spec/noumenon_spec.rb +17 -1
- data/spec/support/file_matchers.rb +29 -7
- data/spec/support/implementation_matcher.rb +55 -0
- metadata +30 -24
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# 0.1.4
|
2
|
+
|
3
|
+
## New Features
|
4
|
+
|
5
|
+
* Support for saving assets to the content repository, and selecting an alternative repository
|
6
|
+
such as S3 for static assets.
|
7
|
+
* Added the `asset` and `theme_asset` template tags to generate URLs.
|
8
|
+
* Allow use of a `nav_title` attribute to put a different title in the navigation menu to the page itself.
|
9
|
+
|
10
|
+
## Bug Fixes
|
11
|
+
|
12
|
+
* Fix the template tags link from the documentation to the wiki.
|
13
|
+
* Make sure future releases of gems can't break Noumenon by restricting future versions
|
14
|
+
in the gemspec.
|
15
|
+
|
1
16
|
# 0.1.3
|
2
17
|
|
3
18
|
## New Features
|
@@ -6,8 +21,6 @@
|
|
6
21
|
* Make RSpec output color when running Rake
|
7
22
|
* Tidied up the Cucumber features
|
8
23
|
|
9
|
-
## Bug Fixes
|
10
|
-
|
11
24
|
# 0.1.2
|
12
25
|
|
13
26
|
## New Features
|
data/README.md
CHANGED
@@ -14,10 +14,18 @@ editors thats more likely to be a web interface.
|
|
14
14
|
|
15
15
|
## Getting Started
|
16
16
|
|
17
|
-
If you want to skip the theory, and
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
If you want to skip the theory, and get on with the fun bit, follow these steps:
|
18
|
+
|
19
|
+
```shell
|
20
|
+
$ gem install noumenon thin bundler
|
21
|
+
$ noumenon my_site
|
22
|
+
$ cd my_site
|
23
|
+
$ bundle install
|
24
|
+
$ thin start
|
25
|
+
```
|
26
|
+
|
27
|
+
You're basic Noumenon site will be running at http://localhost:3000/ now. If you want to put it on the internet it should
|
28
|
+
work straight out of the box on Heroku.
|
21
29
|
|
22
30
|
## How it Works
|
23
31
|
|
@@ -122,6 +130,9 @@ Currently the only available content repository uses YAML files to store your co
|
|
122
130
|
and the API for content repositories is simple enough that just about any datastore could be used if you want to implement an
|
123
131
|
adapater for it.
|
124
132
|
|
133
|
+
Content repositories can also store static assets such as images for use within your pages. For the default file system repository
|
134
|
+
just put them below the `assets` directory, which is made available at the URL `/assets/` by Noumenon.
|
135
|
+
|
125
136
|
### Applications
|
126
137
|
|
127
138
|
If part of your site requires something other then rendering a piece of content within a static template then you can use a
|
data/Rakefile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'bundler/gem_helper'
|
1
2
|
require 'rspec/core/rake_task'
|
2
3
|
require 'cucumber/rake/task'
|
3
4
|
require 'yard'
|
@@ -20,3 +21,7 @@ end
|
|
20
21
|
YARD::Rake::YardocTask.new do |t|
|
21
22
|
t.name = :doc
|
22
23
|
end
|
24
|
+
|
25
|
+
namespace :gem do
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
Feature: Serving assets from the content repository
|
2
|
+
As a user
|
3
|
+
I want to have assets in my content repository
|
4
|
+
So that I can attach my own images to pages
|
5
|
+
|
6
|
+
Scenario: The asset has been added
|
7
|
+
Given I have uploaded this asset as "example.txt" to the asset repository
|
8
|
+
"""
|
9
|
+
Hello, world!
|
10
|
+
"""
|
11
|
+
When I view "/assets/example.txt"
|
12
|
+
Then the page content should be:
|
13
|
+
"""
|
14
|
+
Hello, world!
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: The asset does not exist
|
18
|
+
Given there is no asset called "example.txt" in the asset repository
|
19
|
+
When I view "/assets/example.txt"
|
20
|
+
Then the page should return a 404 error
|
21
|
+
|
22
|
+
Scenario: Using an alternative asset repository
|
23
|
+
Given I have chosen to use a seperate asset repository
|
24
|
+
And I have uploaded this asset as "example.txt" to the asset repository
|
25
|
+
"""
|
26
|
+
Alternative repos FTW
|
27
|
+
"""
|
28
|
+
When I view "/assets/example.txt"
|
29
|
+
Then the page content should be:
|
30
|
+
"""
|
31
|
+
Alternative repos FTW
|
32
|
+
"""
|
@@ -5,3 +5,16 @@ end
|
|
5
5
|
Given /^the asset "([^"]*)" exists within the theme with this content:$/ do |asset_name, content|
|
6
6
|
File.open(File.join(Noumenon.theme.path, "assets", asset_name), "w") { |f| f.print content }
|
7
7
|
end
|
8
|
+
|
9
|
+
Given /^I have chosen to use a seperate asset repository$/ do
|
10
|
+
FileUtils.mkdir_p File.join(tmp_path, "assets")
|
11
|
+
Noumenon.asset_repository = Noumenon::Repository::FileSystem.new(path: File.join(tmp_path, "assets"))
|
12
|
+
end
|
13
|
+
|
14
|
+
Given /^I have uploaded this asset as "([^"]*)" to the asset repository$/ do |path, string|
|
15
|
+
Noumenon.asset_repository.save_asset(path, string)
|
16
|
+
end
|
17
|
+
|
18
|
+
Given /^there is no asset called "([^"]*)" in the asset repository$/ do |path|
|
19
|
+
# noop
|
20
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -17,9 +17,13 @@ Capybara.app = Noumenon.server
|
|
17
17
|
World do
|
18
18
|
include RSpec::Expectations
|
19
19
|
include RSpec::Matchers
|
20
|
+
|
21
|
+
def tmp_path
|
22
|
+
File.expand_path('../tmp', __FILE__)
|
23
|
+
end
|
20
24
|
|
21
25
|
def content_path
|
22
|
-
File.
|
26
|
+
File.join(tmp_path, "content")
|
23
27
|
end
|
24
28
|
|
25
29
|
true
|
@@ -33,6 +37,5 @@ Before do
|
|
33
37
|
end
|
34
38
|
|
35
39
|
After do
|
36
|
-
FileUtils.rm_r
|
37
|
-
FileUtils.rm_r theme_path
|
40
|
+
FileUtils.rm_r tmp_path
|
38
41
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Feature: Generating asset paths from templates
|
2
|
+
As a designer
|
3
|
+
I want to use a tag to reference asset paths in templates
|
4
|
+
So that they keep working once deployed
|
5
|
+
|
6
|
+
Scenario: Generating a path for a theme asset
|
7
|
+
Given this template exists at "theme_assets.nou.html"
|
8
|
+
"""
|
9
|
+
<img src="{% theme_asset logo.png %}" />
|
10
|
+
"""
|
11
|
+
And this content item exists at "/theme_assets"
|
12
|
+
| template | theme_assets |
|
13
|
+
When I view "/theme_assets"
|
14
|
+
Then the page content should be:
|
15
|
+
"""
|
16
|
+
<img src="/themes/Example%20Theme/logo.png" />
|
17
|
+
"""
|
18
|
+
|
19
|
+
Scenario: Generating a URL for a content asset
|
20
|
+
Given this template exists at "content_assets.nou.html"
|
21
|
+
"""
|
22
|
+
<img src="{% asset attachment.png %}" />
|
23
|
+
"""
|
24
|
+
And this content item exists at "/content_assets"
|
25
|
+
| template | content_assets |
|
26
|
+
When I view "/content_assets"
|
27
|
+
Then the page content should be:
|
28
|
+
"""
|
29
|
+
<img src="/assets/attachment.png" />
|
30
|
+
"""
|
@@ -23,6 +23,25 @@ Feature: Navigation menus
|
|
23
23
|
| About | /about |
|
24
24
|
| Contact | /contact |
|
25
25
|
| Team | /team |
|
26
|
+
|
27
|
+
Scenario: Custom page titles
|
28
|
+
Given this template exists at "basic_menu.nou.html"
|
29
|
+
"""
|
30
|
+
<nav>
|
31
|
+
{% nav_menu %}
|
32
|
+
</nav>
|
33
|
+
"""
|
34
|
+
And this content item exists at "/about"
|
35
|
+
| title | About |
|
36
|
+
| template | basic_menu |
|
37
|
+
And this content item exists at "/contact"
|
38
|
+
| title | Get In Touch With Us |
|
39
|
+
| nav_title | Contact |
|
40
|
+
When I view "/about"
|
41
|
+
Then a menu with the following items should be visible:
|
42
|
+
| Label | Link Target |
|
43
|
+
| About | /about |
|
44
|
+
| Contact | /contact |
|
26
45
|
|
27
46
|
Scenario: Multi-level menu
|
28
47
|
Given this template exists at "deep_menu.nou.html"
|
data/lib/noumenon/core.rb
CHANGED
@@ -40,7 +40,14 @@ class Noumenon::Core < Sinatra::Base
|
|
40
40
|
def content
|
41
41
|
Noumenon.content_repository
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
|
+
get "/assets/*" do |path|
|
45
|
+
asset = Noumenon.asset_repository.get_asset("/#{path}")
|
46
|
+
halt 404, "Asset not found" unless asset
|
47
|
+
|
48
|
+
asset
|
49
|
+
end
|
50
|
+
|
44
51
|
get "*" do |path|
|
45
52
|
page = content.get(path)
|
46
53
|
|
@@ -38,10 +38,8 @@ class Noumenon::Repository::FileSystem < Noumenon::Repository
|
|
38
38
|
# @return [ Hash, #each, nil ] The piece of content, or nil if it could not be found.
|
39
39
|
# @api public
|
40
40
|
def get(path, check_for_index = true)
|
41
|
-
|
42
|
-
|
43
|
-
if File.exist?(file)
|
44
|
-
YAML.load(File.read(file)).symbolize_keys
|
41
|
+
if content = read_file("#{path}.yml")
|
42
|
+
YAML.load(content).symbolize_keys
|
45
43
|
elsif check_for_index
|
46
44
|
return get("#{path}/index", false)
|
47
45
|
end
|
@@ -50,13 +48,18 @@ class Noumenon::Repository::FileSystem < Noumenon::Repository
|
|
50
48
|
# Saves a piece of content to the repsitory.
|
51
49
|
#
|
52
50
|
# @see Noumenon::Repository#put
|
51
|
+
# @api public
|
53
52
|
def put(path, content = {})
|
54
53
|
create_tree(path)
|
55
|
-
|
54
|
+
|
56
55
|
path = File.join(path, "index") if File.exist?(repository_path(path))
|
57
|
-
|
56
|
+
write_file "#{path}.yml", content.symbolize_keys.to_yaml
|
58
57
|
end
|
59
58
|
|
59
|
+
# Return any content items below the path specified.
|
60
|
+
#
|
61
|
+
# @see Noumenon::Repository#children
|
62
|
+
# @api public
|
60
63
|
def children(root = "/", depth = 1)
|
61
64
|
root.gsub! /\/$/, ''
|
62
65
|
|
@@ -79,6 +82,24 @@ class Noumenon::Repository::FileSystem < Noumenon::Repository
|
|
79
82
|
page
|
80
83
|
end
|
81
84
|
end
|
85
|
+
|
86
|
+
# Save a static asset to the repository.
|
87
|
+
#
|
88
|
+
# @see Noumenon::Repository#save_asset
|
89
|
+
# @api public
|
90
|
+
def save_asset(path, content)
|
91
|
+
raise ArgumentError.new("Assets must have a file extension.") unless File.extname(path).size > 0
|
92
|
+
write_file File.join("assets", path), content
|
93
|
+
end
|
94
|
+
|
95
|
+
# Retreive a static asset to the repository.
|
96
|
+
#
|
97
|
+
# @see Noumenon::Repository#get_asset
|
98
|
+
# @api public
|
99
|
+
def get_asset(path)
|
100
|
+
return nil unless File.extname(path).size > 0
|
101
|
+
read_file File.join("assets", path)
|
102
|
+
end
|
82
103
|
|
83
104
|
private
|
84
105
|
# Return the on-disk path to a repository item.
|
@@ -97,6 +118,22 @@ class Noumenon::Repository::FileSystem < Noumenon::Repository
|
|
97
118
|
end
|
98
119
|
end
|
99
120
|
|
121
|
+
def write_file(path, content)
|
122
|
+
path_on_disk = repository_path(path)
|
123
|
+
directory = File.dirname(path_on_disk)
|
124
|
+
FileUtils.mkdir_p(directory) unless File.exist?(directory)
|
125
|
+
|
126
|
+
File.open(path_on_disk, "w") { |f| f.print content }
|
127
|
+
end
|
128
|
+
|
129
|
+
def read_file(path)
|
130
|
+
begin
|
131
|
+
File.read(repository_path(path))
|
132
|
+
rescue Errno::ENOENT => e
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
100
137
|
# Moves conflicting YAML files into directory/index.yml
|
101
138
|
#
|
102
139
|
# For example given
|
data/lib/noumenon/repository.rb
CHANGED
@@ -25,7 +25,7 @@ class Noumenon::Repository
|
|
25
25
|
# @param [ Hash, #each ] content A hash of key/value pairs to use as the content.
|
26
26
|
# @api public
|
27
27
|
def put(path, content)
|
28
|
-
|
28
|
+
not_supported "updating it's contents"
|
29
29
|
end
|
30
30
|
|
31
31
|
# Load an item from the repository. If the item does not exist then `nil` should be returned.
|
@@ -34,10 +34,64 @@ class Noumenon::Repository
|
|
34
34
|
# @return [ Hash, #each, nil ] Either the hash stored at the specified path, or nil if no content was found.
|
35
35
|
# @api public
|
36
36
|
def get(path)
|
37
|
-
|
37
|
+
not_supported "reading it's contents"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an array of content items below the root specified. If depth is greater then 1 that
|
41
|
+
# many sub-directories will also be checked, with any items in those being placed in a `:children` attribute
|
42
|
+
# on the parent.
|
43
|
+
#
|
44
|
+
# @param [ #to_s ] root The path to start searching from.
|
45
|
+
# @param [ Integer ] depth The number of sub-directories to check.
|
46
|
+
# @return [ Array, #each ] An array of child items.
|
47
|
+
# @api public
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# Noumenon.content_repository.children("/", 2)
|
51
|
+
# # => [
|
52
|
+
# # {
|
53
|
+
# # path: "/about", title: "About, children: [
|
54
|
+
# # { path: "/about/team", title: "The Team" },
|
55
|
+
# # { path: "/about/area", title: "The Area" }
|
56
|
+
# # ]
|
57
|
+
# # }
|
58
|
+
# # ]
|
59
|
+
#
|
60
|
+
def children(root = "/", depth = 1)
|
61
|
+
not_supported "listing children from a path"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Saves a static asset to the repository. To be saved the asset must have a file extension, to prevent
|
65
|
+
# problems with an entire directory being overridden by a single asset.
|
66
|
+
#
|
67
|
+
# @param [ String ] path The path to save the asset at.
|
68
|
+
# @param [ String ] content The contents of the asset.
|
69
|
+
# @raises [ ArgumentError ] The path did not have a file extension.
|
70
|
+
def save_asset(path, content)
|
71
|
+
not_supported "saving assets"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retrieves a static asset from the repository. If the asset does not exist nil is returned.
|
75
|
+
#
|
76
|
+
# @param [ String ] path The path to load from.
|
77
|
+
# @return [ String, nil ] The asset's content, or nil if it does not exist.
|
78
|
+
def get_asset(path)
|
79
|
+
not_supported "loading assets"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a URL for the specified asset, without checking for it's existence.
|
83
|
+
#
|
84
|
+
# This is most useful for repositories that save their assets to a CDN such as S3, and allows them to be
|
85
|
+
# directly accessed from there, instead of downloaded and then retransmitted.
|
86
|
+
#
|
87
|
+
# @param [ String ] path The asset path within the repository.
|
88
|
+
# @return [ String ] The URL to use when requesting the asset.
|
89
|
+
def asset_url(path)
|
90
|
+
File.join("/assets", path)
|
38
91
|
end
|
39
92
|
|
40
|
-
|
41
|
-
|
93
|
+
protected
|
94
|
+
def not_supported(action)
|
95
|
+
raise NotImplementedError.new("This repository type does not support #{action}.")
|
42
96
|
end
|
43
97
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'noumenon/template'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
# Liquid extensions provided by Noumenon.
|
4
5
|
#
|
5
|
-
# @see http://github.com/Noumenon/noumenon/wiki/Template
|
6
|
+
# @see http://github.com/Noumenon/noumenon/wiki/Template-Tags
|
6
7
|
module Noumenon::Template::CoreTags
|
7
8
|
# The core set of filters provided by Noumenon for use in templates.
|
8
9
|
#
|
@@ -74,7 +75,7 @@ module Noumenon::Template::CoreTags
|
|
74
75
|
def render_list(items)
|
75
76
|
list = items.collect do |item|
|
76
77
|
str = "<li>"
|
77
|
-
str << %Q{<a href="#{item[:path]}">#{item[:title]}</a>}
|
78
|
+
str << %Q{<a href="#{item[:path]}">#{item[:nav_title] || item[:title]}</a>}
|
78
79
|
str << render_list(item[:children]) if item[:children]
|
79
80
|
str << "</li>"
|
80
81
|
|
@@ -89,4 +90,26 @@ module Noumenon::Template::CoreTags
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
Liquid::Template.register_tag('nav_menu', NavigationTag)
|
93
|
+
|
94
|
+
class ThemeAssetTag < Liquid::Tag
|
95
|
+
def initialize(tag_name, asset, tokens)
|
96
|
+
@asset = asset.strip
|
97
|
+
end
|
98
|
+
|
99
|
+
def render(context)
|
100
|
+
File.join("/themes", URI.escape(Noumenon.theme.name), @asset)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
Liquid::Template.register_tag('theme_asset', ThemeAssetTag)
|
104
|
+
|
105
|
+
class ContentAssetTag < Liquid::Tag
|
106
|
+
def initialize(tag_name, asset, tokens)
|
107
|
+
@asset = asset.strip
|
108
|
+
end
|
109
|
+
|
110
|
+
def render(context)
|
111
|
+
Noumenon.asset_repository.asset_url(@asset)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
Liquid::Template.register_tag('asset', ContentAssetTag)
|
92
115
|
end
|
data/lib/noumenon/version.rb
CHANGED
data/lib/noumenon.rb
CHANGED
@@ -47,7 +47,23 @@ module Noumenon
|
|
47
47
|
def self.content_repository=(repository)
|
48
48
|
@content_repository = repository
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
|
+
# Returns the current asset repository. If one hasn't been set then the content repository will be returned.
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
# @return [ Noumenon::Repository, #get_asset, #save_asset ]
|
55
|
+
def self.asset_repository
|
56
|
+
@asset_repository || content_repository
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets the current asset repository. If you want to return to using the default content repository set to nil.
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
# @param [ Noumenon::Repository, #get_asset, #save_asset ] repository The repository to use for assets.
|
63
|
+
def self.asset_repository=(repository)
|
64
|
+
@asset_repository = repository
|
65
|
+
end
|
66
|
+
|
51
67
|
# Returns the current theme
|
52
68
|
#
|
53
69
|
# @api public
|
data/noumenon.gemspec
CHANGED
@@ -31,16 +31,16 @@ EOF
|
|
31
31
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
32
|
s.require_paths = ["lib"]
|
33
33
|
|
34
|
-
s.add_dependency "facets", "
|
35
|
-
s.add_dependency "liquid", "
|
36
|
-
s.add_dependency "sinatra", "
|
34
|
+
s.add_dependency "facets", "~> 2.9"
|
35
|
+
s.add_dependency "liquid", "~> 2.2"
|
36
|
+
s.add_dependency "sinatra", "~> 1.2"
|
37
37
|
|
38
|
-
s.add_development_dependency "aruba", "
|
39
|
-
s.add_development_dependency "capybara", "
|
40
|
-
s.add_development_dependency "cucumber", "
|
41
|
-
s.add_development_dependency "rake", "
|
42
|
-
s.add_development_dependency "rdiscount", "
|
43
|
-
s.add_development_dependency "rspec", "
|
44
|
-
s.add_development_dependency "thor", "
|
45
|
-
s.add_development_dependency "yard", "
|
38
|
+
s.add_development_dependency "aruba", "~> 0.3"
|
39
|
+
s.add_development_dependency "capybara", "~> 1.0.0.beta1"
|
40
|
+
s.add_development_dependency "cucumber", "~> 0.10"
|
41
|
+
s.add_development_dependency "rake", "~> 0.9"
|
42
|
+
s.add_development_dependency "rdiscount", "~> 1.6"
|
43
|
+
s.add_development_dependency "rspec", "~> 2.5"
|
44
|
+
s.add_development_dependency "thor", "~> 0.14"
|
45
|
+
s.add_development_dependency "yard", "~> 0.7"
|
46
46
|
end
|
@@ -29,7 +29,7 @@ describe Noumenon::Repository::FileSystem do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
it { should
|
32
|
+
it { should implement(:put, "/", foo: "bar") }
|
33
33
|
|
34
34
|
describe "putting content in the repository" do
|
35
35
|
context "when writing to a top level file" do
|
@@ -80,7 +80,8 @@ describe Noumenon::Repository::FileSystem do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
|
+
it { should implement(:get, "/") }
|
84
85
|
describe "retrieving content from the repository" do
|
85
86
|
context "when the item does not exist" do
|
86
87
|
it "returns nil" do
|
@@ -113,6 +114,8 @@ describe Noumenon::Repository::FileSystem do
|
|
113
114
|
end
|
114
115
|
end
|
115
116
|
|
117
|
+
it { should implement(:children) }
|
118
|
+
|
116
119
|
describe "listing child items" do
|
117
120
|
before do
|
118
121
|
subject.put("/", title: "Home")
|
@@ -170,4 +173,36 @@ describe Noumenon::Repository::FileSystem do
|
|
170
173
|
end
|
171
174
|
end
|
172
175
|
end
|
176
|
+
|
177
|
+
it { should implement(:save_asset, "/foo.txt", "foo") }
|
178
|
+
describe "saving assets" do
|
179
|
+
it "raises an ArgumentError when the path has no extension" do
|
180
|
+
lambda { subject.save_asset("/foo", "Content") }.should raise_error(ArgumentError, "Assets must have a file extension.")
|
181
|
+
end
|
182
|
+
|
183
|
+
it "saves the asset to the filesystem" do
|
184
|
+
lambda { subject.save_asset("/foo.txt", "Content") }.should create_file(File.join(content_path, "assets", "foo.txt")).with_content("Content")
|
185
|
+
end
|
186
|
+
|
187
|
+
it "creates any neccesary parent directories" do
|
188
|
+
lambda { subject.save_asset("/bar/foo.txt", "Content") }.should create_file(File.join(content_path, "assets", "bar", "foo.txt"))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it { should implement(:get_asset, "/foo.txt") }
|
193
|
+
describe "retrieving assets" do
|
194
|
+
it "returns nil if the path provided is not a file with an extension" do
|
195
|
+
subject.get_asset("/foo").should be_nil
|
196
|
+
subject.get_asset("/foo/").should be_nil
|
197
|
+
end
|
198
|
+
|
199
|
+
it "returns nil if the asset did not exist" do
|
200
|
+
subject.get_asset("/foo.txt").should be_nil
|
201
|
+
end
|
202
|
+
|
203
|
+
it "returns the file's contents if ithe asset does exist" do
|
204
|
+
subject.save_asset("/foo.txt", "Content")
|
205
|
+
subject.get_asset("/foo.txt").should == "Content"
|
206
|
+
end
|
207
|
+
end
|
173
208
|
end
|
@@ -7,49 +7,15 @@ describe Noumenon::Repository do
|
|
7
7
|
repo.options[:foo].should == "bar"
|
8
8
|
end
|
9
9
|
end
|
10
|
+
|
11
|
+
it { should_not implement(:put, "/", foo: "bar").with_message("This repository type does not support updating it's contents.") }
|
12
|
+
it { should_not implement(:get, "/").with_message("This repository type does not support reading it's contents.") }
|
13
|
+
it { should_not implement(:children).with_message("This repository type does not support listing children from a path.") }
|
14
|
+
it { should_not implement(:save_asset, "/example.txt", "Content").with_message("This repository type does not support saving assets.") }
|
15
|
+
it { should_not implement(:get_asset, "/example.txt").with_message("This repository type does not support loading assets.") }
|
10
16
|
|
11
|
-
it { should
|
12
|
-
|
13
|
-
|
14
|
-
lambda { subject.put("foo/bar", "Content") }.should raise_error NotImplementedError
|
15
|
-
end
|
16
|
-
|
17
|
-
it "provides some details in the error message" do
|
18
|
-
begin
|
19
|
-
subject.put("foo/bar", "Content")
|
20
|
-
rescue NotImplementedError => e
|
21
|
-
e.to_s.should == "This repository type does not support updating it's contents."
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
it { should respond_to(:get) }
|
27
|
-
describe "calling #get" do
|
28
|
-
it "raises a NotImplementedError" do
|
29
|
-
lambda { subject.get("foo/bar") }.should raise_error NotImplementedError
|
30
|
-
end
|
31
|
-
|
32
|
-
it "provides some details in the error message" do
|
33
|
-
begin
|
34
|
-
subject.get("foo/bar")
|
35
|
-
rescue NotImplementedError => e
|
36
|
-
e.to_s.should == "This repository type does not support reading it's contents."
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
it { should respond_to(:children) }
|
42
|
-
describe "calling #children" do
|
43
|
-
it "raises a NotImplementedError" do
|
44
|
-
lambda { subject.children("/") }.should raise_error NotImplementedError
|
45
|
-
end
|
46
|
-
|
47
|
-
it "provides some details in the error message" do
|
48
|
-
begin
|
49
|
-
subject.children("/")
|
50
|
-
rescue NotImplementedError => e
|
51
|
-
e.to_s.should == "This repository type does not support listing children from a path."
|
52
|
-
end
|
53
|
-
end
|
17
|
+
it { should implement(:asset_url, "/example.txt") }
|
18
|
+
it "returns the default URL below /assets/ when requested" do
|
19
|
+
subject.asset_url("/example.txt").should == "/assets/example.txt"
|
54
20
|
end
|
55
21
|
end
|
data/spec/noumenon_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Noumenon do
|
4
|
-
describe "
|
4
|
+
describe "accessing the content repository" do
|
5
5
|
it "can be set" do
|
6
6
|
Noumenon.content_repository = Noumenon::Repository.new
|
7
7
|
end
|
@@ -12,6 +12,22 @@ describe Noumenon do
|
|
12
12
|
Noumenon.content_repository.should == repo
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
describe "accessing the asset repository" do
|
17
|
+
it "returns the content repository if it has not been set" do
|
18
|
+
Noumenon.content_repository = Noumenon::Repository.new
|
19
|
+
Noumenon.asset_repository.should === Noumenon.content_repository
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns the set asset repository if one has been set" do
|
23
|
+
content = Noumenon::Repository.new
|
24
|
+
assets = Noumenon::Repository.new
|
25
|
+
|
26
|
+
Noumenon.content_repository = content
|
27
|
+
Noumenon.asset_repository = assets
|
28
|
+
Noumenon.asset_repository.should === assets
|
29
|
+
end
|
30
|
+
end
|
15
31
|
|
16
32
|
describe "setting the theme" do
|
17
33
|
context "when assigning with a Theme object" do
|
@@ -1,18 +1,40 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class CreateFileExpectation
|
2
|
+
def initialize(path)
|
3
|
+
@path = path
|
4
|
+
end
|
5
|
+
|
6
|
+
def with_content(content)
|
7
|
+
@content = content
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(block)
|
3
12
|
block.call
|
4
|
-
|
13
|
+
|
14
|
+
exists = File.exist?(@path)
|
15
|
+
if @content
|
16
|
+
exists && (File.read(@path) == @content)
|
17
|
+
else
|
18
|
+
exists
|
19
|
+
end
|
5
20
|
end
|
6
21
|
|
7
|
-
failure_message
|
8
|
-
"expected a file to be created at #{File.expand_path(path)}"
|
22
|
+
def failure_message
|
23
|
+
msg = "expected a file to be created at #{File.expand_path(@path)}"
|
24
|
+
msg << " with content #{@content}" if @content
|
25
|
+
|
26
|
+
msg
|
9
27
|
end
|
10
28
|
|
11
|
-
description
|
12
|
-
"create a file at #{File.expand_path(path)}"
|
29
|
+
def description
|
30
|
+
"create a file at #{File.expand_path(@path)}"
|
13
31
|
end
|
14
32
|
end
|
15
33
|
|
34
|
+
def create_file(block)
|
35
|
+
CreateFileExpectation.new(block)
|
36
|
+
end
|
37
|
+
|
16
38
|
RSpec::Matchers.define :create_directory do |path|
|
17
39
|
match do |block|
|
18
40
|
block.call
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class ImplementedExpectation
|
2
|
+
def initialize(method, *args)
|
3
|
+
@method = method
|
4
|
+
@args = args
|
5
|
+
@message = "NotImplementedError"
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_message(message)
|
9
|
+
@message = message
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(obj)
|
14
|
+
implemented = true
|
15
|
+
|
16
|
+
begin
|
17
|
+
obj.send(@method, *@args)
|
18
|
+
rescue NotImplementedError => e
|
19
|
+
implemented = false
|
20
|
+
actual_message = e.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
unless negative_expectation?
|
24
|
+
implemented
|
25
|
+
else
|
26
|
+
implemented || actual_message != @message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_message_for_should
|
31
|
+
"expected to implement #{@method}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure_message_for_should_not
|
35
|
+
message = "expected not to implement #{@method}"
|
36
|
+
if @message
|
37
|
+
message << " with the message '#{@message}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
message
|
41
|
+
end
|
42
|
+
|
43
|
+
def description
|
44
|
+
"implement #{@method}"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def negative_expectation?
|
49
|
+
caller.first(3).find { |s| s =~ /should_not/ }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def implement(method, *args)
|
54
|
+
ImplementedExpectation.new(method, *args)
|
55
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: noumenon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.1.
|
5
|
+
version: 0.1.4
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jon Wood
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
13
|
+
date: 2011-05-27 00:00:00 +01:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -19,9 +19,9 @@ dependencies:
|
|
19
19
|
requirement: &id001 !ruby/object:Gem::Requirement
|
20
20
|
none: false
|
21
21
|
requirements:
|
22
|
-
- -
|
22
|
+
- - ~>
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: 2.9
|
24
|
+
version: "2.9"
|
25
25
|
type: :runtime
|
26
26
|
version_requirements: *id001
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirement: &id002 !ruby/object:Gem::Requirement
|
31
31
|
none: false
|
32
32
|
requirements:
|
33
|
-
- -
|
33
|
+
- - ~>
|
34
34
|
- !ruby/object:Gem::Version
|
35
35
|
version: "2.2"
|
36
36
|
type: :runtime
|
@@ -41,9 +41,9 @@ dependencies:
|
|
41
41
|
requirement: &id003 !ruby/object:Gem::Requirement
|
42
42
|
none: false
|
43
43
|
requirements:
|
44
|
-
- -
|
44
|
+
- - ~>
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.2
|
46
|
+
version: "1.2"
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id003
|
49
49
|
- !ruby/object:Gem::Dependency
|
@@ -52,9 +52,9 @@ dependencies:
|
|
52
52
|
requirement: &id004 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
54
54
|
requirements:
|
55
|
-
- -
|
55
|
+
- - ~>
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
version: 0.3
|
57
|
+
version: "0.3"
|
58
58
|
type: :development
|
59
59
|
version_requirements: *id004
|
60
60
|
- !ruby/object:Gem::Dependency
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
requirement: &id005 !ruby/object:Gem::Requirement
|
64
64
|
none: false
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.0.0.beta1
|
69
69
|
type: :development
|
@@ -74,9 +74,9 @@ dependencies:
|
|
74
74
|
requirement: &id006 !ruby/object:Gem::Requirement
|
75
75
|
none: false
|
76
76
|
requirements:
|
77
|
-
- -
|
77
|
+
- - ~>
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: 0.10
|
79
|
+
version: "0.10"
|
80
80
|
type: :development
|
81
81
|
version_requirements: *id006
|
82
82
|
- !ruby/object:Gem::Dependency
|
@@ -85,9 +85,9 @@ dependencies:
|
|
85
85
|
requirement: &id007 !ruby/object:Gem::Requirement
|
86
86
|
none: false
|
87
87
|
requirements:
|
88
|
-
- -
|
88
|
+
- - ~>
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: 0.9
|
90
|
+
version: "0.9"
|
91
91
|
type: :development
|
92
92
|
version_requirements: *id007
|
93
93
|
- !ruby/object:Gem::Dependency
|
@@ -96,9 +96,9 @@ dependencies:
|
|
96
96
|
requirement: &id008 !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
98
98
|
requirements:
|
99
|
-
- -
|
99
|
+
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: 1.6
|
101
|
+
version: "1.6"
|
102
102
|
type: :development
|
103
103
|
version_requirements: *id008
|
104
104
|
- !ruby/object:Gem::Dependency
|
@@ -107,9 +107,9 @@ dependencies:
|
|
107
107
|
requirement: &id009 !ruby/object:Gem::Requirement
|
108
108
|
none: false
|
109
109
|
requirements:
|
110
|
-
- -
|
110
|
+
- - ~>
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
version: 2.5
|
112
|
+
version: "2.5"
|
113
113
|
type: :development
|
114
114
|
version_requirements: *id009
|
115
115
|
- !ruby/object:Gem::Dependency
|
@@ -118,9 +118,9 @@ dependencies:
|
|
118
118
|
requirement: &id010 !ruby/object:Gem::Requirement
|
119
119
|
none: false
|
120
120
|
requirements:
|
121
|
-
- -
|
121
|
+
- - ~>
|
122
122
|
- !ruby/object:Gem::Version
|
123
|
-
version: 0.14
|
123
|
+
version: "0.14"
|
124
124
|
type: :development
|
125
125
|
version_requirements: *id010
|
126
126
|
- !ruby/object:Gem::Dependency
|
@@ -129,9 +129,9 @@ dependencies:
|
|
129
129
|
requirement: &id011 !ruby/object:Gem::Requirement
|
130
130
|
none: false
|
131
131
|
requirements:
|
132
|
-
- -
|
132
|
+
- - ~>
|
133
133
|
- !ruby/object:Gem::Version
|
134
|
-
version: 0.7
|
134
|
+
version: "0.7"
|
135
135
|
type: :development
|
136
136
|
version_requirements: *id011
|
137
137
|
description: |
|
@@ -165,6 +165,8 @@ files:
|
|
165
165
|
- README.md
|
166
166
|
- Rakefile
|
167
167
|
- bin/noumenon
|
168
|
+
- features/asset_serving/from_content_repositories.feature
|
169
|
+
- features/asset_serving/from_themes.feature
|
168
170
|
- features/generator/site_generator.feature
|
169
171
|
- features/mounted_applications.feature
|
170
172
|
- features/step_definitions/asset_steps.rb
|
@@ -179,10 +181,10 @@ files:
|
|
179
181
|
- features/template_extensions.feature
|
180
182
|
- features/template_rendering/dynamic.feature
|
181
183
|
- features/template_rendering/static.feature
|
184
|
+
- features/template_tags/asset_paths.feature
|
182
185
|
- features/template_tags/assign_to.feature
|
183
186
|
- features/template_tags/content_from.feature
|
184
187
|
- features/template_tags/nav_menu.feature
|
185
|
-
- features/theme_assets.feature
|
186
188
|
- generators/repository/index.yml
|
187
189
|
- generators/site/Gemfile
|
188
190
|
- generators/site/config.ru
|
@@ -213,6 +215,7 @@ files:
|
|
213
215
|
- spec/noumenon_spec.rb
|
214
216
|
- spec/spec_helper.rb
|
215
217
|
- spec/support/file_matchers.rb
|
218
|
+
- spec/support/implementation_matcher.rb
|
216
219
|
- spec/support/templates/basic_template.html
|
217
220
|
- spec/support/templates/template_with_fields.html
|
218
221
|
has_rdoc: true
|
@@ -244,6 +247,8 @@ signing_key:
|
|
244
247
|
specification_version: 3
|
245
248
|
summary: A flexible content management system.
|
246
249
|
test_files:
|
250
|
+
- features/asset_serving/from_content_repositories.feature
|
251
|
+
- features/asset_serving/from_themes.feature
|
247
252
|
- features/generator/site_generator.feature
|
248
253
|
- features/mounted_applications.feature
|
249
254
|
- features/step_definitions/asset_steps.rb
|
@@ -258,10 +263,10 @@ test_files:
|
|
258
263
|
- features/template_extensions.feature
|
259
264
|
- features/template_rendering/dynamic.feature
|
260
265
|
- features/template_rendering/static.feature
|
266
|
+
- features/template_tags/asset_paths.feature
|
261
267
|
- features/template_tags/assign_to.feature
|
262
268
|
- features/template_tags/content_from.feature
|
263
269
|
- features/template_tags/nav_menu.feature
|
264
|
-
- features/theme_assets.feature
|
265
270
|
- spec/noumenon/repository/file_system_spec.rb
|
266
271
|
- spec/noumenon/repository_spec.rb
|
267
272
|
- spec/noumenon/template_spec.rb
|
@@ -269,5 +274,6 @@ test_files:
|
|
269
274
|
- spec/noumenon_spec.rb
|
270
275
|
- spec/spec_helper.rb
|
271
276
|
- spec/support/file_matchers.rb
|
277
|
+
- spec/support/implementation_matcher.rb
|
272
278
|
- spec/support/templates/basic_template.html
|
273
279
|
- spec/support/templates/template_with_fields.html
|