bunto 2.0.0.pre → 3.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +80 -0
  3. data/README.markdown +15 -19
  4. data/bin/bunto +1 -1
  5. data/lib/bunto.rb +7 -5
  6. data/lib/bunto/cleaner.rb +3 -2
  7. data/lib/bunto/collection.rb +3 -3
  8. data/lib/bunto/commands/clean.rb +10 -12
  9. data/lib/bunto/commands/doctor.rb +1 -1
  10. data/lib/bunto/commands/new.rb +29 -0
  11. data/lib/bunto/commands/serve.rb +17 -15
  12. data/lib/bunto/configuration.rb +4 -3
  13. data/lib/bunto/converter.rb +6 -2
  14. data/lib/bunto/converters/markdown.rb +1 -1
  15. data/lib/bunto/converters/markdown/kramdown_parser.rb +1 -0
  16. data/lib/bunto/convertible.rb +5 -5
  17. data/lib/bunto/deprecator.rb +1 -1
  18. data/lib/bunto/document.rb +11 -4
  19. data/lib/bunto/drops/document_drop.rb +7 -0
  20. data/lib/bunto/entry_filter.rb +6 -2
  21. data/lib/bunto/errors.rb +4 -0
  22. data/lib/bunto/external.rb +1 -1
  23. data/lib/bunto/filters.rb +49 -8
  24. data/lib/bunto/frontmatter_defaults.rb +2 -2
  25. data/lib/bunto/hooks.rb +1 -0
  26. data/lib/bunto/layout.rb +16 -1
  27. data/lib/bunto/mime.types +1 -1
  28. data/lib/bunto/page.rb +1 -0
  29. data/lib/bunto/plugin.rb +1 -1
  30. data/lib/bunto/plugin_manager.rb +4 -4
  31. data/lib/bunto/publisher.rb +4 -4
  32. data/lib/bunto/readers/data_reader.rb +3 -2
  33. data/lib/bunto/readers/layout_reader.rb +19 -3
  34. data/lib/bunto/readers/post_reader.rb +5 -1
  35. data/lib/bunto/regenerator.rb +5 -3
  36. data/lib/bunto/renderer.rb +3 -2
  37. data/lib/bunto/site.rb +47 -17
  38. data/lib/bunto/static_file.rb +5 -1
  39. data/lib/bunto/tags/include.rb +33 -31
  40. data/lib/bunto/tags/link.rb +26 -0
  41. data/lib/bunto/tags/post_url.rb +18 -8
  42. data/lib/bunto/theme.rb +56 -0
  43. data/lib/bunto/utils.rb +3 -2
  44. data/lib/bunto/version.rb +1 -1
  45. data/lib/site_template/_config.yml +8 -2
  46. data/lib/site_template/_includes/footer.html +3 -3
  47. data/lib/site_template/_includes/head.html +2 -2
  48. data/lib/site_template/_includes/header.html +3 -3
  49. data/lib/site_template/_layouts/default.html +2 -2
  50. data/lib/site_template/_layouts/page.html +1 -1
  51. data/lib/site_template/_layouts/post.html +1 -1
  52. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +1 -1
  53. data/lib/site_template/_sass/_base.scss +11 -17
  54. data/lib/site_template/about.md +5 -1
  55. data/lib/site_template/index.html +1 -1
  56. metadata +32 -29
@@ -8,7 +8,9 @@ module Bunto
8
8
  #
9
9
  # Returns the String prefix.
10
10
  def self.highlighter_prefix(highlighter_prefix = nil)
11
- @highlighter_prefix = highlighter_prefix if highlighter_prefix
11
+ if !defined?(@highlighter_prefix) || !highlighter_prefix.nil?
12
+ @highlighter_prefix = highlighter_prefix
13
+ end
12
14
  @highlighter_prefix
13
15
  end
14
16
 
@@ -20,7 +22,9 @@ module Bunto
20
22
  #
21
23
  # Returns the String suffix.
22
24
  def self.highlighter_suffix(highlighter_suffix = nil)
23
- @highlighter_suffix = highlighter_suffix if highlighter_suffix
25
+ if !defined?(@highlighter_suffix) || !highlighter_suffix.nil?
26
+ @highlighter_suffix = highlighter_suffix
27
+ end
24
28
  @highlighter_suffix
