bunto 3.2.1 → 3.4.5

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -4
  3. data/LICENSE +1 -1
  4. data/README.markdown +20 -25
  5. data/exe/bunto +1 -1
  6. data/lib/bunto.rb +10 -4
  7. data/lib/bunto/collection.rb +11 -4
  8. data/lib/bunto/commands/build.rb +17 -2
  9. data/lib/bunto/commands/doctor.rb +1 -1
  10. data/lib/bunto/commands/new.rb +35 -5
  11. data/lib/bunto/commands/new_theme.rb +4 -2
  12. data/lib/bunto/commands/serve.rb +45 -15
  13. data/lib/bunto/commands/serve/servlet.rb +1 -1
  14. data/lib/bunto/configuration.rb +9 -7
  15. data/lib/bunto/converters/markdown/kramdown_parser.rb +2 -2
  16. data/lib/bunto/converters/markdown/redcarpet_parser.rb +1 -1
  17. data/lib/bunto/convertible.rb +21 -82
  18. data/lib/bunto/desktop.ini +1 -1
  19. data/lib/bunto/document.rb +118 -81
  20. data/lib/bunto/drops/bunto_drop.rb +1 -1
  21. data/lib/bunto/drops/static_file_drop.rb +11 -0
  22. data/lib/bunto/drops/url_drop.rb +5 -0
  23. data/lib/bunto/entry_filter.rb +9 -10
  24. data/lib/bunto/excerpt.rb +2 -3
  25. data/lib/bunto/external.rb +1 -1
  26. data/lib/bunto/filters.rb +10 -32
  27. data/lib/bunto/filters/grouping_filters.rb +63 -0
  28. data/lib/bunto/filters/url_filters.rb +40 -0
  29. data/lib/bunto/frontmatter_defaults.rb +1 -1
  30. data/lib/bunto/hooks.rb +9 -9
  31. data/lib/bunto/log_adapter.rb +1 -1
  32. data/lib/bunto/page.rb +8 -4
  33. data/lib/bunto/plugin.rb +1 -1
  34. data/lib/bunto/reader.rb +2 -1
  35. data/lib/bunto/readers/data_reader.rb +9 -10
  36. data/lib/bunto/readers/post_reader.rb +1 -1
  37. data/lib/bunto/readers/theme_assets_reader.rb +47 -0
  38. data/lib/bunto/regenerator.rb +1 -1
  39. data/lib/bunto/related_posts.rb +3 -9
  40. data/lib/bunto/renderer.rb +26 -6
  41. data/lib/bunto/site.rb +12 -7
  42. data/lib/bunto/static_file.rb +20 -9
  43. data/lib/bunto/tags/highlight.rb +3 -3
  44. data/lib/bunto/tags/include.rb +9 -5
  45. data/lib/bunto/tags/link.rb +4 -2
  46. data/lib/bunto/tags/post_url.rb +4 -2
  47. data/lib/bunto/theme.rb +8 -4
  48. data/lib/bunto/theme_builder.rb +2 -2
  49. data/lib/bunto/url.rb +31 -8
  50. data/lib/bunto/utils.rb +16 -2
  51. data/lib/bunto/utils/ansi.rb +1 -1
  52. data/lib/bunto/utils/exec.rb +25 -0
  53. data/lib/bunto/utils/platforms.rb +52 -2
  54. data/lib/bunto/utils/win_tz.rb +73 -0
  55. data/lib/bunto/version.rb +1 -1
  56. data/lib/site_template/_config.yml +8 -3
  57. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +4 -4
  58. data/lib/site_template/about.md +1 -1
  59. data/lib/site_template/index.md +6 -0
  60. data/lib/theme_template/LICENSE.txt.erb +1 -1
  61. data/lib/theme_template/README.md.erb +4 -4
  62. data/lib/theme_template/gitignore.erb +1 -0
  63. data/lib/theme_template/theme.gemspec.erb +3 -2
  64. metadata +55 -40
  65. data/lib/site_template/css/main.scss +0 -39
  66. data/lib/site_template/feed.xml +0 -30
  67. data/lib/site_template/index.html +0 -23
@@ -7,20 +7,20 @@ module Bunto
7
7
  @entry_filter = EntryFilter.new(site)
8
8
  end
9
9
 
10
- # Read all the files in <source>/<dir>/_drafts and create a new Draft
11
- # object with each one.
10
+ # Read all the files in <dir> and adds them to @content
12
11
  #
13
12
  # dir - The String relative path of the directory to read.
