monad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/CONTRIBUTING.md +68 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +21 -0
  4. data/README.md +88 -0
  5. data/Rakefile +136 -0
  6. data/bin/monad +102 -0
  7. data/cucumber.yml +3 -0
  8. data/features/create_sites.feature +112 -0
  9. data/features/data_sources.feature +76 -0
  10. data/features/drafts.feature +25 -0
  11. data/features/embed_filters.feature +60 -0
  12. data/features/markdown.feature +30 -0
  13. data/features/pagination.feature +54 -0
  14. data/features/permalinks.feature +65 -0
  15. data/features/post_data.feature +214 -0
  16. data/features/site_configuration.feature +206 -0
  17. data/features/site_data.feature +101 -0
  18. data/features/step_definitions/monad_steps.rb +175 -0
  19. data/features/support/env.rb +25 -0
  20. data/lib/monad.rb +90 -0
  21. data/lib/monad/command.rb +27 -0
  22. data/lib/monad/commands/build.rb +64 -0
  23. data/lib/monad/commands/doctor.rb +29 -0
  24. data/lib/monad/commands/new.rb +50 -0
  25. data/lib/monad/commands/serve.rb +33 -0
  26. data/lib/monad/configuration.rb +183 -0
  27. data/lib/monad/converter.rb +48 -0
  28. data/lib/monad/converters/identity.rb +21 -0
  29. data/lib/monad/converters/markdown.rb +43 -0
  30. data/lib/monad/converters/markdown/kramdown_parser.rb +44 -0
  31. data/lib/monad/converters/markdown/maruku_parser.rb +47 -0
  32. data/lib/monad/converters/markdown/rdiscount_parser.rb +35 -0
  33. data/lib/monad/converters/markdown/redcarpet_parser.rb +70 -0
  34. data/lib/monad/converters/textile.rb +50 -0
  35. data/lib/monad/convertible.rb +152 -0
  36. data/lib/monad/core_ext.rb +68 -0
  37. data/lib/monad/deprecator.rb +32 -0
  38. data/lib/monad/draft.rb +35 -0
  39. data/lib/monad/drivers/json_driver.rb +39 -0
  40. data/lib/monad/drivers/yaml_driver.rb +23 -0
  41. data/lib/monad/errors.rb +4 -0
  42. data/lib/monad/filters.rb +154 -0
  43. data/lib/monad/generator.rb +4 -0
  44. data/lib/monad/generators/pagination.rb +143 -0
  45. data/lib/monad/layout.rb +42 -0
  46. data/lib/monad/logger.rb +54 -0
  47. data/lib/monad/mime.types +85 -0
  48. data/lib/monad/page.rb +163 -0
  49. data/lib/monad/plugin.rb +75 -0
  50. data/lib/monad/post.rb +377 -0
  51. data/lib/monad/site.rb +455 -0
  52. data/lib/monad/static_file.rb +70 -0
  53. data/lib/monad/tags/gist.rb +30 -0
  54. data/lib/monad/tags/highlight.rb +85 -0
  55. data/lib/monad/tags/include.rb +37 -0
  56. data/lib/monad/tags/post_url.rb +61 -0
  57. data/lib/site_template/.gitignore +1 -0
  58. data/lib/site_template/_config.yml +2 -0
  59. data/lib/site_template/_layouts/default.html +46 -0
  60. data/lib/site_template/_layouts/post.html +9 -0
  61. data/lib/site_template/_posts/0000-00-00-welcome-to-monad.markdown.erb +24 -0
  62. data/lib/site_template/css/main.css +165 -0
  63. data/lib/site_template/css/syntax.css +60 -0
  64. data/lib/site_template/index.html +13 -0
  65. data/monad.gemspec +197 -0
  66. data/script/bootstrap +2 -0
  67. data/test/fixtures/broken_front_matter1.erb +5 -0
  68. data/test/fixtures/broken_front_matter2.erb +4 -0
  69. data/test/fixtures/broken_front_matter3.erb +7 -0
  70. data/test/fixtures/exploit_front_matter.erb +4 -0
  71. data/test/fixtures/front_matter.erb +4 -0
  72. data/test/fixtures/members.yaml +7 -0
  73. data/test/helper.rb +62 -0
  74. data/test/source/.htaccess +8 -0
  75. data/test/source/_includes/sig.markdown +3 -0
  76. data/test/source/_layouts/default.html +27 -0
  77. data/test/source/_layouts/simple.html +1 -0
  78. data/test/source/_plugins/dummy.rb +8 -0
  79. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  80. data/test/source/_posts/2008-02-02-published.textile +8 -0
  81. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  82. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  83. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  84. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  85. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  86. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  87. data/test/source/_posts/2009-01-27-category.textile +7 -0
  88. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  89. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  90. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  91. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  92. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  93. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  94. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  95. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  96. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  97. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  98. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  99. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  100. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  101. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  102. data/test/source/_posts/2011-04-12-md-extension.md +7 -0
  103. data/test/source/_posts/2011-04-12-text-extension.text +0 -0
  104. data/test/source/_posts/2013-01-02-post-excerpt.markdown +14 -0
  105. data/test/source/_posts/2013-01-12-nil-layout.textile +6 -0
  106. data/test/source/_posts/2013-01-12-no-layout.textile +5 -0
  107. data/test/source/_posts/2013-03-19-not-a-post.markdown/.gitkeep +0 -0
  108. data/test/source/_posts/2013-04-11-custom-excerpt.markdown +10 -0
  109. data/test/source/_posts/2013-05-10-number-category.textile +7 -0
  110. data/test/source/_posts/es/2008-11-21-nested.textile +8 -0
  111. data/test/source/about.html +6 -0
  112. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  113. data/test/source/contacts.html +5 -0
  114. data/test/source/contacts/bar.html +5 -0
  115. data/test/source/contacts/index.html +5 -0
  116. data/test/source/css/screen.css +76 -0
  117. data/test/source/deal.with.dots.html +7 -0
  118. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  119. data/test/source/index.html +22 -0
  120. data/test/source/sitemap.xml +32 -0
  121. data/test/source/symlink-test/symlinked-file +22 -0
  122. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  123. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  124. data/test/suite.rb +11 -0
  125. data/test/test_command.rb +39 -0
  126. data/test/test_configuration.rb +137 -0
  127. data/test/test_convertible.rb +51 -0
  128. data/test/test_core_ext.rb +88 -0
  129. data/test/test_filters.rb +102 -0
  130. data/test/test_generated_site.rb +83 -0
  131. data/test/test_json_driver.rb +63 -0
  132. data/test/test_kramdown.rb +35 -0
  133. data/test/test_new_command.rb +104 -0
  134. data/test/test_page.rb +193 -0
  135. data/test/test_pager.rb +115 -0
  136. data/test/test_post.rb +573 -0
  137. data/test/test_rdiscount.rb +22 -0
  138. data/test/test_redcarpet.rb +61 -0
  139. data/test/test_redcloth.rb +86 -0
  140. data/test/test_site.rb +374 -0
  141. data/test/test_tags.rb +310 -0
  142. data/test/test_yaml_driver.rb +35 -0
  143. metadata +554 -0
