bunto 2.0.0.pre → 3.0.0

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