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
@@ -35,7 +35,11 @@ module Bunto
35
35
  read_content(dir, magic_dir, matcher).tap do |docs|
36
36
  docs.each(&:read)
37
37
  end.select do |doc|
38
- site.publisher.publish?(doc)
38
+ site.publisher.publish?(doc).tap do |will_publish|
39
+ if !will_publish && site.publisher.hidden_in_the_future?(doc)
40
+ Bunto.logger.debug "Skipping:", "#{doc.relative_path} has a future date"
41
+ end
42
+ end
39
43
  end
40
44
  end
41
45
 
@@ -1,6 +1,8 @@
1
1
  module Bunto
2
2
  class Regenerator
3
3
  attr_reader :site, :metadata, :cache
4
+ attr_accessor :disabled
5
+ private :disabled, :disabled=
4
6
 
5
7
  def initialize(site)
6
8
  @site = site
@@ -115,7 +117,7 @@ module Bunto
115
117
  #
116
118
  # Returns nothing.
117
119
  def add_dependency(path, dependency)
118
- return if metadata[path].nil? || @disabled
120
+ return if metadata[path].nil? || disabled
119
121
 
120
122
  unless metadata[path]["deps"].include? dependency
121
123
  metadata[path]["deps"] << dependency
@@ -144,8 +146,8 @@ module Bunto
144
146
  #
145
147
  # Returns a Boolean (true for disabled, false for enabled).
146
148
  def disabled?
147
- @disabled = !site.incremental? if @disabled.nil?
148
- @disabled
149
+ self.disabled = !site.incremental? if disabled.nil?
150
+ disabled
149
151
  end
150
152
 
151
153
  private
@@ -40,6 +40,8 @@ module Bunto
40
40
 
41
41
  if document.is_a?(Document) && document.collection.label == 'posts'
42
42
  payload['site']['related_posts'] = document.related_posts
43
+ else
44
+ payload['site']['related_posts'] = nil
43
45
  end
44
46
 
45
47
  # render and transform content (this becomes the final content of the object)
@@ -50,7 +52,6 @@ module Bunto
50
52
  document.trigger_hooks(:pre_render, payload)
51
53
 
52
54
  info = {
53
- :filters => [Bunto::Filters],
54
55
  :registers => { :site => site, :page => payload['page'] }
55
56
  }
56
57
 
@@ -144,7 +145,7 @@ module Bunto
144
145
  layout.content,
145
146
  payload,
146
147
  info,
147
- File.join(site.config['layouts_dir'], layout.name)
148
+ layout.relative_path
148
149
  )
149
150
 
150
151
  # Add layout to dependency tree
@@ -8,15 +8,40 @@ module Bunto
8
8
  :exclude, :include, :lsi, :highlighter, :permalink_style,
9
9
  :time, :future, :unpublished, :safe, :plugins, :limit_posts,
10
10
  :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
11
- :gems, :plugin_manager
11
+ :gems, :plugin_manager, :theme
12
12
 
13
13
  attr_accessor :converters, :generators, :reader
14
- attr_reader :regenerator, :liquid_renderer
14
+ attr_reader :regenerator, :liquid_renderer, :includes_load_paths
15
15
 
16
16
  # Public: Initialize a new Site.
17
17
  #
18
18
  # config - A Hash containing site configuration details.
19
19
  def initialize(config)
20
+ # Source and destination may not be changed after the site has been created.
21
+ @source = File.expand_path(config['source']).freeze
22
+ @dest = File.expand_path(config['destination']).freeze
23
+
24
+ self.config = config
25
+
26
+ @reader = Reader.new(self)
27
+ @regenerator = Regenerator.new(self)
28
+ @liquid_renderer = LiquidRenderer.new(self)
29
+
30
+ Bunto.sites << self
31
+
32
+ Bunto::Hooks.trigger :site, :after_init, self
33
+
34
+ reset
35
+ setup
36
+ end
37
+
38
+ # Public: Set the site's configuration. This handles side-effects caused by
39
+ # changing values in the configuration.
40
+ #
41
+ # config - a Bunto::Configuration, containing the new configuration.
42
+ #
43
+ # Returns the new configuration.
44
+ def config=(config)
20
45
  @config = config.clone