14
13
  #
15
- # Returns nothing.
14
+ # Returns @content, a Hash of the .yaml, .yml,
15
+ # .json, and .csv files in the base directory
16
16
  def read(dir)
17
17
  base = site.in_source_dir(dir)
18
18
  read_data_to(base, @content)
19
19
  @content
20
20
  end
21
21
 
22
- # Read and parse all yaml files under <dir> and add them to the
23
- # <data> variable.
22
+ # Read and parse all .yaml, .yml, .json, and .csv
23
+ # files under <dir> and add them to the <data> variable.
24
24
  #
25
25
  # dir - The string absolute path of the directory to read.
26
26
  # data - The variable to which data will be added.
@@ -37,10 +37,10 @@ module Bunto
37
37
  path = @site.in_source_dir(dir, entry)
38
38
  next if @entry_filter.symlink?(path)
39
39
 
40
- key = sanitize_filename(File.basename(entry, ".*"))
41
40
  if File.directory?(path)
42
- read_data_to(path, data[key] = {})
41
+ read_data_to(path, data[sanitize_filename(entry)] = {})
43
42
  else
43
+ key = sanitize_filename(File.basename(entry, ".*"))
44
44
  data[key] = read_data_file(path)
45
45
  end
46
46
  end
@@ -54,7 +54,7 @@ module Bunto
54
54
  when ".csv"
55
55
  CSV.read(path, {
56
56
  :headers => true,
57
- :encoding => site.config["encoding"]
57
+ :encoding => site.config["encoding"],
58
58
  }).map(&:to_hash)
59
59
  else
60
60
  SafeYAML.load_file(path)
@@ -62,8 +62,7 @@ module Bunto
62
62
  end
63
63
 
64
64
  def sanitize_filename(name)
65
- name.gsub!(%r![^\w\s-]+!, "")
66
- name.gsub!(%r!(^|\b\s)\s+($|\s?\b)!, '\\1\\2')
65
+ name.gsub!(%r![^\w\s-]+|(?<=^|\b\s)\s+(?=$|\s?\b)!, "".freeze)
67
66
  name.gsub(%r!\s+!, "_")
68
67
  end
69
68
  end
@@ -57,7 +57,7 @@ module Bunto
57
57
  path = @site.in_source_dir(File.join(dir, magic_dir, entry))
58
58
  Document.new(path, {
59
59
  :site => @site,
60
- :collection => @site.posts
60
+ :collection => @site.posts,
61
61
  })
62
62
  end.reject(&:nil?)
63
63
  end
@@ -0,0 +1,47 @@
1
+ module Bunto
2
+ class ThemeAssetsReader
3
+ attr_reader :site
4
+ def initialize(site)
5
+ @site = site
6
+ end
7
+
8
+ def read
9
+ return unless site.theme && site.theme.assets_path
10
+
11
+ Find.find(site.theme.assets_path) do |path|
12
+ next if File.directory?(path)
13
+ if File.symlink?(path)
14
+ Bunto.logger.warn "Theme reader:", "Ignored symlinked asset: #{path}"
15
+ else
16
+ read_theme_asset(path)
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+ def read_theme_asset(path)
23
+ base = site.theme.root
24
+ dir = File.dirname(path.sub("#{site.theme.root}/", ""))
25
+ name = File.basename(path)
26
+
27
+ if Utils.has_yaml_header?(path)
28
+ append_unless_exists site.pages,
29
+ Bunto::Page.new(site, base, dir, name)
30
+ else
31
+ append_unless_exists site.static_files,
32
+ Bunto::StaticFile.new(site, base, dir, name)
33
+ end
34
+ end
35
+
36
+ def append_unless_exists(haystack, new_item)
37
+ if haystack.any? { |file| file.relative_path == new_item.relative_path }
38
+ Bunto.logger.debug "Theme:",
39
+ "Ignoring #{new_item.relative_path} in theme due to existing file " \
40
+ "with that path in site."
41
+ return
42
+ end
43
+
44
+ haystack << new_item
45
+ end
46
+ end
47
+ end
@@ -40,7 +40,7 @@ module Bunto
40
40
 
41
41
  metadata[path] = {
42
42
  "mtime" => File.mtime(path),
43
- "deps" => []
43
+ "deps" => [],
44
44
  }
45
45
  cache[path] = true
46
46
  end
@@ -26,15 +26,15 @@ module Bunto
26
26
  def build_index
27
27
  self.class.lsi ||= begin
