monad 0.0.1

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 (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