bunto 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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.markdown +59 -0
  4. data/bin/bunto +51 -0
  5. data/lib/bunto.rb +179 -0
  6. data/lib/bunto/cleaner.rb +105 -0
  7. data/lib/bunto/collection.rb +205 -0
  8. data/lib/bunto/command.rb +65 -0
  9. data/lib/bunto/commands/build.rb +77 -0
  10. data/lib/bunto/commands/clean.rb +42 -0
  11. data/lib/bunto/commands/doctor.rb +114 -0
  12. data/lib/bunto/commands/help.rb +31 -0
  13. data/lib/bunto/commands/new.rb +82 -0
  14. data/lib/bunto/commands/serve.rb +204 -0
  15. data/lib/bunto/commands/serve/servlet.rb +61 -0
  16. data/lib/bunto/configuration.rb +323 -0
  17. data/lib/bunto/converter.rb +48 -0
  18. data/lib/bunto/converters/identity.rb +21 -0
  19. data/lib/bunto/converters/markdown.rb +92 -0
  20. data/lib/bunto/converters/markdown/kramdown_parser.rb +117 -0
  21. data/lib/bunto/converters/markdown/rdiscount_parser.rb +33 -0
  22. data/lib/bunto/converters/markdown/redcarpet_parser.rb +102 -0
  23. data/lib/bunto/converters/smartypants.rb +34 -0
  24. data/lib/bunto/convertible.rb +297 -0
  25. data/lib/bunto/deprecator.rb +46 -0
  26. data/lib/bunto/document.rb +444 -0
  27. data/lib/bunto/drops/bunto_drop.rb +21 -0
  28. data/lib/bunto/drops/collection_drop.rb +22 -0
  29. data/lib/bunto/drops/document_drop.rb +27 -0
  30. data/lib/bunto/drops/drop.rb +176 -0
  31. data/lib/bunto/drops/site_drop.rb +38 -0
  32. data/lib/bunto/drops/unified_payload_drop.rb +25 -0
  33. data/lib/bunto/drops/url_drop.rb +83 -0
  34. data/lib/bunto/entry_filter.rb +72 -0
  35. data/lib/bunto/errors.rb +10 -0
  36. data/lib/bunto/excerpt.rb +127 -0
  37. data/lib/bunto/external.rb +59 -0
  38. data/lib/bunto/filters.rb +367 -0
  39. data/lib/bunto/frontmatter_defaults.rb +188 -0
  40. data/lib/bunto/generator.rb +3 -0
  41. data/lib/bunto/hooks.rb +101 -0
  42. data/lib/bunto/layout.rb +49 -0
  43. data/lib/bunto/liquid_extensions.rb +22 -0
  44. data/lib/bunto/liquid_renderer.rb +39 -0
  45. data/lib/bunto/liquid_renderer/file.rb +50 -0
  46. data/lib/bunto/liquid_renderer/table.rb +94 -0
  47. data/lib/bunto/log_adapter.rb +115 -0
  48. data/lib/bunto/mime.types +800 -0
  49. data/lib/bunto/page.rb +180 -0
  50. data/lib/bunto/plugin.rb +96 -0
  51. data/lib/bunto/plugin_manager.rb +95 -0
  52. data/lib/bunto/post.rb +329 -0
  53. data/lib/bunto/publisher.rb +21 -0
  54. data/lib/bunto/reader.rb +126 -0
  55. data/lib/bunto/readers/collection_reader.rb +20 -0
  56. data/lib/bunto/readers/data_reader.rb +69 -0
  57. data/lib/bunto/readers/layout_reader.rb +53 -0
  58. data/lib/bunto/readers/page_reader.rb +21 -0
  59. data/lib/bunto/readers/post_reader.rb +62 -0
  60. data/lib/bunto/readers/static_file_reader.rb +21 -0
  61. data/lib/bunto/regenerator.rb +175 -0
  62. data/lib/bunto/related_posts.rb +56 -0
  63. data/lib/bunto/renderer.rb +191 -0
  64. data/lib/bunto/site.rb +391 -0
  65. data/lib/bunto/static_file.rb +141 -0
  66. data/lib/bunto/stevenson.rb +58 -0
  67. data/lib/bunto/tags/highlight.rb +122 -0
  68. data/lib/bunto/tags/include.rb +190 -0
  69. data/lib/bunto/tags/post_url.rb +88 -0
  70. data/lib/bunto/url.rb +136 -0
  71. data/lib/bunto/utils.rb +287 -0
  72. data/lib/bunto/utils/ansi.rb +59 -0
  73. data/lib/bunto/utils/platforms.rb +30 -0
  74. data/lib/bunto/version.rb +3 -0
  75. data/lib/site_template/.gitignore +3 -0
  76. data/lib/site_template/_config.yml +21 -0
  77. data/lib/site_template/_includes/footer.html +38 -0
  78. data/lib/site_template/_includes/head.html +12 -0
  79. data/lib/site_template/_includes/header.html +27 -0
  80. data/lib/site_template/_includes/icon-github.html +1 -0
  81. data/lib/site_template/_includes/icon-github.svg +1 -0
  82. data/lib/site_template/_includes/icon-twitter.html +1 -0
  83. data/lib/site_template/_includes/icon-twitter.svg +1 -0
  84. data/lib/site_template/_layouts/default.html +20 -0
  85. data/lib/site_template/_layouts/page.html +14 -0
  86. data/lib/site_template/_layouts/post.html +15 -0
  87. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +25 -0
  88. data/lib/site_template/_sass/_base.scss +206 -0
  89. data/lib/site_template/_sass/_layout.scss +242 -0
  90. data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
  91. data/lib/site_template/about.md +15 -0
  92. data/lib/site_template/css/main.scss +53 -0
  93. data/lib/site_template/feed.xml +30 -0
  94. data/lib/site_template/index.html +23 -0
  95. metadata +252 -0