28
28
  lsi = ClassifierReborn::LSI.new(:auto_rebuild => false)
29
- display("Populating LSI...")
29
+ Bunto.logger.info("Populating LSI...")
30
30
 
31
31
  site.posts.docs.each do |x|
32
32
  lsi.add_item(x)
33
33
  end
34
34
 
35
- display("Rebuilding index...")
35
+ Bunto.logger.info("Rebuilding index...")
36
36
  lsi.build_index
37
- display("")
37
+ Bunto.logger.info("")
38
38
  lsi
39
39
  end
40
40
  end
@@ -46,11 +46,5 @@ module Bunto
46
46
  def most_recent_posts
47
47
  @most_recent_posts ||= (site.posts.docs.reverse - [post]).first(10)
48
48
  end
49
-
50
- def display(output)
51
- $stdout.print("\n")
52
- $stdout.print(Bunto.logger.formatted_topic(output))
53
- $stdout.flush
54
- end
55
49
  end
56
50
  end
@@ -2,12 +2,32 @@
2
2
 
3
3
  module Bunto
4
4
  class Renderer
5
- attr_reader :document, :site, :payload
5
+ attr_reader :document, :site
6
+ attr_writer :layouts, :payload
6
7
 
7
8
  def initialize(site, document, site_payload = nil)
8
9
  @site = site
9
10
  @document = document
10
- @payload = site_payload || site.site_payload
11
+ @payload = site_payload
12
+ end
13
+
14
+ # Fetches the payload used in Liquid rendering.
15
+ # It can be written with #payload=(new_payload)
16
+ # Falls back to site.site_payload if no payload is set.
17
+ #
18
+ # Returns a Bunto::Drops::UnifiedPayloadDrop
19
+ def payload
20
+ @payload ||= site.site_payload
21
+ end
22
+
23
+ # The list of layouts registered for this Renderer.
24
+ # It can be written with #layouts=(new_layouts)
25
+ # Falls back to site.layouts if no layouts are registered.
26
+ #
27
+ # Returns a Hash of String => Bunto::Layout identified
28
+ # as basename without the extension name.
29
+ def layouts
30
+ @layouts || site.layouts
11
31
  end
12
32
 
13
33
  # Determine which converters to use based on this document's
@@ -15,7 +35,7 @@ module Bunto
15
35
  #
16
36
  # Returns an array of Converter instances.
17
37
  def converters
18
- @converters ||= site.converters.select { |c| c.matches(document.extname) }
38
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }.sort
19
39
  end
20
40
 
21
41
  # Determine the extname the outputted file should have
@@ -126,7 +146,7 @@ module Bunto
126
146
  #
127
147
  # Returns true if the layout is invalid, false if otherwise
128
148
  def invalid_layout?(layout)
129
- !document.data["layout"].nil? && layout.nil?
149
+ !document.data["layout"].nil? && layout.nil? && !(document.is_a? Bunto::Excerpt)
130
150
  end
131
151
 
132
152
  # Render layouts and place given content inside.
@@ -137,7 +157,7 @@ module Bunto
137
157
  # Returns the content placed in the Liquid-rendered layouts
138
158
  def place_in_layouts(content, payload, info)
139
159
  output = content.dup
140
- layout = site.layouts[document.data["layout"]]
160
+ layout = layouts[document.data["layout"]]
141
161
 
142
162
  Bunto.logger.warn(
143
163
  "Build Warning:",
@@ -167,7 +187,7 @@ module Bunto
167
187
  site.in_source_dir(layout.path)
168
188
  ) if document.write?
169
189
 
170
- if (layout = site.layouts[layout.data["layout"]])
190
+ if (layout = layouts[layout.data["layout"]])
171
191
  break if used.include?(layout)
172
192
  used << layout
173
193
  end
@@ -191,11 +191,7 @@ module Bunto
191
191
  render_pages(payload)
192
192
 
193
193
  Bunto::Hooks.trigger :site, :post_render, self, payload
194
- # rubocop: disable HandleExceptions
195
- rescue Errno::ENOENT
196
- # ignore missing layout dir
197
194
  end
198
- # rubocop: enable HandleExceptions
199
195
 
200
196
  # Remove orphaned files and empty directories in destination.
201
197
  #
@@ -306,7 +302,7 @@ module Bunto
306
302
  Bunto.logger.abort_with "Since v3.0, permalinks for pages" \
307
303
  " in subfolders must be relative to the" \
308
304
  " site source directory, not the parent" \