21
46
 
22
47
  %w(safe lsi highlighter baseurl exclude include future unpublished
@@ -24,29 +49,21 @@ module Bunto
24
49
  self.send("#{opt}=", config[opt])
25
50
  end
26
51
 
27
- # Source and destination may not be changed after the site has been created.
28
- @source = File.expand_path(config['source']).freeze
29
- @dest = File.expand_path(config['destination']).freeze
30
-
31
- @reader = Bunto::Reader.new(self)
32
-
33
- # Initialize incremental regenerator
34
- @regenerator = Regenerator.new(self)
35
-
36
- @liquid_renderer = LiquidRenderer.new(self)
37
-
38
52
  self.plugin_manager = Bunto::PluginManager.new(self)
39
53
  self.plugins = plugin_manager.plugins_path
40
54
 
55
+ self.theme = nil
56
+ self.theme = Bunto::Theme.new(config["theme"]) if config["theme"]
57
+
58
+ @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
59
+ @includes_load_paths << theme.includes_path if self.theme
60
+
41
61
  self.file_read_opts = {}
42
62
  self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
43
63
 
44
64
  self.permalink_style = config['permalink'].to_sym
45
65
 
46
- Bunto.sites << self
47
-
48
- reset
49
- setup
66
+ @config
50
67
  end
51
68
 
52
69
  # Public: Read, process, and write this Site to output.
@@ -356,6 +373,19 @@ module Bunto
356
373
  end
357
374
  end
358
375
 
376
+ # Public: Prefix a given path with the theme directory.
377
+ #
378
+ # paths - (optional) path elements to a file or directory within the
379
+ # theme directory
380
+ #
381
+ # Returns a path which is prefixed with the theme root directory.
382
+ def in_theme_dir(*paths)
383
+ return nil unless theme
384
+ paths.reduce(theme.root) do |base, path|
385
+ Bunto.sanitized_path(base, path)
386
+ end
387
+ end
388
+
359
389
  # Public: Prefix a given path with the destination directory.
360
390
  #
361
391
  # paths - (optional) path elements to a file or directory within the
@@ -80,7 +80,11 @@ module Bunto
80
80
 
81
81
  FileUtils.mkdir_p(File.dirname(dest_path))
82
82
  FileUtils.rm(dest_path) if File.exist?(dest_path)
83
- FileUtils.cp(path, dest_path)
83
+ if @site.safe || Bunto.env == "production"
84
+ FileUtils.cp(path, dest_path)
85
+ else
86
+ FileUtils.copy_entry(path, dest_path)
87
+ end
84
88
  File.utime(@@mtimes[path], @@mtimes[path], dest_path)
85
89
 
86
90
  true
@@ -12,8 +12,6 @@ module Bunto
12
12
  end
13
13
 
14
14
  class IncludeTag < Liquid::Tag
15
- attr_reader :includes_dir
16
-
17
15
  VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
18
16
  VARIABLE_SYNTAX = /(?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?<params>.*)/
19
17
 
@@ -98,20 +96,29 @@ eos
98
96
  end
99
97
  end
100
98
 
101
- def tag_includes_dir(context)
102
- context.registers[:site].config['includes_dir'].freeze
99
+ def tag_includes_dirs(context)
100
+ context.registers[:site].includes_load_paths.freeze
101
+ end
102
+
103
+ def locate_include_file(context, file, safe)
104
+ includes_dirs = tag_includes_dirs(context)
105
+ includes_dirs.each do |dir|
106
+ path = File.join(dir, file)
107
+ return path if valid_include_file?(path, dir, safe)
108
+ end
109
+ raise IOError, "Could not locate the included file '#{file}' in any of #{includes_dirs}." \
110
+ " Ensure it exists in one of those directories and, if it is a symlink, " \
111
+ "does not point outside your site source."
103
112
  end
104
113
 
105
114
  def render(context)
106
115
  site = context.registers[:site]
107
- @includes_dir = tag_includes_dir(context)
108
- dir = resolved_includes_dir(context)
109
116
 
110
117
  file = render_variable(context) || @file
111
118
  validate_file_name(file)
112
119
 
113
- path = File.join(dir, file)
114
- validate_path(path, dir, site.safe)
120
+ path = locate_include_file(context, file, site.safe)
121
+ return unless path
115
122
 
116
123
  # Add include to dependency tree
117
124
  if context.registers[:page] && context.registers[:page].key?("path")
@@ -121,16 +128,16 @@ eos
121
128
  )
