bunto 3.2.1 → 3.4.5

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