@@ -0,0 +1,10 @@
1
+ module Bunto
2
+ module Errors
3
+ FatalException = Class.new(::RuntimeError)
4
+
5
+ DropMutationException = Class.new(FatalException)
6
+ InvalidPermalinkError = Class.new(FatalException)
7
+ InvalidYAMLFrontMatterError = Class.new(FatalException)
8
+ MissingDependencyException = Class.new(FatalException)
9
+ end
10
+ end
@@ -0,0 +1,127 @@
1
+ module Bunto
2
+ class Excerpt
3
+ extend Forwardable
4
+
5
+ attr_accessor :doc
6
+ attr_accessor :content, :ext
7
+ attr_writer :output
8
+
9
+ def_delegators :@doc, :site, :name, :ext, :relative_path, :extname,
10
+ :render_with_liquid?, :collection, :related_posts
11
+
12
+ # Initialize this Excerpt instance.
13
+ #
14
+ # doc - The Document.
15
+ #
16
+ # Returns the new Excerpt.
17
+ def initialize(doc)
18
+ self.doc = doc
19
+ self.content = extract_excerpt(doc.content)
20
+ end
21
+
22
+ # Fetch YAML front-matter data from related doc, without layout key
23
+ #
24
+ # Returns Hash of doc data
25
+ def data
26
+ @data ||= doc.data.dup
27
+ @data.delete("layout")
28
+ @data.delete("excerpt")
29
+ @data
30
+ end
31
+
32
+ def trigger_hooks(*)
33
+ end
34
+
35
+ # 'Path' of the excerpt.
36
+ #
37
+ # Returns the path for the doc this excerpt belongs to with #excerpt appended
38
+ def path
39
+ File.join(doc.path, "#excerpt")
40
+ end
41
+
42
+ # Check if excerpt includes a string
43
+ #
44
+ # Returns true if the string passed in
45
+ def include?(something)
46
+ (output && output.include?(something)) || content.include?(something)
47
+ end
48
+
49
+ # The UID for this doc (useful in feeds).
50
+ # e.g. /2008/11/05/my-awesome-doc
51
+ #
52
+ # Returns the String UID.
53
+ def id
54
+ "#{doc.id}#excerpt"
55
+ end
56
+
57
+ def to_s
58
+ output || content
59
+ end
60
+
61
+ def to_liquid
62
+ doc.data['excerpt'] = nil
63
+ @to_liquid ||= doc.to_liquid
64
+ doc.data['excerpt'] = self
65
+ @to_liquid
66
+ end
67
+
68
+ # Returns the shorthand String identifier of this doc.
69
+ def inspect
70
+ "<Excerpt: #{self.id}>"
71
+ end
72
+
73
+ def output
74
+ @output ||= Renderer.new(doc.site, self, site.site_payload).run
75
+ end
76
+
77
+ def place_in_layout?
78
+ false
79
+ end
80
+
81
+ protected
82
+
83
+ # Internal: Extract excerpt from the content
84
+ #
85
+ # By default excerpt is your first paragraph of a doc: everything before
86
+ # the first two new lines:
87
+ #
88
+ # ---
89
+ # title: Example
90
+ # ---
91
+ #
92
+ # First paragraph with [link][1].
93
+ #
94
+ # Second paragraph.
95
+ #
96
+ # [1]: http://example.com/
97
+ #
98
+ # This is fairly good option for Markdown and Textile files. But might cause
99
+ # problems for HTML docs (which is quite unusual for Bunto). If default
100
+ # excerpt delimiter is not good for you, you might want to set your own via
101
+ # configuration option `excerpt_separator`. For example, following is a good
102
+ # alternative for HTML docs:
103
+ #
104
+ # # file: _config.yml
105
+ # excerpt_separator: "<!-- more -->"
106
+ #
107
+ # Notice that all markdown-style link references will be appended to the
108
+ # excerpt. So the example doc above will have this excerpt source:
109
+ #
110
+ # First paragraph with [link][1].
111
+ #
112
+ # [1]: http://example.com/
113
+ #
114
+ # Excerpts are rendered same time as content is rendered.
115
+ #
116
+ # Returns excerpt String
117
+ def extract_excerpt(doc_content)
118
+ head, _, tail = doc_content.to_s.partition(doc.excerpt_separator)
119
+
120
+ if tail.empty?
121
+ head
122
+ else
123
+ "" << head << "\n\n" << tail.scan(/^\[[^\]]+\]:.+$/).join("\n")
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,59 @@
1
+ module Bunto
2
+ module External
3
+ class << self
4
+ #
5
+ # Gems that, if installed, should be loaded.
6
+ # Usually contain subcommands.
7
+ #
8
+ def blessed_gems
9
+ %w(
10
+ bunto-docs
11
+ bunto-import
12
+ )
13
+ end
14
+
15
+ #
16
+ # Require a gem or file if it's present, otherwise silently fail.
17
+ #
18
+ # names - a string gem name or array of gem names
19
+ #
20
+ def require_if_present(names, &block)
21
+ Array(names).each do |name|
22
+ begin
23
+ require name
24
+ rescue LoadError
25
+ Bunto.logger.debug "Couldn't load #{name}. Skipping."
26
+ block.call(name) if block
27
+ false
28
+ end
29
+ end
30
+ end
31
+
32
+ #
33
+ # Require a gem or gems. If it's not present, show a very nice error
34
+ # message that explains everything and is much more helpful than the
35
+ # normal LoadError.
36
+ #
37
+ # names - a string gem name or array of gem names
38
+ #
39
+ def require_with_graceful_fail(names)
40
+ Array(names).each do |name|
41
+ begin
42
+ Bunto.logger.debug "Requiring:", "#{name}"
43
+ require name
44
+ rescue LoadError => e
45
+ Bunto.logger.error "Dependency Error:", <<-MSG
46
+ Yikes! It looks like you don't have #{name} or one of its dependencies installed.
47
+ In order to use Bunto as currently configured, you'll need to install this gem.
48
+
49
+ The full error message from Ruby is: '#{e.message}'
50
+
51
+ If you run into trouble, you can find helpful resources at http://buntorb.com/help/!
52
+ MSG
53
+ raise Bunto::Errors::MissingDependencyException.new(name)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,367 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'date'
4
+
5
+ module Bunto
6
+ module Filters
7
+ # Convert a Markdown string into HTML output.
8
+ #
9
+ # input - The Markdown String to convert.
10
+ #
11
+ # Returns the HTML formatted String.
12
+ def markdownify(input)
13
+ site = @context.registers[:site]
14
+ converter = site.find_converter_instance(Bunto::Converters::Markdown)
15
+ converter.convert(input)
16
+ end
17
+
18
+ # Convert a Markdown string into HTML output.
19
+ #
20
+ # input - The Markdown String to convert.
21
+ #
22
+ # Returns the HTML formatted String.
23
+ def smartify(input)
24
+ site = @context.registers[:site]
25
+ converter = site.find_converter_instance(Bunto::Converters::SmartyPants)
26
+ converter.convert(input)
27
+ end
28
+
29
+ # Convert a Sass string into CSS output.
30
+ #
31
+ # input - The Sass String to convert.
32
+ #
33
+ # Returns the CSS formatted String.
34
+ def sassify(input)
35
+ site = @context.registers[:site]
36
+ converter = site.find_converter_instance(Bunto::Converters::Sass)
37
+ converter.convert(input)
38
+ end
39
+
40
+ # Convert a Scss string into CSS output.
41
+ #
42
+ # input - The Scss String to convert.
43
+ #
44
+ # Returns the CSS formatted String.
45
+ def scssify(input)
46
+ site = @context.registers[:site]
47
+ converter = site.find_converter_instance(Bunto::Converters::Scss)
48
+ converter.convert(input)
49
+ end
50
+
51
+ # Slugify a filename or title.
52
+ #
53
+ # input - The filename or title to slugify.
54
+ # mode - how string is slugified
55
+ #
56
+ # Returns the given filename or title as a lowercase URL String.
57
+ # See Utils.slugify for more detail.
58
+ def slugify(input, mode=nil)
59
+ Utils.slugify(input, :mode => mode)
60
+ end
61
+
62
+ # Format a date in short format e.g. "27 Jan 2011".
63
+ #
64
+ # date - the Time to format.
65
+ #
66
+ # Returns the formatting String.
67
+ def date_to_string(date)
68
+ time(date).strftime("%d %b %Y")
69
+ end
70
+
71
+ # Format a date in long format e.g. "27 January 2011".
72
+ #
73
+ # date - The Time to format.
74
+ #
75
+ # Returns the formatted String.
76
+ def date_to_long_string(date)
77
+ time(date).strftime("%d %B %Y")
78
+ end
79
+
80
+ # Format a date for use in XML.
81
+ #
82
+ # date - The Time to format.
83
+ #
84
+ # Examples
85
+ #
86
+ # date_to_xmlschema(Time.now)
87
+ # # => "2011-04-24T20:34:46+08:00"
88
+ #
89
+ # Returns the formatted String.
90
+ def date_to_xmlschema(date)
91
+ time(date).xmlschema
92
+ end
93
+
94
+ # Format a date according to RFC-822
95
+ #
96
+ # date - The Time to format.
97
+ #
98
+ # Examples
99
+ #
100
+ # date_to_rfc822(Time.now)
101
+ # # => "Sun, 24 Apr 2011 12:34:46 +0000"
102
+ #
103
+ # Returns the formatted String.
104
+ def date_to_rfc822(date)
105
+ time(date).rfc822
106
+ end
107
+
108
+ # XML escape a string for use. Replaces any special characters with
109
+ # appropriate HTML entity replacements.
110
+ #
111
+ # input - The String to escape.
112
+ #
113
+ # Examples
114
+ #
115
+ # xml_escape('foo "bar" <baz>')
116
+ # # => "foo &quot;bar&quot; &lt;baz&gt;"
117
+ #
118
+ # Returns the escaped String.
119
+ def xml_escape(input)
120
+ CGI.escapeHTML(input.to_s)
121
+ end
122
+
123
+ # CGI escape a string for use in a URL. Replaces any special characters
124
+ # with appropriate %XX replacements.
125
+ #
126
+ # input - The String to escape.
127
+ #
128
+ # Examples
129
+ #
130
+ # cgi_escape('foo,bar;baz?')
131
+ # # => "foo%2Cbar%3Bbaz%3F"
132
+ #
133
+ # Returns the escaped String.
134
+ def cgi_escape(input)
135
+ CGI::escape(input)
136
+ end
137
+
138
+ # URI escape a string.
139
+ #
140
+ # input - The String to escape.
141
+ #
142
+ # Examples
143
+ #
144
+ # uri_escape('foo, bar \\baz?')
145
+ # # => "foo,%20bar%20%5Cbaz?"
146
+ #
147
+ # Returns the escaped String.
148
+ def uri_escape(input)
149
+ URI.escape(input)
150
+ end
151
+
152
+ # Count the number of words in the input string.
153
+ #
154
+ # input - The String on which to operate.
155
+ #
156
+ # Returns the Integer word count.
157
+ def number_of_words(input)
158
+ input.split.length
159
+ end
160
+
161
+ # Join an array of things into a string by separating with commas and the
162
+ # word "and" for the last one.
163
+ #
164
+ # array - The Array of Strings to join.
165
+ #
166
+ # Examples
167
+ #
168
+ # array_to_sentence_string(["apples", "oranges", "grapes"])
169
+ # # => "apples, oranges, and grapes"
170
+ #
171
+ # Returns the formatted String.
172
+ def array_to_sentence_string(array)
173
+ connector = "and"
174
+ case array.length
175
+ when 0
176
+ ""
177
+ when 1
178
+ array[0].to_s
179
+ when 2
180
+ "#{array[0]} #{connector} #{array[1]}"
181
+ else
182
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
183
+ end
184
+ end
185
+
186
+ # Convert the input into json string
187
+ #
188
+ # input - The Array or Hash to be converted
189
+ #
190
+ # Returns the converted json string
191
+ def jsonify(input)
192
+ as_liquid(input).to_json
193
+ end
194
+
195
+ # Group an array of items by a property
196
+ #
197
+ # input - the inputted Enumerable
198
+ # property - the property
199
+ #
200
+ # Returns an array of Hashes, each looking something like this:
201
+ # {"name" => "larry"
202
+ # "items" => [...] } # all the items where `property` == "larry"
203
+ def group_by(input, property)
204
+ if groupable?(input)
205
+ input.group_by do |item|
206
+ item_property(item, property).to_s
207
+ end.inject([]) do |memo, i|
208
+ memo << { "name" => i.first, "items" => i.last }
209
+ end
210
+ else
211
+ input
212
+ end
213
+ end
214
+
215
+ # Filter an array of objects
216
+ #
217
+ # input - the object array
218
+ # property - property within each object to filter by
219
+ # value - desired value
220
+ #
221
+ # Returns the filtered array of objects
222
+ def where(input, property, value)
223
+ return input unless input.is_a?(Enumerable)
224
+ input = input.values if input.is_a?(Hash)
225
+ input.select { |object| item_property(object, property).to_s == value.to_s }
226
+ end
227
+
228
+ # Sort an array of objects
229
+ #
230
+ # input - the object array
231
+ # property - property within each object to filter by
232
+ # nils ('first' | 'last') - nils appear before or after non-nil values
233
+ #
234
+ # Returns the filtered array of objects
235
+ def sort(input, property = nil, nils = "first")
236
+ if input.nil?
237
+ raise ArgumentError.new("Cannot sort a null object.")
238
+ end
239
+ if property.nil?
240
+ input.sort
241
+ else
242
+ case
243
+ when nils == "first"
244
+ order = - 1
245
+ when nils == "last"
246
+ order = + 1
247
+ else
248
+ raise ArgumentError.new("Invalid nils order: " \
249
+ "'#{nils}' is not a valid nils order. It must be 'first' or 'last'.")
250
+ end
251
+
252
+ input.sort do |apple, orange|
253
+ apple_property = item_property(apple, property)
254
+ orange_property = item_property(orange, property)
255
+
256
+ if !apple_property.nil? && orange_property.nil?
257
+ - order
258
+ elsif apple_property.nil? && !orange_property.nil?
259
+ + order
260
+ else
261
+ apple_property <=> orange_property
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ def pop(array, input = 1)
268
+ return array unless array.is_a?(Array)
269
+ new_ary = array.dup
270
+ new_ary.pop(input.to_i || 1)
271
+ new_ary
272
+ end
273
+
274
+ def push(array, input)
275
+ return array unless array.is_a?(Array)
276
+ new_ary = array.dup
277
+ new_ary.push(input)
278
+ new_ary
279
+ end
280
+
281
+ def shift(array, input = 1)
282
+ return array unless array.is_a?(Array)
283
+ new_ary = array.dup
284
+ new_ary.shift(input.to_i || 1)
285
+ new_ary
286
+ end
287
+
288
+ def unshift(array, input)
289
+ return array unless array.is_a?(Array)
290
+ new_ary = array.dup
291
+ new_ary.unshift(input)
292
+ new_ary
293
+ end
294
+
295
+ def sample(input, num = 1)
296
+ return input unless input.respond_to?(:sample)
297
+ n = num.to_i rescue 1
298
+ if n == 1
299
+ input.sample
300
+ else
301
+ input.sample(n)
302
+ end
303
+ end
304
+
305
+ # Convert an object into its String representation for debugging
306
+ #
307
+ # input - The Object to be converted
308
+ #
309
+ # Returns a String representation of the object.
310
+ def inspect(input)
311
+ CGI.escapeHTML(input.inspect)
312
+ end
313
+
314
+ private
315
+ def time(input)
316
+ case input
317
+ when Time
318
+ input
319
+ when Date
320
+ input.to_time
321
+ when String
322
+ Time.parse(input) rescue Time.at(input.to_i)
323
+ when Numeric
324
+ Time.at(input)
325
+ else
326
+ Bunto.logger.error "Invalid Date:", "'#{input}' is not a valid datetime."
327
+ exit(1)
328
+ end.localtime
329
+ end
330
+
331
+ def groupable?(element)
332
+ element.respond_to?(:group_by)
333
+ end
334
+
335
+ def item_property(item, property)
336
+ if item.respond_to?(:to_liquid)
337
+ item.to_liquid[property.to_s]
338
+ elsif item.respond_to?(:data)
339
+ item.data[property.to_s]
340
+ else
341
+ item[property.to_s]
342
+ end
343
+ end
344
+
345
+ def as_liquid(item)
346
+ case item
347
+ when Hash
348
+ pairs = item.map { |k, v| as_liquid([k, v]) }
349
+ Hash[pairs]
350
+ when Array
351
+ item.map { |i| as_liquid(i) }
352
+ else
353
+ if item.respond_to?(:to_liquid)
354
+ liquidated = item.to_liquid
355
+ # prevent infinite recursion for simple types (which return `self`)
356
+ if liquidated == item
357
+ item
358
+ else
359
+ as_liquid(liquidated)
360
+ end
361
+ else
362
+ item
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end