bonsai 1.0.0

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.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/.kick +26 -0
  4. data/LICENSE +20 -0
  5. data/README.md +65 -0
  6. data/Rakefile +64 -0
  7. data/VERSION +1 -0
  8. data/benchmark/associations.rb +51 -0
  9. data/bin/bonsai +69 -0
  10. data/bonsai.gemspec +142 -0
  11. data/lib/bonsai.rb +60 -0
  12. data/lib/bonsai/console.rb +8 -0
  13. data/lib/bonsai/exporter.rb +103 -0
  14. data/lib/bonsai/generate.rb +24 -0
  15. data/lib/bonsai/navigation.rb +7 -0
  16. data/lib/bonsai/page.rb +192 -0
  17. data/lib/bonsai/template.rb +31 -0
  18. data/lib/bonsai/templates/content/index/default.yml +3 -0
  19. data/lib/bonsai/templates/public/.htaccess +28 -0
  20. data/lib/bonsai/templates/public/docs/css/base.less +1 -0
  21. data/lib/bonsai/templates/templates/default.mustache +14 -0
  22. data/lib/bonsai/webserver.rb +19 -0
  23. data/spec/bonsai/console_spec.rb +12 -0
  24. data/spec/bonsai/exporter_spec.rb +142 -0
  25. data/spec/bonsai/generate_spec.rb +40 -0
  26. data/spec/bonsai/navigation_spec.rb +28 -0
  27. data/spec/bonsai/page_spec.rb +225 -0
  28. data/spec/bonsai/template_spec.rb +19 -0
  29. data/spec/bonsai_spec.rb +21 -0
  30. data/spec/spec.opts +1 -0
  31. data/spec/spec_helper.rb +14 -0
  32. data/spec/support/broken/content/broken_page/demo-template.yml +3 -0
  33. data/spec/support/content/1.about-us/1.contact/1.child/a_file_asset.txt +0 -0
  34. data/spec/support/content/1.about-us/1.contact/1.child/demo-template.yml +1 -0
  35. data/spec/support/content/1.about-us/1.contact/demo-template.yml +7 -0
  36. data/spec/support/content/1.about-us/1.contact/images/image001.jpg +0 -0
  37. data/spec/support/content/1.about-us/1.contact/magic/image001.jpg +0 -0
  38. data/spec/support/content/1.about-us/1.contact/magic/image002.jpg +0 -0
  39. data/spec/support/content/1.about-us/demo-template.yml +1 -0
  40. data/spec/support/content/1.about-us/history/a_file_asset.txt +0 -0
  41. data/spec/support/content/1.about-us/history/child/a_file_asset.txt +0 -0
  42. data/spec/support/content/1.about-us/history/child/demo-template.yml +1 -0
  43. data/spec/support/content/1.about-us/history/demo-template.yml +1 -0
  44. data/spec/support/content/1.about-us/history/image001.jpg +0 -0
  45. data/spec/support/content/1.about-us/history/images/image001.jpg +0 -0
  46. data/spec/support/content/1.about-us/history/magic/image001.jpg +0 -0
  47. data/spec/support/content/1.about-us/history/magic/image002.jpg +0 -0
  48. data/spec/support/content/10.many-pages/demo-template.yml +1 -0
  49. data/spec/support/content/2.products/1.product-a/demo-template.yml +1 -0
  50. data/spec/support/content/2.products/2.product-b/demo-template.yml +1 -0
  51. data/spec/support/content/2.products/demo-template.yml +1 -0
  52. data/spec/support/content/index/demo-template.yml +1 -0
  53. data/spec/support/content/legals/terms-and-conditions/demo-template.yml +1 -0
  54. data/spec/support/public/.htaccess +31 -0
  55. data/spec/support/public/js/script.js +19 -0
  56. data/spec/support/public/stylesheets/base.less +2 -0
  57. data/spec/support/public/stylesheets/broken.less +2 -0
  58. data/spec/support/templates/demo-template.mustache +19 -0
  59. data/spec/support/templates/partials/inserted.mustache +1 -0
  60. data/vendor/yui-compressor/yuicompressor-2.4.2.jar +0 -0
  61. metadata +201 -0