25
29
  end
26
30
 
@@ -6,7 +6,7 @@ module Bunto
6
6
  safe true
7
7
 
8
8
  def setup
9
- return if @setup
9
+ return if @setup ||= false
10
10
  unless (@parser = get_processor)
11
11
  Bunto.logger.error "Invalid Markdown processor given:", @config["markdown"]
12
12
  Bunto.logger.info "", "Custom processors are not loaded in safe mode" if @config["safe"]
@@ -18,6 +18,7 @@ module Bunto
18
18
  Bunto::External.require_with_graceful_fail "kramdown"
19
19
  @main_fallback_highlighter = config["highlighter"] || "rouge"
20
20
  @config = config["kramdown"] || {}
21
+ @highlighter = nil
21
22
  setup
22
23
  end
23
24
 
@@ -39,9 +39,9 @@ module Bunto
39
39
  filename = File.join(base, name)
40
40
 
41
41
  begin
42
- self.content = File.read(site.in_source_dir(base, name),
42
+ self.content = File.read(@path || site.in_source_dir(base, name),
43
43
  Utils.merged_file_read_opts(site, opts))
44
- if content =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
44
+ if content =~ Document::YAML_FRONT_MATTER_REGEXP
45
45
  self.content = $POSTMATCH
46
46
  self.data = SafeYAML.load(Regexp.last_match(1))
47
47
  end
@@ -215,9 +215,9 @@ module Bunto
215
215
  payload["layout"] = Utils.deep_merge_hashes(payload["layout"] || {}, layout.data)
216
216
 
217
217
  self.output = render_liquid(layout.content,
218
- payload,
219
- info,
220
- File.join(site.config['layouts_dir'], layout.name))
218
+ payload,
219
+ info,
220
+ layout.relative_path)
221
221
 
222
222
  # Add layout to dependency tree