309
- " directory. Check http://bunto.github.io/docs/upgrading"\
305
+ " directory. Check https://buntorb.com/docs/upgrading/"\
310
306
  " for more info."
311
307
  end
312
308
  end
@@ -424,13 +420,22 @@ module Bunto
424
420
  private
425
421
  def configure_theme
426
422
  self.theme = nil
427
- self.theme = Bunto::Theme.new(config["theme"]) if config["theme"]
423
+ return if config["theme"].nil?
424
+
425
+ self.theme =
426
+ if config["theme"].is_a?(String)
427
+ Bunto::Theme.new(config["theme"])
428
+ else
429
+ Bunto.logger.warn "Theme:", "value of 'theme' in config should be " \
430
+ "String to use gem-based themes, but got #{config["theme"].class}"
431
+ nil
432
+ end
428
433
  end
429
434
 
430
435
  private
431
436
  def configure_include_paths
432
437
  @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
433
- @includes_load_paths << theme.includes_path if self.theme
438
+ @includes_load_paths << theme.includes_path if theme && theme.includes_path
434
439
  end
435
440
 
436
441
  private
@@ -1,6 +1,6 @@
1
1
  module Bunto
2
2
  class StaticFile
3
- attr_reader :relative_path, :extname
3
+ attr_reader :relative_path, :extname, :name
4
4
 
5
5
  class << self
6
6
  # The cache of last modification times [path] -> mtime.
@@ -28,6 +28,10 @@ module Bunto
28
28
  @collection = collection
29
29
  @relative_path = File.join(*[@dir, @name].compact)
30
30
  @extname = File.extname(@name)
31
+
32
+ data.default_proc = proc do |_, key|
33
+ site.frontmatter_defaults.find(relative_path, type, key)
34
+ end
31
35
  end
32
36
  # rubocop: enable ParameterLists
33
37
 
@@ -96,11 +100,15 @@ module Bunto
96
100
  end
97
101
 
98
102
  def to_liquid
99
- {
100
- "extname" => extname,
101
- "modified_time" => modified_time,
102
- "path" => File.join("", relative_path)
103
- }
103
+ @to_liquid ||= Drops::StaticFileDrop.new(self)
104
+ end
105
+
106
+ def data
107
+ @data ||= {}
108
+ end
109
+
110
+ def basename
111
+ File.basename(name, extname)
104
112
  end
105
113
 
106
114
  def placeholders
@@ -110,7 +118,7 @@ module Bunto
110
118
  @collection.relative_directory.size..relative_path.size],
111
119
  :output_ext => "",
112
120
  :name => "",
113
- :title => ""
121
+ :title => "",
114
122
  }
115
123
  end
116
124
 
@@ -123,7 +131,7 @@ module Bunto
123
131
  else
124
132
  ::Bunto::URL.new({
125
133
  :template => @collection.url_template,
126
- :placeholders => placeholders
134
+ :placeholders => placeholders,
127
135
  })
128
136
  end.to_s.gsub(%r!/$!, "")
129
137
  end
@@ -146,7 +154,10 @@ module Bunto
146
154
  else
147
155
  FileUtils.copy_entry(path, dest_path)
148
156
  end
149
- File.utime(self.class.mtimes[path], self.class.mtimes[path], dest_path)
157
+
158
+ unless File.symlink?(dest_path)
159
+ File.utime(self.class.mtimes[path], self.class.mtimes[path], dest_path)
160
+ end
150
161
  end
151
162
  end
152
163
  end
@@ -8,7 +8,7 @@ module Bunto
8
8
  # forms: name, name=value, or name="<quoted list>"
9
9
  #
10
10
  # <quoted list> is a space-separated list of numbers
