noumenon 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 0.1.3
2
+
3
+ ## New Features
4
+
5
+ * Add the nav_menu template tag for generating menus from the content repository.
6
+ * Make RSpec output color when running Rake
7
+ * Tidied up the Cucumber features
8
+
9
+ ## Bug Fixes
10
+
1
11
  # 0.1.2
2
12
 
3
13
  ## New Features
data/Rakefile CHANGED
@@ -5,7 +5,9 @@ require 'yard/rake/yardoc_task'
5
5
 
6
6
  task :default => [ :spec, :cucumber ]
7
7
 
8
- RSpec::Core::RakeTask.new
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.rspec_opts = %w(--color)
10
+ end
9
11
 
10
12
  Cucumber::Rake::Task.new do |t|
11
13
  t.cucumber_opts = %w(--format progress)
@@ -0,0 +1,26 @@
1
+ def check_for_items(menu_items)
2
+ menu_items.hashes.each do |item|
3
+ page.should have_css "li a[href='#{item["Link Target"]}']", :text => item["Label"]
4
+ end
5
+ end
6
+
7
+ Then /^a menu with the following items should be visible:$/ do |menu_items|
8
+ page.should have_css "nav ul"
9
+ within "nav ul" do
10
+ check_for_items(menu_items)
11
+ end
12
+ end
13
+
14
+ Then /^the "([^"]*)" menu item should have these children:$/ do |label, items|
15
+ within("nav ul li", :text => label) do
16
+ check_for_items(items)
17
+ end
18
+ end
19
+
20
+ Then /^these menu items should not be visible:$/ do |items|
21
+ within "nav ul" do
22
+ items.hashes.each do |item|
23
+ page.should have_no_css "li a[href='#{item["Link Target"]}']", :text => item["Label"]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ Feature: The assign_to tag
2
+ As a designer
3
+ I want to assign a variable in my template
4
+ So that I can use it multiple times later
5
+
6
+ Scenario: Assigning the variable
7
+ Given this template exists at "assign.nou.html"
8
+ """
9
+ {{ "Hello" | assign_to: "hello" }}
10
+ <h1>{{ hello }}</h1>
11
+ """
12
+ And this content item exists at "/assign"
13
+ | template | assign |
14
+ When I view "/assign"
15
+ Then the headline should be "Hello"
@@ -0,0 +1,33 @@
1
+ Feature: Including content in a template
2
+ As a designer
3
+ I use content from elsewhere in the repository
4
+ So that it can be changed by the end user
5
+
6
+ Scenario: The content item exists
7
+ Given this template exists at "inclusion.nou.html"
8
+ """
9
+ {{ "/includes/header" | item_at_path | assign_to: "header" }}
10
+ <h1>{{ header.title }}</h1>
11
+ """
12
+ And this content item exists at "/inclusion"
13
+ | template | inclusion |
14
+ And this content item exists at "/includes/header"
15
+ | title | The site title |
16
+ When I view "/inclusion"
17
+ Then the headline should be "The site title"
18
+
19
+ Scenario: The content item does not exist
20
+ Given this template exists at "missing_content.nou.html"
21
+ """
22
+ {{ "/includes/header" | item_at_path | assign_to: "header" }}
23
+ {% if header %}
24
+ <h1>Header Found</h1>
25
+ {% else %}
26
+ <h1>No Header</h1>
27
+ {% endif %}
28
+ """
29
+ And this content item exists at "/missing_content"
30
+ | template | missing_content |
31
+ And no content item exists at "/includes/header"
32
+ When I view "/missing_content"
33
+ Then the headline should be "No Header"
@@ -0,0 +1,77 @@
1
+ Feature: Navigation menus
2
+ As a designer
3
+ I want to build a navigation menu from the content repository
4
+ So that my users can add new pages
5
+
6
+ Scenario: Basic menu
7
+ Given this template exists at "basic_menu.nou.html"
8
+ """
9
+ <nav>
10
+ {% nav_menu %}
11
+ </nav>
12
+ """
13
+ And this content item exists at "/about"
14
+ | title | About |
15
+ | template | basic_menu |
16
+ And this content item exists at "/contact"
17
+ | title | Contact |
18
+ And this content item exists at "/team"
19
+ | title | Team |
20
+ When I view "/about"
21
+ Then a menu with the following items should be visible:
22
+ | Label | Link Target |
23
+ | About | /about |
24
+ | Contact | /contact |
25
+ | Team | /team |
26
+
27
+ Scenario: Multi-level menu
28
+ Given this template exists at "deep_menu.nou.html"
29
+ """
30
+ <nav>
31
+ {% nav_menu depth=2 %}
32
+ </nav>
33
+ """
34
+ And this content item exists at "/about"
35
+ | title | About |
36
+ | template | deep_menu |
37
+ And this content item exists at "/about/team"
38
+ | title | Team |
39
+ And this content item exists at "/contact"
40
+ | title | Contact |
41
+ And this content item exists at "/team"
42
+ | title | Team |
43
+ When I view "/about"
44
+ Then a menu with the following items should be visible:
45
+ | Label | Link Target |
46
+ | About | /about |
47
+ | Contact | /contact |
48
+ | Team | /team |
49
+ And the "About" menu item should have these children:
50
+ | Label | Link Target |
51
+ | Team | /about/team |
52
+
53
+ Scenario: Non-root menus
54
+ Given this template exists at "deep_menu.nou.html"
55
+ """
56
+ <nav>
57
+ {% nav_menu root=/about %}
58
+ </nav>
59
+ """
60
+ And this content item exists at "/about"
61
+ | title | About |
62
+ | template | deep_menu |
63
+ And this content item exists at "/about/team"
64
+ | title | Team |
65
+ And this content item exists at "/contact"
66
+ | title | Contact |
67
+ And this content item exists at "/team"
68
+ | title | Team |
69
+ When I view "/about"
70
+ Then a menu with the following items should be visible:
71
+ | Label | Link Target |
72
+ | Team | /about/team |
73
+ And these menu items should not be visible:
74
+ | Label | Link Target |
75
+ | About | /about |
76
+ | Contact | /contact |
77
+ | Team | /team |
@@ -56,6 +56,29 @@ class Noumenon::Repository::FileSystem < Noumenon::Repository
56
56
  path = File.join(path, "index") if File.exist?(repository_path(path))