122
129
  end
123
130
 
124
- begin
131
+ #begin
125
132
  partial = load_cached_partial(path, context)
126
133
 
127
134
  context.stack do
128
135
  context['include'] = parse_params(context) if @params
129
136
  partial.render!(context)
130
137
  end
131
- rescue => e
132
- raise IncludeTagError.new e.message, File.join(@includes_dir, @file)
133
- end
138
+ #rescue => e
139
+ #raise IncludeTagError.new e.message, path
140
+ #end
134
141
  end
135
142
 
136
143
  def load_cached_partial(path, context)
@@ -144,24 +151,18 @@ eos
144
151
  end
145
152
  end
146
153
 
147
- def resolved_includes_dir(context)
148
- context.registers[:site].in_source_dir(@includes_dir)
149
- end
150
-
151
- def validate_path(path, dir, safe)
152
- if safe && !realpath_prefixed_with?(path, dir)
153
- raise IOError.new "The included file '#{path}' should exist and should not be a symlink"
154
- elsif !File.exist?(path)
155
- raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found"
156
- end
154
+ def valid_include_file?(path, dir, safe)
155
+ !(outside_site_source?(path, dir, safe) || !File.exist?(path))
157
156
  end
158
157
 
159
- def path_relative_to_source(dir, path)
160
- File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), ""))
158
+ def outside_site_source?(path, dir, safe)
159
+ safe && !realpath_prefixed_with?(path, dir)
161
160
  end
162
161
 
163
162
  def realpath_prefixed_with?(path, dir)
164
163
  File.exist?(path) && File.realpath(path).start_with?(dir)
164
+ rescue
165
+ false
165
166
  end
166
167
 
167
168
  # This method allows to modify the file content by inheriting from the class.
@@ -171,16 +172,17 @@ eos
171
172
  end
172
173
 
173
174
  class IncludeRelativeTag < IncludeTag
174
- def tag_includes_dir(context)
175
- '.'.freeze
175
+ def tag_includes_dirs(context)
176
+ Array(page_path(context)).freeze
176
177
  end
177
178
 
178
179
  def page_path(context)
179
- context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"])
180
- end
181
-
182
- def resolved_includes_dir(context)
183
- context.registers[:site].in_source_dir(page_path(context))
180
+ if context.registers[:page].nil?
181
+ context.registers[:site].source
182
+ else
183
+ current_doc_dir = File.dirname(context.registers[:page]["path"])
184
+ context.registers[:site].in_source_dir current_doc_dir
185
+ end
184
186
  end
185
187
  end
186
188
  end
@@ -0,0 +1,26 @@
1
+ module Bunto
2
+ module Tags
3
+ class Link < Liquid::Tag
4
+ TagName = 'link'
5
+
6
+ def initialize(tag_name, relative_path, tokens)
7
+ super
8
+
9
+ @relative_path = relative_path.strip
10
+ end
11
+
12
+ def render(context)
13
+ site = context.registers[:site]
14
+
15
+ site.docs_to_write.each do |document|
16
+ return document.url if document.relative_path == @relative_path
17
+ end
18
+
19
+ raise ArgumentError, "Could not find document '#{@relative_path}' in tag '#{TagName}'.\n\n" \
20
+ "Make sure the document exists and the path is correct."
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Liquid::Template.register_tag(Bunto::Tags::Link::TagName, Bunto::Tags::Link)
@@ -7,22 +7,30 @@ module Bunto
7
7
 
8
8
  def initialize(name)
9
9
  @name = name
