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 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 just play around with things in practice then your best bet is to run `noumenon site example_site`
18
- from a terminal, which will generate you a Gemfile, config.ru, content repository, and theme. If you want to run it
19
- any Rack based application server should work. I've tested under Thin and Unicorn, and Heroku will work if you want
20
- to publish a static site.
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
+ """
@@ -1,4 +1,4 @@
1
- Feature: Theme Assets
1
+ Feature: Serving assets from a theme
2
2
  As a designer
3
3
  I want to serve files from my theme
4
4
  So that I can bundle any required images with it
@@ -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
@@ -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.expand_path('../tmp/content', __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 content_path
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
- file = repository_path("#{path}.yml")
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
- File.open(repository_path("#{path}.yml"), "w") { |f| f.print content.symbolize_keys.to_yaml }
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
@@ -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
- raise NotImplementedError.new("This repository type does not support updating it's contents.")
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
- raise NotImplementedError.new("This repository type does not support reading it's contents.")
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
- def children(root = "/")
41
- raise NotImplementedError.new("This repository type does not support listing children from a path.")
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%20Tags
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
@@ -1,5 +1,5 @@
1
1
  module Noumenon
2
2
  # The current version of Noumenon.
3
3
  # @api public
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
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", ">= 2.9.1"
35
- s.add_dependency "liquid", ">= 2.2"
36
- s.add_dependency "sinatra", ">= 1.2.3"
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", ">= 0.3.6"
39
- s.add_development_dependency "capybara", ">= 1.0.0.beta1"
40
- s.add_development_dependency "cucumber", ">= 0.10.2"
41
- s.add_development_dependency "rake", ">= 0.9.0"
42
- s.add_development_dependency "rdiscount", ">= 1.6.8"
43
- s.add_development_dependency "rspec", ">= 2.5.0"
44
- s.add_development_dependency "thor", ">= 0.14.6"
45
- s.add_development_dependency "yard", ">= 0.7.1"
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 respond_to(:put) }
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 respond_to(:put) }
12
- describe "calling #put" do
13
- it "raises a NotImplementedError" do
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
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Noumenon do
4
- describe "managing the content repository" do
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
- RSpec::Matchers.define :create_file do |path|
2
- match do |block|
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
- File.exist?(path)
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 do
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 do
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.3
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-26 00:00:00 +01:00
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.1
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.3
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.6
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.2
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.0
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.8
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.0
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.6
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.1
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