11
- SYNTAX = %r!^([a-zA-Z0-9.+#-]+)((\s+\w+(=(\w+|"([0-9]+\s)*[0-9]+"))?)*)$!
11
+ SYNTAX = %r!^([a-zA-Z0-9.+#_-]+)((\s+\w+(=(\w+|"([0-9]+\s)*[0-9]+"))?)*)$!
12
12
 
13
13
  def initialize(tag_name, markup, tokens)
14
14
  super
@@ -54,7 +54,7 @@ eos
54
54
  [:hl_lines, opts.fetch(:hl_lines, nil)],
55
55
  [:linenos, opts.fetch(:linenos, nil)],
56
56
  [:encoding, opts.fetch(:encoding, "utf-8")],
57
- [:cssclass, opts.fetch(:cssclass, nil)]
57
+ [:cssclass, opts.fetch(:cssclass, nil)],
58
58
  ].reject { |f| f.last.nil? }]
59
59
  else
60
60
  opts
@@ -125,7 +125,7 @@ eos
125
125
  def add_code_tag(code)
126
126
  code_attributes = [
127
127
  "class=\"language-#{@lang.to_s.tr("+", "-")}\"",
128
- "data-lang=\"#{@lang}\""
128
+ "data-lang=\"#{@lang}\"",
129
129
  ].join(" ")
130
130
  "<figure class=\"highlight\"><pre><code #{code_attributes}>"\
131
131
  "#{code.chomp}</code></pre></figure>"
@@ -112,8 +112,8 @@ eos
112
112
  def locate_include_file(context, file, safe)
113
113
  includes_dirs = tag_includes_dirs(context)
114
114
  includes_dirs.each do |dir|
115
- path = File.join(dir, file)
116
- return path if valid_include_file?(path, dir, safe)
115
+ path = File.join(dir.to_s, file.to_s)
116
+ return path if valid_include_file?(path, dir.to_s, safe)
117
117
  end
118
118
  raise IOError, "Could not locate the included file '#{file}' in any of "\
119
119
  "#{includes_dirs}. Ensure it exists in one of those directories and, "\
@@ -155,15 +155,19 @@ eos
155
155
  if cached_partial.key?(path)
156
156
  cached_partial[path]
157
157
  else
158
- cached_partial[path] = context.registers[:site]
158
+ unparsed_file = context.registers[:site]
159
159
  .liquid_renderer
160
160
  .file(path)
161
- .parse(read_file(path, context))
161
+ begin
162
+ cached_partial[path] = unparsed_file.parse(read_file(path, context))
163
+ rescue Liquid::SyntaxError => ex
164
+ raise IncludeTagError.new(ex.message, path)
165
+ end
162
166
  end
163
167
  end
164
168
 
165
169
  def valid_include_file?(path, dir, safe)
166
- !(outside_site_source?(path, dir, safe) || !File.exist?(path))
170
+ !outside_site_source?(path, dir, safe) && File.file?(path)
167
171
  end
168
172
 
169
173
  def outside_site_source?(path, dir, safe)
@@ -16,8 +16,10 @@ module Bunto
16
16
  def render(context)
17
17
  site = context.registers[:site]
18
18
 
19
- site.docs_to_write.each do |document|
20
- return document.url if document.relative_path == @relative_path
19
+ site.each_site_file do |item|
20
+ return item.url if item.relative_path == @relative_path
21
+ # This takes care of the case for static files that have a leading /
22
+ return item.url if item.relative_path == "/#{@relative_path}"
21
23
  end
22
24
 
23
25
  raise ArgumentError, <<eos
@@ -14,7 +14,9 @@ module Bunto
14
14
  "'#{name}' does not contain valid date and/or title."
15
15
  end
16
16
 
17
- @name_regex = %r!^#{path}#{date}-#{slug}\.[^.]+!
17
+ escaped_slug = Regexp.escape(slug)
18
+ @name_regex = %r!^_posts/#{path}#{date}-#{escaped_slug}\.[^.]+|
19
+ ^#{path}_posts/?#{date}-#{escaped_slug}\.[^.]+!x
18
20
  end
19
21
 
20
22
  def post_date
@@ -23,7 +25,7 @@ module Bunto
23
25
  end
24
26
 
25
27
  def ==(other)
26
- other.basename.match(@name_regex)
28
+ other.relative_path.match(@name_regex)
27
29
  end
28
30
 
29
31
  def deprecated_equality(other)
@@ -18,15 +18,19 @@ module Bunto
18
18
  end
19
19
 
20
20
  def includes_path
21
- path_for :includes
21
+ path_for "_includes".freeze
22
22
  end
23
23
 
24
24
  def layouts_path
25
- path_for :layouts
25
+ path_for "_layouts".freeze
26
26
  end
27
27
 
28
28
  def sass_path
29
- path_for :sass
29
+ path_for "_sass".freeze
30
+ end
31
+
32
+ def assets_path
33
+ path_for "assets".freeze
30
34
  end
31
35
 
32
36
  def configure_sass
@@ -43,7 +47,7 @@ module Bunto
43
47
  end
44
48
 
45
49
  def realpath_for(folder)
46
- File.realpath(Bunto.sanitized_path(root, "_#{folder}"))
50
+ File.realpath(Bunto.sanitized_path(root, folder.to_s))
47
51
  rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
48
52
  nil
49
53
  end