bunto 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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