10
+
10
11
  all, @path, @date, @slug = *name.sub(/^\//, "").match(MATCHER)
11
- raise ArgumentError.new("'#{name}' does not contain valid date and/or title.") unless all
12
+ unless all
13
+ raise Bunto::Errors::InvalidPostNameError,
14
+ "'#{name}' does not contain valid date and/or title."
15
+ end
12
16
 
13
17
  @name_regex = /^#{path}#{date}-#{slug}\.[^.]+/
14
18
  end
15
19
 
20
+ def post_date
21
+ @post_date ||= Utils.parse_date(date,
22
+ "\"#{date}\" does not contain valid date and/or title.")
23
+ end
24
+
16
25
  def ==(other)
17
26
  other.basename.match(@name_regex)
18
27
  end
19
28
 
20
29
  def deprecated_equality(other)
21
- date = Utils.parse_date(name, "'#{name}' does not contain valid date and/or title.")
22
30
  slug == post_slug(other) &&
23
- date.year == other.date.year &&
24
- date.month == other.date.month &&
25
- date.day == other.date.day
31
+ post_date.year == other.date.year &&
32
+ post_date.month == other.date.month &&
33
+ post_date.day == other.date.day
26
34
  end
27
35
 
28
36
  private
@@ -47,11 +55,13 @@ module Bunto
47
55
  @orig_post = post.strip
48
56
  begin
49
57
  @post = PostComparer.new(@orig_post)
50
- rescue
51
- raise ArgumentError.new <<-eos
58
+ rescue => e
59
+ raise Bunto::Errors::PostURLError, <<-eos
52
60
  Could not parse name of post "#{@orig_post}" in tag 'post_url'.
53
61
 
54
62
  Make sure the post exists and the name is correct.
63
+
64
+ #{e.class}: #{e.message}
55
65
  eos
56
66
  end
57
67
  end
@@ -75,7 +85,7 @@ eos
75
85
  return p.url
76
86
  end
77
87
 
78
- raise ArgumentError.new <<-eos
88
+ raise Bunto::Errors::PostURLError, <<-eos
79
89
  Could not find post "#{@orig_post}" in tag 'post_url'.
80
90
 
81
91
  Make sure the post exists and the name is correct.
@@ -0,0 +1,56 @@
1
+ module Bunto
2
+ class Theme
3
+ extend Forwardable
4
+ attr_reader :name
5
+ def_delegator :gemspec, :version, :version
6
+
7
+ def initialize(name)
8
+ @name = name.downcase.strip
9
+ configure_sass
10
+ end
11
+
12
+ def root
13
+ @root ||= gemspec.full_gem_path
14
+ end
15
+
16
+ def includes_path
17
+ path_for :includes
18
+ end
19
+
20
+ def layouts_path
21
+ path_for :layouts
22
+ end
23
+
24
+ def sass_path
25
+ path_for :sass
26
+ end
27
+
28
+ def configure_sass
29
+ return unless sass_path
30
+ require 'sass'
31
+ Sass.load_paths << sass_path
32
+ end
33
+
34
+ private
35
+
36
+ def path_for(folder)
37
+ resolved_dir = realpath_for(folder)
38
+ return unless resolved_dir
39
+
40
+ path = Bunto.sanitized_path(root, resolved_dir)
41
+ path if Dir.exists?(path)
42
+ end
43
+
44
+ def realpath_for(folder)
45
+ File.realpath(Bunto.sanitized_path(root, "_#{folder}"))
46
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
47
+ nil
48
+ end
49
+
50
+ def gemspec
51
+ @gemspec ||= Gem::Specification.find_by_name(name)
52
+ rescue Gem::LoadError
53
+ raise Bunto::Errors::MissingDependencyException, "The #{name} theme could not be found."
54
+ end
55
+ end
56
+ end
@@ -1,3 +1,4 @@
1
+
1
2
  module Bunto
2
3
  module Utils
3
4
  extend self
@@ -20,7 +21,7 @@ module Bunto
20
21
 
21
22
  def titleize_slug(slug)
22
23
  slug.split("-").map! do |val|
23
- val.capitalize!
24
+ val.capitalize
24
25
  end.join(" ")
25
26
  end
26
27
 
@@ -126,7 +127,7 @@ module Bunto
126
127
  def parse_date(input, msg = "Input could not be parsed.")
127
128
  Time.parse(input).localtime
128
129
  rescue ArgumentError
129
- raise Errors::FatalException.new("Invalid date '#{input}': " + msg)
130
+ raise Errors::InvalidDateError, "Invalid date '#{input}': #{msg}"
130
131
  end
131
132
 
132
133
  # Determines whether a given file has