@@ -0,0 +1 @@
1
+ /* Put your styles here */
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
+ <title>{{name}}</title>
6
+ <link rel="stylesheet" href="/css/base.css" type="text/css">
7
+ </head>
8
+ <body id="{{slug}}" class="default">
9
+ <header>
10
+ <h1>{{headline}}</h1>
11
+ {{{body}}}
12
+ </header>
13
+ </body>
14
+ </html>
@@ -0,0 +1,19 @@
1
+ module Bonsai
2
+ class StaticPassThrough < Rack::Static
3
+ def call(env)
4
+ result = super
5
+ return result unless result[0] == 404 || result[0] == "404"
6
+ @app.call(env)
7
+ end
8
+ end
9
+
10
+ class DevelopmentServer < Sinatra::Base
11
+ get '/' do
12
+ Page.find("index").render
13
+ end
14
+
15
+ get '/*' do
16
+ Page.find(params[:splat].to_s).render
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+
3
+ describe Bonsai::Console do
4
+ it "should respond to new" do
5
+ Bonsai::Console.should respond_to(:new)
6
+ end
7
+
8
+ it "should start a new IRB session" do
9
+ IRB.should_receive(:start)
10
+ Bonsai::Console.new
11
+ end
12
+ end
@@ -0,0 +1,142 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+
3
+ describe Bonsai::Exporter do
4
+ after :suite do
5
+ FileUtils.rm_rf Bonsai::Exporter.path
6
+ end
7
+
8
+ it "should have a path" do
9
+ Bonsai::Exporter.path.should_not be_nil
10
+ end
11
+
12
+ it "should set the path" do
13
+ Bonsai::Exporter.path = 'support/exporter/test'
14
+ Bonsai::Exporter.path.should == 'support/exporter/test'
15
+ Bonsai::Exporter.path = BONSAI_PATH + "/output"
16
+ end
17
+
18
+ shared_examples_for "lesscss" do
19
+ it "should process .less files to .css" do
20
+ File.exists?("#{Bonsai::Exporter.path}/stylesheets/base.css").should be_true
21
+ end
22
+
23
+ it "should log an error when badly formatted less is supplied (and not raise an exception)" do
24
+ Bonsai.should_receive(:log)
25
+ lambda { Bonsai::Exporter.send(:generate_css_from_less) }.should_not raise_error(Less::SyntaxError)
26
+ end
27
+ end
28
+
29
+ describe "process!" do
30
+ describe "tasks" do
31
+ before :all do
32
+ FileUtils.rm_rf Bonsai::Exporter.path
33
+ Bonsai::Exporter.process!
34
+ end
35
+
36
+ it_should_behave_like "lesscss"
37
+
38
+ # Uncompressed CSS
39
+ it "should be processed with less" do
40
+ File.read("#{Bonsai::Exporter.path}/stylesheets/base.css").should == ".mymixin, #content { display: block; }\n"
41
+ end
42
+
43
+ it "should not write the base.css file to the public directory" do
44
+ File.exists?("#{Bonsai.root_dir}/public/base.css").should_not be_true
45
+ end
46
+
47
+ end
48
+
49
+ describe "expectations" do
50
+ after do
51
+ Bonsai::Exporter.process!
52
+ end
53
+
54
+ it "should copy assets" do
55
+ Bonsai::Exporter.should_receive(:copy_assets)
56
+ end
57
+
58
+ it "should copy public" do
59
+ Bonsai::Exporter.should_receive(:copy_public)
60
+ end
61
+
62
+ it "should generate css via lesscss" do
63
+ Bonsai::Exporter.should_receive(:generate_css_from_less)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "publish!" do
69
+ describe "actions" do
70
+ before :all do
71
+ FileUtils.rm_rf Bonsai::Exporter.path
72
+ Bonsai::Exporter.publish!
73
+ end
74
+
75
+ it_should_behave_like "lesscss"
76
+
77
+ # Compressed CSS
78
+ it "should be processed with less" do
79
+ File.read("#{Bonsai::Exporter.path}/stylesheets/base.css").should == ".mymixin,#content{display:block;}"
80
+ end
81
+
82
+ it "should not export the base.less file" do
83
+ File.exists?("#{Bonsai::Exporter.path}/stylesheets/base.less").should be_false
84
+ end
85
+
86
+ it "should create the output directory" do
87
+ File.exists?(Bonsai::Exporter.path).should be_true
88
+ end
89
+
90
+ it "should render pages to the output directory" do
91
+ # Index is rendered to index.html and index/index.html
92
+ Dir[Bonsai::Exporter.path + "/**/*.html"].size.should == Bonsai::Page.all.size + 1
93
+ end
94
+
95
+ it "should copy the images of each page to its directory" do
96
+ File.exists?("#{Bonsai::Exporter.path}/about-us/history/images/image001.jpg").should be_true
97
+ end
98
+
99
+ it 'should copy the assets of each page to its directory' do
100
+ File.exists?("#{Bonsai::Exporter.path}/about-us/history/a_file_asset.txt").should be_true
101
+ end
102
+
103
+ it "should copy the contents of the public directory to the root export path" do
104
+ File.exists?("#{Bonsai::Exporter.path}/.htaccess").should be_true
105
+ end
106
+
107
+ it "should write the index file to output/index.html" do
108
+ File.exists?("#{Bonsai::Exporter.path}/index.html").should be_true
109
+ end
110
+
111
+ it "should write a readme file to explain how the site was generated"
112
+
113
+ describe "asset compression" do
114
+ it "should compress the css file" do
115
+ File.read("#{Bonsai::Exporter.path}/stylesheets/base.css").should == ".mymixin,#content{display:block;}"
116
+ end
117
+
118
+ it "should compress the js file" do
119
+ File.read("#{Bonsai::Exporter.path}/js/script.js").should == "$(function(){$(\".default-value\").each(function(){var a=this.value;$(this).focus(function(){if(this.value==a){this.value=\"\"}});$(this).blur(function(){if(this.value==\"\"){this.value=a}})});$(\".details dd:empty\").hide().prev(\"dt\").hide()});"
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "expectations" do
125
+ after do
126
+ Bonsai::Exporter.publish!
127
+ end
128
+
129
+ it "should remove the output directory before re-creating it" do
130
+ FileUtils.should_receive(:rm_rf).with(Bonsai::Exporter.path)
131
+ end
132
+
133
+ it "should generate css via lesscss" do
134
+ Bonsai::Exporter.should_receive(:generate_css_from_less)
135
+ end
136
+
137
+ it "should compress assets" do
138
+ Bonsai::Exporter.should_receive(:compress_assets)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,40 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+ describe Bonsai::Generate do
3
+ describe "generator" do
4
+ before :all do
5
+ @path = File.expand_path("spec/tmp-generator")
6
+ FileUtils.rm_rf @path
7
+ Bonsai::Generate.new(@path)
8
+ end
9
+
10
+ it "should create base directories" do
11
+ %w(content content/index templates public public/docs public/docs/css public/docs/js).each do |dir|
12
+ File.directory?("#{@path}/#{dir}").should be_true
13
+ end
14
+ end
15
+
16
+ it "should copy the htaccess file to public/.htaccess" do
17
+ File.exists?("#{@path}/public/.htaccess").should be_true
18
+ end
19
+
20
+ it "should copy the base.less file" do
21
+ File.exists?("#{@path}/public/docs/css/base.less").should be_true
22
+ end
23
+
24
+ it "should copy an index page" do
25
+ File.exists?("#{@path}/content/index/default.yml").should be_true
26
+ end
27
+
28
+ it "should copy a default template" do
29
+ File.exists?("#{@path}/templates/default.mustache").should be_true
30
+ end
31
+
32
+ it "should generate 4 files" do
33
+ Dir.glob("#{@path}/**/*", File::FNM_DOTMATCH).select{|f| File.file?(f) }.size.should == 4
34
+ end
35
+
36
+ it "should generate 7 directories" do
37
+ Dir.glob("#{@path}/**/*").select{|f| File.directory?(f) }.size.should == 7
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+
3
+ describe Bonsai::Navigation do
4
+ it "should respond to tree" do
5
+ Bonsai::Navigation.should respond_to :tree
6
+ end
7
+
8
+ before do
9
+ @tree = Bonsai::Navigation.tree
10
+ @about = Bonsai::Page.find("about-us")
11
+ @products = Bonsai::Page.find("products")
12
+ end
13
+
14
+ it "should contain 2 items" do
15
+ @tree.should be_an_instance_of(Array)
16
+ @tree.size.should == 3
17
+ end
18
+
19
+ it "should contain about and products" do
20
+ @tree.should include @about
21
+ @tree.should include @products
22
+ end
23
+
24
+ it "should be ordered" do
25
+ @tree.first.should == @about
26
+ @tree.last.should == @products
27
+ end
28
+ end
@@ -0,0 +1,225 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+
3
+ describe Bonsai::Page do
4
+ it "should respond to all" do
5
+ Bonsai::Page.should respond_to :all
6
+ end
7
+
8
+ it "should contain pages" do
9
+ Bonsai::Page.all.first.should be_an_instance_of(Bonsai::Page)
10
+ end
11
+
12
+ describe "instance" do
13
+ before :all do
14
+ @page = Bonsai::Page.find("about-us/history")
15
+ end
16
+
17
+ it "should have a slug" do
18
+ @page.slug.should == "history"
19
+ end
20
+
21
+ it "should have a name" do
22
+ @page.name.should == "History"
23
+ end
24
+
25
+ it "should have a permalink" do
26
+ @page.permalink.should == "/about-us/history"
27
+ end
28
+
29
+ it "should remove numbers over 10 from the permalink" do
30
+ Bonsai::Page.find("many-pages").permalink.should == "/many-pages"
31
+ end
32
+
33
+ it "should have a write_path" do
34
+ @page.write_path.should == "/about-us/history/index.html"
35
+ end
36
+
37
+ it "should respond to disk path" do
38
+ @page.disk_path.should == "#{Bonsai.root_dir}/content/1.about-us/history/demo-template.yml"
39
+ end
40
+
41
+ it "should have assets" do
42
+ @page.assets.should be_an_instance_of(Array)
43
+ @page.assets.length.should == 6
44
+ end
45
+
46
+ it "should be equal" do
47
+ Bonsai::Page.find("about-us").should == Bonsai::Page.find("about-us")
48
+ end
49
+
50
+ describe "relationships" do
51
+ before :all do
52
+ @index = Bonsai::Page.find("index")
53
+ @about = Bonsai::Page.find("about-us")
54
+ @history = Bonsai::Page.find("about-us/history")
55
+ @contact = Bonsai::Page.find("about-us/contact")
56
+ @child = Bonsai::Page.find("about-us/history/child")
57
+ end
58
+
59
+ it "should have siblings" do
60
+ @history.siblings.should be_an_instance_of(Array)
61
+ @history.siblings.size.should == 1
62
+ @history.siblings.should include(@contact)
63
+ @history.siblings.should_not include(@history)
64
+ end
65
+
66
+ it "should have a parent" do
67
+ @history.parent.should == @about
68
+ end
69
+
70
+ it "should not have a parent" do
71
+ @about.parent.should == nil
72
+ end
73
+
74
+ it "should not have a parent for index" do
75
+ @index.parent.should == nil
76
+ end
77
+
78
+ it "should have children" do
79
+ @about.children.should be_an_instance_of(Array)
80
+ @about.children.size.should == 1
81
+ @about.children.should include(@contact)
82
+ end
83
+
84
+ it "should not have floating pages in the children array" do
85
+ @about.children.should_not include(@history)
86
+ end
87
+
88
+ it "should have ancestors" do
89
+ @child.ancestors.should be_an_instance_of(Array)
90
+ @child.ancestors.size.should == 2
91
+ @child.ancestors.should include(@history)
92
+ @child.ancestors.should include(@about)
93
+ end
94
+
95
+ it "should have the ancestors in a top down order" do
96
+ @child.ancestors.first.should == @about
97
+ @child.ancestors.last.should == @history
98
+ end
99
+
100
+ it "index should be a floating page" do
101
+ @index.floating?.should be_true
102
+ end
103
+
104
+ it "about should not be a floating page" do
105
+ @about.floating?.should be_false
106
+ end
107
+ end
108
+
109
+ it "should have a template" do
110
+ @page.template.should be_an_instance_of(Bonsai::Template)
111
+ end
112
+
113
+ it "should to_hash to its variables" do
114
+ @page.content[:page_title].should == "About our history"
115
+ @page.content[:page_title].should_not be_nil
116
+ end
117
+
118
+ describe "render" do
119
+ before :all do
120
+ @output = Bonsai::Page.find("about-us/contact").render
121
+ end
122
+
123
+ it "should render" do
124
+ @output.should_not be_nil
125
+ end
126
+
127
+ it "should replace moustache variables with properties from the content file" do
128
+ @output.should == "Hello from our template, named Contact\n\nGet in touch\n\n/about-us/contact/images/image001.jpg\n/about-us/contact/child\n/about-us/contact/magic/image001.jpg\n/about-us/contact/magic/image002.jpg\nThis content should be inserted!\n\n<p>&ldquo;A designer knows he has achieved perfection\nnot when there is nothing left to add,\nbut when there is nothing left to take away.&rdquo;</p>\n\n<p>– Antoine de Saint-Exupery</p>\n"
129
+ end
130
+
131
+ it "should write in images" do
132
+ @output.should include "image001.jpg"
133
+ end
134
+
135
+ # Pages that use a structure yet have no parent page should still render
136
+ describe "page without parent" do
137
+ it "should render successfully" do
138
+ lambda { Bonsai::Page.find("legals/terms-and-conditions").render }.should_not raise_error
139
+ end
140
+ end
141
+
142
+ describe "markdown" do
143
+ it "should not use markdown for single line content" do
144
+ @output.should =~ /\nGet in touch\n/
145
+ end
146
+
147
+ it "should use markdown for multiple line content" do
148
+ @output.should =~ /<p>&ldquo;A designer knows he/
149
+ end
150
+
151
+ it "should use smartypants" do
152
+ @output.should =~ /&rdquo;/
153
+ end
154
+ end
155
+ end
156
+
157
+ describe "to hash" do
158
+ before :all do
159
+ @page = Bonsai::Page.find("about-us/history")
160
+ end
161
+
162
+ it "should respond to to_hash" do
163
+ @page.should respond_to(:to_hash)
164
+ end
165
+
166
+ %w(slug permalink name page_title children siblings parent ancestors magic navigation).each do |key|
167
+ it "should have a to_hash key for #{key}" do
168
+ @page.to_hash.keys.should include(key.to_sym)
169
+ end
170
+ end
171
+
172
+ describe "disk_assets" do
173
+ before :all do
174
+ @vars = @page.to_hash
175
+ end
176
+
177
+ describe "enum" do
178
+ it "should not have a child" do
179
+ @vars.should_not have_key(:child)
180
+ end
181
+
182
+ it "should have magic" do
183
+ @vars.should have_key(:magic)
184
+ end
185
+
186
+ it "it should be a an array of hashes" do
187
+ @vars[:magic].should be_an_instance_of(Array)
188
+ @vars[:magic].first.should be_an_instance_of(Hash)
189
+ @vars[:magic].size.should == 2
190
+ end
191
+ end
192
+
193
+ describe "boolean" do
194
+ it "should be false" do
195
+ pending
196
+ @vars[:child?].should be_false
197
+ end
198
+
199
+ it "should be true" do
200
+ pending
201
+ @vars[:magic?].should be_true
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ describe "broken page" do
208
+ before :all do
209
+ Bonsai::Page.path = "spec/support/broken/content"
210
+ end
211
+
212
+ after :all do
213
+ Bonsai::Page.path = "spec/support/content"
214
+ end
215
+
216
+ it "should exist" do
217
+ Bonsai::Page.find("page").should be_an_instance_of(Bonsai::Page)
218
+ end
219
+
220
+ it "should error gracefully" do
221
+ lambda { Bonsai::Page.find("page").render }.should_not raise_error(ArgumentError)
222
+ end
223
+ end
224
+ end
225
+ end