223
223
  site.regenerator.add_dependency(
@@ -22,7 +22,7 @@ module Bunto
22
22
 
23
23
  def no_subcommand(args)
24
24
  if args.size > 0 && args.first =~ /^--/ && !%w(--help --version).include?(args.first)
25
- deprecation_message "Bunto now uses subcommands instead of just switches. Run `bunto --help` to find out more."
25
+ deprecation_message "Bunto now uses subcommands instead of just switches. Run `bunto help` to find out more."
26
26
  abort
27
27
  end
28
28
  end
@@ -68,7 +68,11 @@ module Bunto
68
68
  end
69
69
 
70
70
  def date
71
- data['date'] ||= site.time
71
+ data['date'] ||= (draft? ? source_file_mtime : site.time)
72
+ end
73
+
74
+ def source_file_mtime
75
+ @source_file_mtime ||= File.mtime(path)
72
76
  end
73
77
 
74
78
  # Returns whether the document is a draft. This is only the case if
@@ -217,8 +221,11 @@ module Bunto
217
221
  def destination(base_directory)
218
222
  dest = site.in_dest_dir(base_directory)
219
223
  path = site.in_dest_dir(dest, URL.unescape_path(url))
220
- path = File.join(path, "index.html") if url.end_with?("/")
221
- path << output_ext unless path.end_with? output_ext
224
+ if url.end_with? "/"
225
+ path = File.join(path, "index.html")
226
+ else
227
+ path << output_ext unless path.end_with? output_ext
228
+ end
222
229
  path
223
230
  end
224
231
 
@@ -256,7 +263,7 @@ module Bunto
256
263
  @data = SafeYAML.load_file(path)
257
264
  else
258
265
  begin
259
- defaults = @site.frontmatter_defaults.all(url, collection.label.to_sym)
266
+ defaults = @site.frontmatter_defaults.all(relative_path, collection.label.to_sym)
260
267
  merge_data!(defaults, source: "front matter defaults") unless defaults.empty?
261
268
 
262
269
  self.content = File.read(path, Utils.merged_file_read_opts(site, opts))
@@ -20,6 +20,13 @@ module Bunto
20
20
  fallback_data['excerpt'].to_s
21
21
  end
22
22
 
23
+ def <=>(other)
24
+ return nil unless other.is_a? DocumentDrop
25
+ cmp = self['date'] <=> other['date']
26
+ cmp = self['path'] <=> other['path'] if cmp.nil? || cmp == 0
27
+ cmp
28
+ end
29
+
23
30
  private
24
31
  def_delegator :@obj, :data, :fallback_data
25
32
  end
@@ -1,6 +1,6 @@
1
1
  module Bunto
2
2
  class EntryFilter
3
- SPECIAL_LEADING_CHARACTERS = ['.', '_', '#'].freeze
3
+ SPECIAL_LEADING_CHARACTERS = ['.', '_', '#', '~'].freeze
4
4
 
5
5
  attr_reader :site
6
6
 
@@ -52,7 +52,11 @@ module Bunto
52
52
  end
53
53
 
54
54
  def symlink?(entry)
55
- File.symlink?(entry) && site.safe
55
+ site.safe && File.symlink?(entry) && symlink_outside_site_source?(entry)
56
+ end
57
+
58
+ def symlink_outside_site_source?(entry)
59
+ ! File.realpath(entry).start_with?(File.realpath(@site.source))
56
60
  end
57
61
 
58
62
  def ensure_leading_slash(path)
@@ -6,5 +6,9 @@ module Bunto
6
6
  InvalidPermalinkError = Class.new(FatalException)
7
7
  InvalidYAMLFrontMatterError = Class.new(FatalException)
8
8
  MissingDependencyException = Class.new(FatalException)
9
+
10
+ InvalidDateError = Class.new(FatalException)
11
+ InvalidPostNameError = Class.new(FatalException)
12
+ PostURLError = Class.new(FatalException)
9
13
  end
10
14
  end
@@ -48,7 +48,7 @@ In order to use Bunto as currently configured, you'll need to install this gem.
48
48
 
49
49
  The full error message from Ruby is: '#{e.message}'
50
50
 
51
- If you run into trouble, you can find helpful resources at https://bunto.github.io/help/!
51
+ If you run into trouble, you can find helpful resources at http://bunto.github.io/help/!
52
52
  MSG
53
53
  raise Bunto::Errors::MissingDependencyException.new(name)
54
54
  end
@@ -1,6 +1,7 @@
1
1
  require 'uri'
2
2
  require 'json'
3
3
  require 'date'
4
+ require 'liquid'
4
5
 
5
6
  module Bunto
6
7
  module Filters
@@ -15,11 +16,11 @@ module Bunto
15
16
  converter.convert(input)
16
17
  end
17
18
 
18
- # Convert a Markdown string into HTML output.
19
+ # Convert quotes into smart quotes.
19
20
  #
20
- # input - The Markdown String to convert.
21
+ # input - The String to convert.
21
22
  #
22
- # Returns the HTML formatted String.
23
+ # Returns the smart-quotified String.
23
24
  def smartify(input)
24
25
  site = @context.registers[:site]
25
26
  converter = site.find_converter_instance(Bunto::Converters::SmartyPants)
@@ -117,7 +118,7 @@ module Bunto
117
118
  #
118
119
  # Returns the escaped String.
119
120
  def xml_escape(input)
120
- CGI.escapeHTML(input.to_s)
121
+ input.to_s.encode(:xml => :attr).gsub(/\A"|"\Z/, "")
121
122
  end
122
123
 
123
124
  # CGI escape a string for use in a URL. Replaces any special characters
@@ -205,7 +206,7 @@ module Bunto
205
206
  input.group_by do |item|
206
207
  item_property(item, property).to_s
207
208
  end.inject([]) do |memo, i|
208
- memo << { "name" => i.first, "items" => i.last }
209
+ memo << { "name" => i.first, "items" => i.last, "size" => i.last.size }
209
210
  end
210
211
  else
211
212
  input
@@ -222,7 +223,27 @@ module Bunto
222
223
  def where(input, property, value)
223
224
  return input unless input.is_a?(Enumerable)
224
225
  input = input.values if input.is_a?(Hash)
225
- input.select { |object| item_property(object, property).to_s == value.to_s }
226
+ input.select { |object| Array(item_property(object, property)).map(&:to_s).include?(value.to_s) }
227
+ end
228
+
229
+ # Filters an array of objects against an expression
230
+ #
231
+ # input - the object array
232
+ # variable - the variable to assign each item to in the expression
233
+ # expression - a Liquid comparison expression passed in as a string
234
+ #
235
+ # Returns the filtered array of objects
236
+ def where_exp(input, variable, expression)
237
+ return input unless input.is_a?(Enumerable)
238
+ input = input.values if input.is_a?(Hash) # FIXME
239
+
240
+ condition = parse_condition(expression)
241
+ @context.stack do
242
+ input.select do |object|
243
+ @context[variable] = object
244
+ condition.evaluate(@context)
245
+ end
246
+ end
226
247
  end
227
248
 
228
249
  # Sort an array of objects
@@ -308,14 +329,14 @@ module Bunto
308
329
  #
309
330
  # Returns a String representation of the object.
310
331
  def inspect(input)
311
- CGI.escapeHTML(input.inspect)
332
+ xml_escape(input.inspect)
312
333
  end
313
334
 
314
335
  private
315
336
  def time(input)
316
337
  case input
317
338
  when Time
318
- input
339
+ input.clone
319
340
  when Date
320
341
  input.to_time
321
342
  when String
@@ -363,5 +384,25 @@ module Bunto
363
384
  end
364
385
  end
365
386
  end
387
+
388
+ # Parse a string to a Liquid Condition
389
+ def parse_condition(exp)
390
+ parser = Liquid::Parser.new(exp)
391
+ left_expr = parser.expression
392
+ operator = parser.consume?(:comparison)
393
+ condition =
394
+ if operator
395
+ Liquid::Condition.new(left_expr, operator, parser.expression)
396
+ else
397
+ Liquid::Condition.new(left_expr)
398
+ end
399
+ parser.consume(:end_of_string)
400
+
401
+ condition
402
+ end
366
403
  end
367
404
  end
405
+
406
+ Liquid::Template.register_filter(
407
+ Bunto::Filters
408
+ )
@@ -94,8 +94,8 @@ module Bunto
94
94
  return true if !scope.key?('path') || scope['path'].empty?
95
95
 
96
96
  scope_path = Pathname.new(scope['path'])
97
- Pathname.new(sanitize_path(path)).ascend do |path|
98
- if path.to_s == scope_path.to_s
97
+ Pathname.new(sanitize_path(path)).ascend do |ascended_path|
98
+ if ascended_path.to_s == scope_path.to_s
99
99
  return true
100
100
  end
101
101
  end
@@ -12,6 +12,7 @@ module Bunto
12
12
  # initial empty hooks
13
13
  @registry = {
14
14
  :site => {
15
+ :after_init => [],
15
16
  :after_reset => [],
16
17
  :post_read => [],
17
18
  :pre_render => [],
@@ -29,7 +29,14 @@ module Bunto
29
29
  @site = site
30
30
  @base = base
31
31
  @name = name
32
- @path = site.in_source_dir(base, name)
32
+
33
+ if site.theme && site.theme.layouts_path.eql?(base)
34
+ @base_dir = site.theme.root
35
+ @path = site.in_theme_dir(base, name)
36
+ else
37
+ @base_dir = site.source
38
+ @path = site.in_source_dir(base, name)
39
+ end
33
40
 
34
41
  self.data = {}
35
42
 
@@ -45,5 +52,13 @@ module Bunto
45
52
  def process(name)
46
53
  self.ext = File.extname(name)
47
54
  end
55
+
56
+ # The path to the layout, relative to the site source.
57
+ #
58
+ # Returns a String path which represents the relative path
59
+ # from the site source to this layout
60
+ def relative_path
61
+ @relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(@base_dir)).to_s
62
+ end
48
63
  end
49
64
  end
@@ -797,4 +797,4 @@ video/x-ms-wvx wvx
797
797
  video/x-msvideo avi
798
798
  video/x-sgi-movie movie
799
799
  video/x-smv smv
800
- x-conference/x-cooltalk ice
800
+ x-conference/x-cooltalk ice
@@ -40,6 +40,7 @@ module Bunto
40
40
  @base = base
41
41
  @dir = dir
42
42
  @name = name
43
+ @path = site.in_source_dir(base, dir, name)
43
44
 
44
45
  process(name)
45
46
  read_yaml(File.join(base, dir), name)
@@ -60,7 +60,7 @@ module Bunto
60
60
  #
61
61
  # Returns the safety Boolean.
62
62
  def self.safe(safe = nil)
63
- if safe
63
+ if !defined?(@safe) || !safe.nil?
64
64
  @safe = safe
65
65
  end
66
66
  @safe || false
@@ -30,16 +30,16 @@ module Bunto
30
30
  def self.require_from_bundler
31
31
  if !ENV["BUNTO_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
32
32
  require "bundler"
33
- Bundler.setup # puts all groups on the load path
34
- required_gems = Bundler.require(:bunto_plugins) # requires the gems in this group only
33
+
34
+ Bundler.setup
35
+ required_gems = Bundler.require(:bunto_plugins)
35
36
  Bunto.logger.debug("PluginManager:", "Required #{required_gems.map(&:name).join(', ')}")
36
37
  ENV["BUNTO_NO_BUNDLER_REQUIRE"] = "true"
38
+
37
39
  true
38
40
  else
39
41
  false
40
42
  end
41
- rescue LoadError, Bundler::GemfileNotFound
42
- false
43
43
  end
44
44
 
45
45
  # Check whether a gem plugin is allowed to be used during this build.
@@ -8,14 +8,14 @@ module Bunto
8
8
  can_be_published?(thing) && !hidden_in_the_future?(thing)
9
9
  end
10
10
 
11
+ def hidden_in_the_future?(thing)
12
+ thing.respond_to?(:date) && !@site.future && thing.date.to_i > @site.time.to_i
13
+ end
14
+
11
15
  private
12
16
 
13
17
  def can_be_published?(thing)
14
18
  thing.data.fetch('published', true) || @site.unpublished
15
19
  end
16
-
17
- def hidden_in_the_future?(thing)
18
- thing.respond_to?(:date) && !@site.future && thing.date.to_i > @site.time.to_i
19
- end
20
20
  end
21
21
  end
@@ -4,6 +4,7 @@ module Bunto
4
4
  def initialize(site)
5
5
  @site = site
6
6
  @content = {}
7
+ @entry_filter = EntryFilter.new(site)
7
8
  end
8
9
 
9
10
  # Read all the files in <source>/<dir>/_drafts and create a new Draft
@@ -26,7 +27,7 @@ module Bunto
26
27
  #
27
28
  # Returns nothing
28
29
  def read_data_to(dir, data)
29
- return unless File.directory?(dir) && (!site.safe || !File.symlink?(dir))
30
+ return unless File.directory?(dir) && !@entry_filter.symlink?(dir)
30
31
 
31
32
  entries = Dir.chdir(dir) do
32
33
  Dir['*.{yaml,yml,json,csv}'] + Dir['*'].select { |fn| File.directory?(fn) }
@@ -34,7 +35,7 @@ module Bunto
34
35
 
35
36
  entries.each do |entry|
36
37
  path = @site.in_source_dir(dir, entry)
37
- next if File.symlink?(path) && site.safe
38
+ next if @entry_filter.symlink?(path)
38
39
 
39
40
  key = sanitize_filename(File.basename(entry, '.*'))
40
41
  if File.directory?(path)
@@ -7,8 +7,12 @@ module Bunto
7
7
  end
8
8
 
9
9
  def read
10
- layout_entries.each do |f|
11
- @layouts[layout_name(f)] = Layout.new(site, layout_directory, f)
10
+ layout_entries.each do |layout_file|
11
+ @layouts[layout_name(layout_file)] = Layout.new(site, layout_directory, layout_file)
12
+ end
13
+
14
+ theme_layout_entries.each do |layout_file|
15
+ @layouts[layout_name(layout_file)] ||= Layout.new(site, theme_layout_directory, layout_file)
12
16
  end
13
17
 
14
18
  @layouts
@@ -18,11 +22,23 @@ module Bunto
18
22
  @layout_directory ||= (layout_directory_in_cwd || layout_directory_inside_source)
19
23
  end
20
24
 
25
+ def theme_layout_directory
26
+ @theme_layout_directory ||= site.theme.layouts_path if site.theme
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def layout_entries
32
+ entries_in layout_directory
33
+ end
34
+
35
+ def theme_layout_entries
36
+ theme_layout_directory ? entries_in(theme_layout_directory) : []
37
+ end
38
+
39
+ def entries_in(dir)
24
40
  entries = []
25
- within(layout_directory) do
41
+ within(dir) do
26
42
  entries = EntryFilter.new(site).filter(Dir['**/*.*'])
27
43
  end
28
44
  entries