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
@@ -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