noumenon 0.1.2 → 0.1.3

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/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"