@@ -0,0 +1,22 @@
1
+ require 'helper'
2
+
3
+ class TestRdiscount < Test::Unit::TestCase
4
+
5
+ context "rdiscount" do
6
+ setup do
7
+ config = {
8
+ 'markdown' => 'rdiscount',
9
+ 'rdiscount' => { 'extensions' => ['smart', 'generate_toc'], 'toc_token' => '{:toc}' }
10
+ }
11
+ @markdown = Converters::Markdown.new config
12
+ end
13
+
14
+ should "pass rdiscount extensions" do
15
+ assert_equal "<p>&ldquo;smart&rdquo;</p>", @markdown.convert('"smart"').strip
16
+ end
17
+
18
+ should "render toc" do
19
+ assert_equal "<h1 id=\"Header+1\">Header 1</h1>\n\n<h2 id=\"Header+2\">Header 2</h2>\n\n<p>\n <ul>\n <li><a href=\"#Header+1\">Header 1</a>\n <ul>\n <li><a href=\"#Header+2\">Header 2</a> </li>\n </ul>\n </li>\n </ul>\n\n</p>", @markdown.convert("# Header 1\n\n## Header 2\n\n{:toc}").strip
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ require 'helper'
2
+
3
+ class TestRedcarpet < Test::Unit::TestCase
4
+ context "redcarpet" do
5
+ setup do
6
+ @config = {
7
+ 'redcarpet' => { 'extensions' => ['smart', 'strikethrough', 'filter_html'] },
8
+ 'markdown' => 'redcarpet'
9
+ }
10
+ @markdown = Converters::Markdown.new @config
11
+ end
12
+
13
+ should "pass redcarpet options" do
14
+ assert_equal "<h1>Some Header</h1>", @markdown.convert('# Some Header #').strip
15
+ end
16
+
17
+ should "pass redcarpet SmartyPants options" do
18
+ assert_equal "<p>&ldquo;smart&rdquo;</p>", @markdown.convert('"smart"').strip
19
+ end
20
+
21
+ should "pass redcarpet extensions" do
22
+ assert_equal "<p><del>deleted</del></p>", @markdown.convert('~~deleted~~').strip
23
+ end
24
+
25
+ should "pass redcarpet render options" do
26
+ assert_equal "<p><strong>bad code not here</strong>: i am bad</p>", @markdown.convert('**bad code not here**: <script>i am bad</script>').strip
27
+ end
28
+
29
+ context "with pygments enabled" do
30
+ setup do
31
+ @markdown = Converters::Markdown.new @config.merge({ 'pygments' => true })
32
+ end
33
+
34
+ should "render fenced code blocks with syntax highlighting" do
35
+ assert_equal "<div class=\"highlight\"><pre><code class=\"ruby language-ruby\" data-lang=\"ruby\"><span class=\"nb\">puts</span> <span class=\"s2\">&quot;Hello world&quot;</span>\n</code></pre></div>", @markdown.convert(
36
+ <<-EOS
37
+ ```ruby
38
+ puts "Hello world"
39
+ ```
40
+ EOS
41
+ ).strip
42
+ end
43
+ end
44
+
45
+ context "with pygments disabled" do
46
+ setup do
47
+ @markdown = Converters::Markdown.new @config.merge({ 'pygments' => false })
48
+ end
49
+
50
+ should "render fenced code blocks without syntax highlighting" do
51
+ assert_equal "<div class=\"highlight\"><pre><code class=\"ruby language-ruby\" data-lang=\"ruby\">puts &quot;Hello world&quot;\n</code></pre></div>", @markdown.convert(
52
+ <<-EOS
53
+ ```ruby
54
+ puts "Hello world"
55
+ ```
56
+ EOS
57
+ ).strip
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestRedCloth < Test::Unit::TestCase
4
+
5
+ context "RedCloth default (no explicit config) hard_breaks enabled" do
6
+ setup do
7
+ @textile = Converters::Textile.new
8
+ end
9
+
10
+ should "preserve single line breaks in HTML output" do
11
+ assert_equal "<p>line1<br />\nline2</p>", @textile.convert("p. line1\nline2").strip
12
+ end
13
+ end
14
+
15
+ context "Default hard_breaks enabled w/ redcloth section, no hard_breaks value" do
16
+ setup do
17
+ config = {
18
+ 'redcloth' => {}
19
+ }
20
+ @textile = Converters::Textile.new config
21
+ end
22
+
23
+ should "preserve single line breaks in HTML output" do
24
+ assert_equal "<p>line1<br />\nline2</p>", @textile.convert("p. line1\nline2").strip
25
+ end
26
+ end
27
+
28
+ context "RedCloth with hard_breaks enabled" do
29
+ setup do
30
+ config = {
31
+ 'redcloth' => {
32
+ 'hard_breaks' => true # default
33
+ }
34
+ }
35
+ @textile = Converters::Textile.new config
36
+ end
37
+
38
+ should "preserve single line breaks in HTML output" do
39
+ assert_equal "<p>line1<br />\nline2</p>", @textile.convert("p. line1\nline2").strip
40
+ end
41
+ end
42
+
43
+ context "RedCloth with hard_breaks disabled" do
44
+ setup do
45
+ config = {
46
+ 'redcloth' => {
47
+ 'hard_breaks' => false
48
+ }
49
+ }
50
+ @textile = Converters::Textile.new config
51
+ end
52
+
53
+ should "not generate break tags in HTML output" do
54
+ assert_equal "<p>line1\nline2</p>", @textile.convert("p. line1\nline2").strip
55
+ end
56
+ end
57
+
58
+ context "RedCloth w/no_span_caps set to false" do
59
+ setup do
60
+ config = {
61
+ 'redcloth' => {
62
+ 'no_span_caps' => false
63
+ }
64
+ }
65
+ @textile = Converters::Textile.new config
66
+ end
67
+ should "generate span tags around capitalized words" do
68
+ assert_equal "<p><span class=\"caps\">NSC</span></p>", @textile.convert("NSC").strip
69
+ end
70
+ end
71
+
72
+ context "RedCloth w/no_span_caps set to true" do
73
+ setup do
74
+ config = {
75
+ 'redcloth' => {
76
+ 'no_span_caps' => true
77
+ }
78
+ }
79
+ @textile = Converters::Textile.new config
80
+ end
81
+
82
+ should "not generate span tags around capitalized words" do
83
+ assert_equal "<p>NSC</p>", @textile.convert("NSC").strip
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,374 @@
1
+ require 'helper'
2
+
3
+ class TestSite < Test::Unit::TestCase
4
+ context "configuring sites" do
5
+ should "have an array for plugins by default" do
6
+ site = Site.new(Monad::Configuration::DEFAULTS)
7
+ assert_equal [File.join(Dir.pwd, '_plugins')], site.plugins
8
+ end
9
+
10
+ should "look for plugins under the site directory by default" do
11
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'source' => File.expand_path(source_dir)}))
12
+ assert_equal [File.join(source_dir, '_plugins')], site.plugins
13
+ end
14
+
15
+ should "have an array for plugins if passed as a string" do
16
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'plugins' => '/tmp/plugins'}))
17
+ assert_equal ['/tmp/plugins'], site.plugins
18
+ end
19
+
20
+ should "have an array for plugins if passed as an array" do
21
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'plugins' => ['/tmp/plugins', '/tmp/otherplugins']}))
22
+ assert_equal ['/tmp/plugins', '/tmp/otherplugins'], site.plugins
23
+ end
24
+
25
+ should "have an empty array for plugins if nothing is passed" do
26
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'plugins' => []}))
27
+ assert_equal [], site.plugins
28
+ end
29
+
30
+ should "have an empty array for plugins if nil is passed" do
31
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'plugins' => nil}))
32
+ assert_equal [], site.plugins
33
+ end
34
+
35
+ should "expose default baseurl" do
36
+ site = Site.new(Monad::Configuration::DEFAULTS)
37
+ assert_equal Monad::Configuration::DEFAULTS['baseurl'], site.baseurl
38
+ end
39
+
40
+ should "expose baseurl passed in from config" do
41
+ site = Site.new(Monad::Configuration::DEFAULTS.merge({'baseurl' => '/blog'}))
42
+ assert_equal '/blog', site.baseurl
43
+ end
44
+ end
45
+ context "creating sites" do
46
+ setup do
47
+ stub(Monad).configuration do
48
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
49
+ end
50
+ @site = Site.new(Monad.configuration)
51
+ @num_invalid_posts = 2
52
+ end
53
+
54
+ should "have an empty tag hash by default" do
55
+ assert_equal Hash.new, @site.tags
56
+ end
57
+
58
+ should "give site with parsed pages and posts to generators" do
59
+ @site.reset
60
+ @site.read
61
+ class MyGenerator < Generator
62
+ def generate(site)
63
+ site.pages.dup.each do |page|
64
+ raise "#{page} isn't a page" unless page.is_a?(Page)
65
+ raise "#{page} doesn't respond to :name" unless page.respond_to?(:name)
66
+ end
67
+ end
68
+ end
69
+ @site.generate
70
+ assert_not_equal 0, @site.pages.size
71
+ end
72
+
73
+ should "reset data before processing" do
74
+ clear_dest
75
+ @site.process
76
+ before_posts = @site.posts.length
77
+ before_layouts = @site.layouts.length
78
+ before_categories = @site.categories.length
79
+ before_tags = @site.tags.length
80
+ before_pages = @site.pages.length
81
+ before_static_files = @site.static_files.length
82
+ before_time = @site.time
83
+
84
+ @site.process
85
+ assert_equal before_posts, @site.posts.length
86
+ assert_equal before_layouts, @site.layouts.length
87
+ assert_equal before_categories, @site.categories.length
88
+ assert_equal before_tags, @site.tags.length
89
+ assert_equal before_pages, @site.pages.length
90
+ assert_equal before_static_files, @site.static_files.length
91
+ assert before_time <= @site.time
92
+ end
93
+
94
+ should "write only modified static files" do
95
+ clear_dest
96
+ StaticFile.reset_cache
97
+
98
+ @site.process
99
+ some_static_file = @site.static_files[0].path
100
+ dest = File.expand_path(@site.static_files[0].destination(@site.dest))
101
+ mtime1 = File.stat(dest).mtime.to_i # first run must generate dest file
102
+
103
+ # need to sleep because filesystem timestamps have best resolution in seconds
104
+ sleep 1
105
+ @site.process
106
+ mtime2 = File.stat(dest).mtime.to_i
107
+ assert_equal mtime1, mtime2
108
+
109
+ # simulate file modification by user
110
+ FileUtils.touch some_static_file
111
+
112
+ sleep 1
113
+ @site.process
114
+ mtime3 = File.stat(dest).mtime.to_i
115
+ assert_not_equal mtime2, mtime3 # must be regenerated!
116
+
117
+ sleep 1
118
+ @site.process
119
+ mtime4 = File.stat(dest).mtime.to_i
120
+ assert_equal mtime3, mtime4 # no modifications, so must be the same
121
+ end
122
+
123
+ should "write static files if not modified but missing in destination" do
124
+ clear_dest
125
+ StaticFile.reset_cache
126
+
127
+ @site.process
128
+ some_static_file = @site.static_files[0].path
129
+ dest = File.expand_path(@site.static_files[0].destination(@site.dest))
130
+ mtime1 = File.stat(dest).mtime.to_i # first run must generate dest file
131
+
132
+ # need to sleep because filesystem timestamps have best resolution in seconds
133
+ sleep 1
134
+ @site.process
135
+ mtime2 = File.stat(dest).mtime.to_i
136
+ assert_equal mtime1, mtime2
137
+
138
+ # simulate destination file deletion
139
+ File.unlink dest
140
+
141
+ sleep 1
142
+ @site.process
143
+ mtime3 = File.stat(dest).mtime.to_i
144
+ assert_not_equal mtime2, mtime3 # must be regenerated and differ!
145
+
146
+ sleep 1
147
+ @site.process
148
+ mtime4 = File.stat(dest).mtime.to_i
149
+ assert_equal mtime3, mtime4 # no modifications, so must be the same
150
+ end
151
+
152
+ should "setup plugins in priority order" do
153
+ assert_equal @site.converters.sort_by(&:class).map{|c|c.class.priority}, @site.converters.map{|c|c.class.priority}
154
+ assert_equal @site.generators.sort_by(&:class).map{|g|g.class.priority}, @site.generators.map{|g|g.class.priority}
155
+ end
156
+
157
+ should "read layouts" do
158
+ @site.read_layouts
159
+ assert_equal ["default", "simple"].sort, @site.layouts.keys.sort
160
+ end
161
+
162
+ should "read posts" do
163
+ @site.read_posts('')
164
+ posts = Dir[source_dir('_posts', '**', '*')]
165
+ posts.delete_if { |post| File.directory?(post) && !Post.valid?(post) }
166
+ assert_equal posts.size - @num_invalid_posts, @site.posts.size
167
+ end
168
+
169
+ should "deploy payload" do
170
+ clear_dest
171
+ @site.process
172
+
173
+ posts = Dir[source_dir("**", "_posts", "**", "*")]
174
+ posts.delete_if { |post| File.directory?(post) && !Post.valid?(post) }
175
+ categories = %w(2013 bar baz category foo z_category publish_test win).sort
176
+
177
+ assert_equal posts.size - @num_invalid_posts, @site.posts.size
178
+ assert_equal categories, @site.categories.keys.sort
179
+ assert_equal 4, @site.categories['foo'].size
180
+ end
181
+
182
+ should "filter entries" do
183
+ ent1 = %w[foo.markdown bar.markdown baz.markdown #baz.markdown#
184
+ .baz.markdow foo.markdown~]
185
+ ent2 = %w[.htaccess _posts _pages bla.bla]
186
+
187
+ assert_equal %w[foo.markdown bar.markdown baz.markdown], @site.filter_entries(ent1)
188
+ assert_equal %w[.htaccess bla.bla], @site.filter_entries(ent2)
189
+ end
190
+
191
+ should "filter entries with exclude" do
192
+ excludes = %w[README TODO]
193
+ files = %w[index.html site.css .htaccess]
194
+
195
+ @site.exclude = excludes + ["exclude*"]
196
+ assert_equal files, @site.filter_entries(excludes + files + ["excludeA"])
197
+ end
198
+
199
+ should "not filter entries within include" do
200
+ includes = %w[_index.html .htaccess include*]
201
+ files = %w[index.html _index.html .htaccess includeA]
202
+
203
+ @site.include = includes
204
+ assert_equal files, @site.filter_entries(files)
205
+ end
206
+
207
+ should "filter symlink entries when safe mode enabled" do
208
+ stub(Monad).configuration do
209
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => true})
210
+ end
211
+ site = Site.new(Monad.configuration)
212
+ stub(File).symlink?('symlink.js') {true}
213
+ files = %w[symlink.js]
214
+ assert_equal [], site.filter_entries(files)
215
+ end
216
+
217
+ should "not filter symlink entries when safe mode disabled" do
218
+ stub(File).symlink?('symlink.js') {true}
219
+ files = %w[symlink.js]
220
+ assert_equal files, @site.filter_entries(files)
221
+ end
222
+
223
+ should "not include symlinks in safe mode" do
224
+ stub(Monad).configuration do
225
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => true})
226
+ end
227
+ site = Site.new(Monad.configuration)
228
+
229
+ site.read_directories("symlink-test")
230
+ assert_equal [], site.pages
231
+ assert_equal [], site.static_files
232
+ end
233
+
234
+ should "include symlinks in unsafe mode" do
235
+ stub(Monad).configuration do
236
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => false})
237
+ end
238
+ site = Site.new(Monad.configuration)
239
+
240
+ site.read_directories("symlink-test")
241
+ assert_not_equal [], site.pages
242
+ assert_not_equal [], site.static_files
243
+ end
244
+
245
+ context 'error handling' do
246
+ should "raise if destination is included in source" do
247
+ stub(Monad).configuration do
248
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => source_dir})
249
+ end
250
+
251
+ assert_raise Monad::FatalException do
252
+ site = Site.new(Monad.configuration)
253
+ end
254
+ end
255
+
256
+ should "raise if destination is source" do
257
+ stub(Monad).configuration do
258
+ Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => File.join(source_dir, "..")})
259
+ end
260
+
261
+ assert_raise Monad::FatalException do
262
+ site = Site.new(Monad.configuration)
263
+ end
264
+ end
265
+ end
266
+
267
+ context 'with orphaned files in destination' do
268
+ setup do
269
+ clear_dest
270
+ @site.process
271
+ # generate some orphaned files:
272
+ # single file
273
+ File.open(dest_dir('obsolete.html'), 'w')
274
+ # single file in sub directory
275
+ FileUtils.mkdir(dest_dir('qux'))
276
+ File.open(dest_dir('qux/obsolete.html'), 'w')
277
+ # empty directory
278
+ FileUtils.mkdir(dest_dir('quux'))
279
+ FileUtils.mkdir(dest_dir('.git'))
280
+ FileUtils.mkdir(dest_dir('.svn'))
281
+ FileUtils.mkdir(dest_dir('.hg'))
282
+ # single file in repository
283
+ File.open(dest_dir('.git/HEAD'), 'w')
284
+ File.open(dest_dir('.svn/HEAD'), 'w')
285
+ File.open(dest_dir('.hg/HEAD'), 'w')
286
+ end
287
+
288
+ teardown do
289
+ FileUtils.rm_f(dest_dir('obsolete.html'))
290
+ FileUtils.rm_rf(dest_dir('qux'))
291
+ FileUtils.rm_f(dest_dir('quux'))
292
+ FileUtils.rm_rf(dest_dir('.git'))
293
+ FileUtils.rm_rf(dest_dir('.svn'))
294
+ FileUtils.rm_rf(dest_dir('.hg'))
295
+ end
296
+
297
+ should 'remove orphaned files in destination' do
298
+ @site.process
299
+ assert !File.exist?(dest_dir('obsolete.html'))
300
+ assert !File.exist?(dest_dir('qux'))
301
+ assert !File.exist?(dest_dir('quux'))
302
+ assert File.exist?(dest_dir('.git'))
303
+ assert File.exist?(dest_dir('.git/HEAD'))
304
+ end
305
+
306
+ should 'remove orphaned files in destination - keep_files .svn' do
307
+ config = Monad::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'keep_files' => ['.svn']})
308
+ @site = Site.new(config)
309
+ @site.process
310
+ assert !File.exist?(dest_dir('.htpasswd'))
311
+ assert !File.exist?(dest_dir('obsolete.html'))
312
+ assert !File.exist?(dest_dir('qux'))
313
+ assert !File.exist?(dest_dir('quux'))
314
+ assert !File.exist?(dest_dir('.git'))
315
+ assert !File.exist?(dest_dir('.git/HEAD'))
316
+ assert File.exist?(dest_dir('.svn'))
317
+ assert File.exist?(dest_dir('.svn/HEAD'))
318
+ end
319
+ end
320
+
321
+ context 'with an invalid markdown processor in the configuration' do
322
+ should 'not throw an error at initialization time' do
323
+ bad_processor = 'not a processor name'
324
+ assert_nothing_raised do
325
+ Site.new(Monad.configuration.merge({ 'markdown' => bad_processor }))
326
+ end
327
+ end
328
+
329
+ should 'throw FatalException at process time' do
330
+ bad_processor = 'not a processor name'
331
+ s = Site.new(Monad.configuration.merge({ 'markdown' => bad_processor }))
332
+ assert_raise Monad::FatalException do
333
+ s.process
334
+ end
335
+ end
336
+ end
337
+
338
+ context 'data sources' do
339
+ should 'not throw exception if data sources is nil' do
340
+ assert_nothing_raised do
341
+ Site.new(Monad.configuration.merge({ 'data_sources' => nil }))
342
+ end
343
+ end
344
+
345
+ should 'not throw exception if data sources is empty' do
346
+ assert_nothing_raised do
347
+ Site.new(Monad.configuration.merge({ 'data_sources' => [] }))
348
+ end
349
+ end
350
+
351
+ should 'load one data source' do
352
+ site = Site.new(Monad.configuration.merge({ 'data_sources' => [{'name' => 'jsonip', 'type' => 'json', 'url' => 'http://jsonip.com/'}] }))
353
+ site.process
354
+
355
+ assert_not_nil site.data_sources['jsonip']['ip']
356
+ assert_not_nil site.data_sources['jsonip']['about']
357
+ end
358
+
359
+ should 'load multiple data sources' do
360
+ jsonip = {'name' => 'jsonip', 'type' => 'json', 'url' => 'http://jsonip.com/'}
361
+
362
+ base = File.expand_path('../fixtures', __FILE__)
363
+ members = {'name' => 'members', 'type' => 'yaml', 'path' => File.join(base, 'members.yaml')}
364
+
365
+ site = Site.new(Monad.configuration.merge({ 'data_sources' => [jsonip, members] }))
366
+ site.process
367
+
368
+ assert_not_nil site.data_sources['jsonip']['ip']
369
+ assert_not_nil site.data_sources['jsonip']['about']
370
+ assert_equal site.data_sources['members'].size, 2
371
+ end
372
+ end
373
+ end
374
+ end