57
57
  File.open(repository_path("#{path}.yml"), "w") { |f| f.print content.symbolize_keys.to_yaml }
58
58
  end
59
+
60
+ def children(root = "/", depth = 1)
61
+ root.gsub! /\/$/, ''
62
+
63
+ pattern = File.join(repository_path(root), "*")
64
+ items = Dir.glob(pattern).collect do |i|
65
+ i.gsub(repository_path("/"), "/").
66
+ gsub(".yml", "")
67
+ end
68
+
69
+ # We're not interested in indexes, since they're not children.
70
+ items.delete(File.join(root, "index"))
71
+
72
+ items.reject { |i| i.gsub(root, "").split("/").size > depth + 1 }.collect do |item|
73
+ path = item == "" ? "/" : item
74
+
75
+ # Loading everyitem probably isn't scalable, but it'll do for now. We can add caching
76
+ # at a later date if needed.
77
+ page = get(path).merge({ path: path })
78
+ page[:children] = children(page[:path], depth - 1) if depth - 1 > 0
79
+ page
80
+ end
81
+ end
59
82
 
60
83
  private
61
84
  # Return the on-disk path to a repository item.
@@ -36,4 +36,8 @@ class Noumenon::Repository
36
36
  def get(path)
37
37
  raise NotImplementedError.new("This repository type does not support reading it's contents.")
38
38
  end
39
+
40
+ def children(root = "/")
41
+ raise NotImplementedError.new("This repository type does not support listing children from a path.")
42
+ end
39
43
  end
@@ -2,7 +2,7 @@ require 'noumenon/template'
2
2
 
3
3
  # Liquid extensions provided by Noumenon.
4
4
  #
5
- # @see [ Noumenon::Template::CoreTags::Filters ]
5
+ # @see http://github.com/Noumenon/noumenon/wiki/Template%20Tags
6
6
  module Noumenon::Template::CoreTags
7
7
  # The core set of filters provided by Noumenon for use in templates.
8
8
  #
@@ -39,4 +39,54 @@ module Noumenon::Template::CoreTags
39
39
  end
40
40
  end
41
41
  Liquid::Template.register_filter(Filters)
42
+
43
+ # Renders a navigation menu.
44
+ #
45
+ # @api public
46
+ # @example
47
+ # <header>
48
+ # {{ nav_menu }}
49
+ # </header>
50
+ #
51
+ # <div id="sidebar">
52
+ # <h1>About Our Stuff</h1>
53
+ # <nav>
54
+ # {{ nav_menu root=/about depth=1 }}
55
+ # </nav>
56
+ # </div>
57
+ class NavigationTag < Liquid::Tag
58
+ def initialize(tag_name, syntax, tokens)
59
+ super
60
+
61
+ @options = { depth: 1, root: "/" }
62
+
63
+ syntax.split(" ").each do |option|
64
+ key, value = option.split("=")
65
+ case key
66
+ when "depth"
67
+ @options[:depth] = value.to_i
68
+ when "root"
69
+ @options[:root] = value
70
+ end
71
+ end
72
+ end
73
+
74
+ def render_list(items)
75
+ list = items.collect do |item|
76
+ str = "<li>"
77
+ str << %Q{<a href="#{item[:path]}">#{item[:title]}</a>}
78
+ str << render_list(item[:children]) if item[:children]
79
+ str << "</li>"
80
+
81
+ str
82
+ end
83
+
84
+ "<ul>#{list.join("\n")}</ul>"
85
+ end
86
+
87
+ def render(context)
88
+ render_list Noumenon.content_repository.children(@options[:root], @options[:depth])
89
+ end
90
+ end
91
+ Liquid::Template.register_tag('nav_menu', NavigationTag)
42
92
  end
@@ -1,5 +1,5 @@
1
1
  module Noumenon
2
2
  # The current version of Noumenon.
3
3
  # @api public
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -112,4 +112,62 @@ describe Noumenon::Repository::FileSystem do
112
112
  end
113
113
  end
114
114
  end
115
+
116
+ describe "listing child items" do
117
+ before do
118
+ subject.put("/", title: "Home")
119
+ subject.put("/about", title: "About")
120
+ subject.put("/about/team", title: "Team")
121
+ subject.put("/about/location", title: "Location")
122
+ subject.put("/contact", title: "Contact")
123
+ end
124
+
125
+ context "a single level from the root" do
126
+ it "returns an array" do
127
+ subject.children.should be_instance_of Array
128
+ end
129
+
130
+ it "does not list items below the top level" do
131
+ subject.children("/").should have(2).items
132
+ end
133
+
134
+ describe "each item" do
135
+ let(:items) { subject.children("/") }
136
+
137
+ it "is a hash" do
138
+ items[0].should be_instance_of Hash
139
+ end
140
+
141
+ it "has a path" do
142
+ items[0][:path].should == "/about"
143
+ items[1][:path].should == "/contact"
144
+ end
145
+
146
+ it "has a title" do
147
+ items[0][:title].should == "About"
148
+ items[1][:title].should == "Contact"
149
+ end
150
+ end
151
+ end
152
+
153
+ context "multiple levels from the root" do
154
+ let(:items) { subject.children("/", 2) }
155
+
156
+ describe "an item with children" do
157
+ it "has an array of children" do
158
+ items[0][:children].should have(2).items
159
+ end
160
+ end
161
+ end
162
+
163
+ context "from a point below the root" do
164
+ let(:items) { subject.children("/about") }
165
+
166
+ it "lists only items below that point" do
167
+ items.should have(2).items
168
+ items[0][:title].should == "Location"
169
+ items[1][:title].should == "Team"
170
+ end
171
+ end
172
+ end
115
173
  end
@@ -37,4 +37,19 @@ describe Noumenon::Repository do
37
37
  end
38
38
  end
39
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
54
+ end
40
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.2
5
+ version: 0.1.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jon Wood
@@ -165,20 +165,23 @@ files:
165
165
  - README.md
166
166
  - Rakefile
167
167
  - bin/noumenon
168
- - features/dynamic_template_rendering.feature
169
168
  - features/generator/site_generator.feature
170
- - features/including_content_from_a_template.feature
171
169
  - features/mounted_applications.feature
172
- - features/static_template_rendering.feature
173
170
  - features/step_definitions/asset_steps.rb
174
171
  - features/step_definitions/content_steps.rb
175
172
  - features/step_definitions/generator_steps.rb
176
173
  - features/step_definitions/liquid_steps.rb
174
+ - features/step_definitions/navigation_steps.rb
177
175
  - features/step_definitions/request_steps.rb
178
176
  - features/step_definitions/theme_steps.rb
179
177
  - features/support/env.rb
180
178
  - features/support/theme/theme.yml
181
179
  - features/template_extensions.feature
180
+ - features/template_rendering/dynamic.feature
181
+ - features/template_rendering/static.feature
182
+ - features/template_tags/assign_to.feature
183
+ - features/template_tags/content_from.feature
184
+ - features/template_tags/nav_menu.feature
182
185
  - features/theme_assets.feature
183
186
  - generators/repository/index.yml
184
187
  - generators/site/Gemfile
@@ -241,20 +244,23 @@ signing_key:
241
244
  specification_version: 3
242
245
  summary: A flexible content management system.
243
246
  test_files:
244
- - features/dynamic_template_rendering.feature
245
247
  - features/generator/site_generator.feature
246
- - features/including_content_from_a_template.feature
247
248
  - features/mounted_applications.feature
248
- - features/static_template_rendering.feature
249
249
  - features/step_definitions/asset_steps.rb
250
250
  - features/step_definitions/content_steps.rb
251
251
  - features/step_definitions/generator_steps.rb
252
252
  - features/step_definitions/liquid_steps.rb
253
+ - features/step_definitions/navigation_steps.rb
253
254
  - features/step_definitions/request_steps.rb
254
255
  - features/step_definitions/theme_steps.rb
255
256
  - features/support/env.rb
256
257
  - features/support/theme/theme.yml
257
258
  - features/template_extensions.feature
259
+ - features/template_rendering/dynamic.feature
260
+ - features/template_rendering/static.feature
261
+ - features/template_tags/assign_to.feature
262
+ - features/template_tags/content_from.feature
263
+ - features/template_tags/nav_menu.feature
258
264
  - features/theme_assets.feature
259
265
  - spec/noumenon/repository/file_system_spec.rb
260
266
  - spec/noumenon/repository_spec.rb
@@ -1,17 +0,0 @@
1
- Feature: Including content in a template
2
- As a designer
3
- I want to include a piece of content within a page
4
- So that it can be changed by the end user
5
-
6
- Scenario: The content item exists
7
- Given this template exists at "inclusion.nou.html"
8
- """
9
- {{ "/includes/header" | item_at_path | assign_to: "header" }}
10
- <h1>{{ header.title }}</h1>
11
- """
12
- And this content item exists at "/inclusion"
13
- | template | inclusion |
14
- And this content item exists at "/includes/header"
15
- | title | The site title |
16
- When I view "/inclusion"
17
- Then the headline should be "The site title"