bridgetown-core 0.13.0 → 0.15.0.beta3

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/bin/bridgetown +9 -48
  4. data/bridgetown-core.gemspec +6 -2
  5. data/lib/bridgetown-core.rb +13 -3
  6. data/lib/bridgetown-core/cleaner.rb +1 -0
  7. data/lib/bridgetown-core/commands/apply.rb +73 -0
  8. data/lib/bridgetown-core/commands/base.rb +45 -0
  9. data/lib/bridgetown-core/commands/build.rb +91 -86
  10. data/lib/bridgetown-core/commands/clean.rb +30 -29
  11. data/lib/bridgetown-core/commands/concerns/actions.rb +123 -0
  12. data/lib/bridgetown-core/commands/concerns/build_options.rb +76 -0
  13. data/lib/bridgetown-core/commands/concerns/configuration_overridable.rb +18 -0
  14. data/lib/bridgetown-core/commands/concerns/summarizable.rb +13 -0
  15. data/lib/bridgetown-core/commands/console.rb +46 -39
  16. data/lib/bridgetown-core/commands/doctor.rb +126 -127
  17. data/lib/bridgetown-core/commands/new.rb +120 -158
  18. data/lib/bridgetown-core/commands/plugins.rb +206 -0
  19. data/lib/bridgetown-core/commands/registrations.rb +16 -0
  20. data/lib/bridgetown-core/commands/serve.rb +214 -215
  21. data/lib/bridgetown-core/{convertible.rb → concerns/convertible.rb} +3 -6
  22. data/lib/bridgetown-core/concerns/site/configurable.rb +153 -0
  23. data/lib/bridgetown-core/concerns/site/content.rb +111 -0
  24. data/lib/bridgetown-core/concerns/site/extensible.rb +56 -0
  25. data/lib/bridgetown-core/concerns/site/processable.rb +74 -0
  26. data/lib/bridgetown-core/concerns/site/renderable.rb +49 -0
  27. data/lib/bridgetown-core/concerns/site/writable.rb +31 -0
  28. data/lib/bridgetown-core/configuration.rb +2 -9
  29. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +0 -3
  30. data/lib/bridgetown-core/document.rb +1 -1
  31. data/lib/bridgetown-core/drops/page_drop.rb +1 -1
  32. data/lib/bridgetown-core/drops/site_drop.rb +1 -1
  33. data/lib/bridgetown-core/excerpt.rb +4 -1
  34. data/lib/bridgetown-core/external.rb +17 -21
  35. data/lib/bridgetown-core/filters.rb +10 -0
  36. data/lib/bridgetown-core/generators/prototype_generator.rb +3 -1
  37. data/lib/bridgetown-core/hooks.rb +62 -62
  38. data/lib/bridgetown-core/layout.rb +10 -4
  39. data/lib/bridgetown-core/liquid_renderer.rb +1 -0
  40. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -4
  41. data/lib/bridgetown-core/liquid_renderer/file_system.rb +3 -1
  42. data/lib/bridgetown-core/page.rb +11 -19
  43. data/lib/bridgetown-core/plugin.rb +2 -0
  44. data/lib/bridgetown-core/plugin_manager.rb +88 -21
  45. data/lib/bridgetown-core/reader.rb +5 -0
  46. data/lib/bridgetown-core/readers/data_reader.rb +5 -2
  47. data/lib/bridgetown-core/readers/layout_reader.rb +9 -2
  48. data/lib/bridgetown-core/readers/plugin_content_reader.rb +48 -0
  49. data/lib/bridgetown-core/renderer.rb +38 -28
  50. data/lib/bridgetown-core/site.rb +20 -463
  51. data/lib/bridgetown-core/tags/include.rb +12 -0
  52. data/lib/bridgetown-core/tags/render_content.rb +29 -16
  53. data/lib/bridgetown-core/tags/with.rb +15 -0
  54. data/lib/bridgetown-core/utils.rb +45 -27
  55. data/lib/bridgetown-core/utils/ruby_exec.rb +1 -4
  56. data/lib/bridgetown-core/version.rb +2 -2
  57. data/lib/bridgetown-core/watcher.rb +21 -10
  58. data/lib/site_template/Gemfile.erb +19 -0
  59. data/lib/site_template/package.json +1 -0
  60. data/lib/site_template/plugins/{.keep → builders/.keep} +0 -0
  61. data/lib/site_template/plugins/site_builder.rb +4 -0
  62. data/lib/site_template/src/_components/footer.html +3 -0
  63. data/lib/site_template/src/_components/head.html +9 -0
  64. data/lib/site_template/src/{_includes → _components}/navbar.html +1 -0
  65. data/lib/site_template/src/_layouts/default.html +3 -3
  66. data/lib/site_template/src/posts.md +15 -0
  67. data/lib/site_template/start.js +1 -1
  68. data/lib/site_template/webpack.config.js +3 -3
  69. metadata +90 -18
  70. data/lib/bridgetown-core/command.rb +0 -106
  71. data/lib/bridgetown-core/commands/help.rb +0 -34
  72. data/lib/site_template/src/_components/.keep +0 -0
  73. data/lib/site_template/src/_includes/footer.html +0 -3
  74. data/lib/site_template/src/_includes/head.html +0 -9
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Site::Writable
5
+ # Remove orphaned files and empty directories in destination.
6
+ #
7
+ # Returns nothing.
8
+ def cleanup
9
+ @cleaner.cleanup!
10
+ end
11
+
12
+ # Write static files, pages, and posts.
13
+ #
14
+ # Returns nothing.
15
+ def write
16
+ each_site_file do |item|
17
+ item.write(dest) if regenerator.regenerate?(item)
18
+ end
19
+ regenerator.write_metadata
20
+ Bridgetown::Hooks.trigger :site, :post_write, self
21
+ end
22
+
23
+ def each_site_file
24
+ %w(pages static_files docs_to_write).each do |type|
25
+ send(type).each do |item|
26
+ yield item
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,7 +4,7 @@ module Bridgetown
4
4
  # TODO: refactor this whole object! Already had to fix obscure
5
5
  # bugs just making minor changes, and all the indirection is
6
6
  # quite hard to decipher. -JW
7
- class Configuration < Hash
7
+ class Configuration < ActiveSupport::HashWithIndifferentAccess
8
8
  # Default options. Overridden by values in bridgetown.config.yml.
9
9
  # Strings rather than symbols are used for compatibility with YAML.
10
10
  DEFAULTS = {
@@ -91,7 +91,7 @@ module Bridgetown
91
91
  #
92
92
  # Returns a Configuration filled with defaults.
93
93
  def from(user_config)
94
- Utils.deep_merge_hashes(DEFAULTS, Configuration[user_config].stringify_keys)
94
+ Utils.deep_merge_hashes(DEFAULTS, Configuration[user_config])
95
95
  .merge_environment_specific_options!
96
96
  .add_default_collections
97
97
  .add_default_excludes
@@ -99,13 +99,6 @@ module Bridgetown
99
99
  end
100
100
  end
101
101
 
102
- # Public: Turn all keys into string
103
- #
104
- # Return a copy of the hash where all its keys are strings
105
- def stringify_keys
106
- each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v }
107
- end
108
-
109
102
  def get_config_value_with_override(config_key, override)
110
103
  override[config_key] || self[config_key] || DEFAULTS[config_key]
111
104
  end
@@ -11,8 +11,6 @@ module Kramdown
11
11
  attr_reader :options, :parser
12
12
 
13
13
  # The implementation is basically the core logic in +Kramdown::Document#initialize+
14
- #
15
- # rubocop:disable Naming/MemoizedInstanceVariableName
16
14
  def setup(options)
17
15
  @cache ||= {}
18
16
 
@@ -36,7 +34,6 @@ module Kramdown
36
34
  end
37
35
  end
38
36
  end
39
- # rubocop:enable Naming/MemoizedInstanceVariableName
40
37
 
41
38
  private
42
39
 
@@ -59,7 +59,7 @@ module Bridgetown
59
59
  # Returns a Hash containing the data. An empty hash is returned if
60
60
  # no data was read.
61
61
  def data
62
- @data ||= {}
62
+ @data ||= ActiveSupport::HashWithIndifferentAccess.new
63
63
  end
64
64
 
65
65
  # Merge some data in with this document's data.
@@ -7,7 +7,7 @@ module Bridgetown
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegators :@obj, :content, :dir, :name, :path, :url
10
+ def_delegators :@obj, :content, :dir, :name, :path, :url, :pager
11
11
  private def_delegator :@obj, :data, :fallback_data
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module Bridgetown
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegator :@obj, :site_data, :data
10
+ def_delegator :@obj, :data
11
11
  def_delegators :@obj, :time, :pages, :static_files, :tags, :categories
12
12
 
13
13
  private def_delegator :@obj, :config, :fallback_data
@@ -81,7 +81,10 @@ module Bridgetown
81
81
  end
82
82
 
83
83
  def output
84
- @output ||= Renderer.new(doc.site, self, site.site_payload).run
84
+ @output || (
85
+ Renderer.new(doc.site, self, site.site_payload).run
86
+ @output
87
+ )
85
88
  end
86
89
 
87
90
  def place_in_layout?
@@ -10,13 +10,11 @@ module Bridgetown
10
10
  #
11
11
  def require_if_present(names)
12
12
  Array(names).each do |name|
13
- begin
14
- require name
15
- rescue LoadError
16
- Bridgetown.logger.debug "Couldn't load #{name}. Skipping."
17
- yield(name, version_constraint(name)) if block_given?
18
- false
19
- end
13
+ require name
14
+ rescue LoadError
15
+ Bridgetown.logger.debug "Couldn't load #{name}. Skipping."
16
+ yield(name, version_constraint(name)) if block_given?
17
+ false
20
18
  end
21
19
  end
22
20
 
@@ -38,23 +36,21 @@ module Bridgetown
38
36
  #
39
37
  def require_with_graceful_fail(names)
40
38
  Array(names).each do |name|
41
- begin
42
- Bridgetown.logger.debug "Requiring:", name.to_s
43
- require name
44
- rescue LoadError => e
45
- Bridgetown.logger.error "Dependency Error:", <<~MSG
46
- Yikes! It looks like you don't have #{name} or one of its dependencies installed.
47
- In order to use Bridgetown as currently configured, you'll need to install this gem.
39
+ Bridgetown.logger.debug "Requiring:", name.to_s
40
+ require name
41
+ rescue LoadError => e
42
+ Bridgetown.logger.error "Dependency Error:", <<~MSG
43
+ Yikes! It looks like you don't have #{name} or one of its dependencies installed.
44
+ In order to use Bridgetown as currently configured, you'll need to install this gem.
48
45
 
49
- If you've run Bridgetown with `bundle exec`, ensure that you have included the #{name}
50
- gem in your Gemfile as well.
46
+ If you've run Bridgetown with `bundle exec`, ensure that you have included the #{name}
47
+ gem in your Gemfile as well.
51
48
 
52
- The full error message from Ruby is: '#{e.message}'
49
+ The full error message from Ruby is: '#{e.message}'
53
50
 
54
- If you run into trouble, you can find helpful resources at https://bridgetownrb.com/help/!
55
- MSG
56
- raise Bridgetown::Errors::MissingDependencyException, name
57
- end
51
+ If you run into trouble, you can find helpful resources at https://www.bridgetownrb.com/docs/community/
52
+ MSG
53
+ raise Bridgetown::Errors::MissingDependencyException, name
58
54
  end
59
55
  end
60
56
  end
@@ -63,6 +63,16 @@ module Bridgetown
63
63
  Utils.slugify(input, mode: mode)
64
64
  end
65
65
 
66
+ # Titleize a slug or identifier string.
67
+ #
68
+ # input - The string to titleize.
69
+ #
70
+ # Returns a transformed string with spaces and capitalized words.
71
+ # See Utils.titleize_slug for more detail.
72
+ def titleize(input)
73
+ Utils.titleize_slug(input)
74
+ end
75
+
66
76
  # XML escape a string for use. Replaces any special characters with
67
77
  # appropriate HTML entity replacements.
68
78
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Bridgetown::Hooks.register :pages, :post_init do |page|
3
+ Bridgetown::Hooks.register :pages, :post_init, reloadable: false do |page|
4
4
  if page.class != Bridgetown::PrototypePage && page.data["prototype"].is_a?(Hash)
5
5
  Bridgetown::PrototypeGenerator.add_matching_template(page)
6
6
  end
@@ -63,6 +63,8 @@ module Bridgetown
63
63
  new_page
64
64
  end
65
65
 
66
+ # TODO: this would be a great use of .try
67
+ # document.try(:collection).try(:label) == @configured_collection
66
68
  def terms_matching_pages(search_term)
67
69
  selected_docs = @site.documents.select do |document|
68
70
  document.respond_to?(:collection) && document.collection.label == @configured_collection
@@ -2,6 +2,19 @@
2
2
 
3
3
  module Bridgetown
4
4
  module Hooks
5
+ HookRegistration = Struct.new(
6
+ :owner,
7
+ :event,
8
+ :priority,
9
+ :reloadable,
10
+ :block,
11
+ keyword_init: true
12
+ ) do
13
+ def to_s
14
+ "#{owner}:#{event} for #{block}"
15
+ end
16
+ end
17
+
5
18
  DEFAULT_PRIORITY = 20
6
19
 
7
20
  # compatibility layer for octopress-hooks users
@@ -12,51 +25,11 @@ module Bridgetown
12
25
  }.freeze
13
26
 
14
27
  # initial empty hooks
15
- @registry = {
16
- site: {
17
- after_init: [],
18
- after_reset: [],
19
- post_read: [],
20
- pre_render: [],
21
- post_render: [],
22
- post_write: [],
23
- },
24
- pages: {
25
- post_init: [],
26
- pre_render: [],
27
- post_render: [],
28
- post_write: [],
29
- },
30
- posts: {
31
- post_init: [],
32
- pre_render: [],
33
- post_render: [],
34
- post_write: [],
35
- },
36
- documents: {
37
- post_init: [],
38
- pre_render: [],
39
- post_render: [],
40
- post_write: [],
41
- },
42
- clean: {
43
- on_obsolete: [],
44
- },
45
- }
46
-
47
- # map of all hooks and their priorities
48
- @hook_priority = {}
28
+ @registry = {}
49
29
 
50
30
  NotAvailable = Class.new(RuntimeError)
51
31
  Uncallable = Class.new(RuntimeError)
52
32
 
53
- # register hook(s) to be called later, public API
54
- def self.register(owners, event, priority: DEFAULT_PRIORITY, &block)
55
- Array(owners).each do |owner|
56
- register_one(owner, event, priority_value(priority), &block)
57
- end
58
- end
59
-
60
33
  # Ensure the priority is a Fixnum
61
34
  def self.priority_value(priority)
62
35
  return priority if priority.is_a?(Integer)
@@ -64,39 +37,66 @@ module Bridgetown
64
37
  PRIORITY_MAP[priority] || DEFAULT_PRIORITY
65
38
  end
66
39
 
67
- # register a single hook to be called later, internal API
68
- def self.register_one(owner, event, priority, &block)
69
- @registry[owner] ||= {
70
- post_init: [],
71
- pre_render: [],
72
- post_render: [],
73
- post_write: [],
74
- }
75
-
76
- unless @registry[owner][event]
77
- raise NotAvailable, "Invalid hook. #{owner} supports only the " \
78
- "following hooks #{@registry[owner].keys.inspect}"
40
+ # register hook(s) to be called later
41
+ def self.register(owners, event, priority: DEFAULT_PRIORITY, reloadable: true, &block)
42
+ Array(owners).each do |owner|
43
+ register_one(owner, event, priority: priority, reloadable: reloadable, &block)
79
44
  end
45
+ end
46
+
47
+ # register a single hook to be called later
48
+ def self.register_one(owner, event, priority: DEFAULT_PRIORITY, reloadable: true, &block)
49
+ @registry[owner] ||= []
80
50
 
81
51
  raise Uncallable, "Hooks must respond to :call" unless block.respond_to? :call
82
52
 
83
- insert_hook owner, event, priority, &block
53
+ @registry[owner] << HookRegistration.new(
54
+ owner: owner,
55
+ event: event,
56
+ priority: priority_value(priority),
57
+ reloadable: reloadable,
58
+ block: block
59
+ )
60
+ if ENV["BRIDGETOWN_LOG_LEVEL"] == "debug"
61
+ if Bridgetown.respond_to?(:logger)
62
+ Bridgetown.logger.debug("Registering hook:", @registry[owner].last.to_s)
63
+ else
64
+ p "Registering hook:", @registry[owner].last.to_s
65
+ end
66
+ end
67
+
68
+ block
84
69
  end
85
70
 
86
- def self.insert_hook(owner, event, priority, &block)
87
- @hook_priority[block] = [-priority, @hook_priority.size]
88
- @registry[owner][event] << block
71
+ def self.remove_hook(owner, _event, block)
72
+ @registry[owner].delete_if { |item| item.block == block }
89
73
  end
90
74
 
91
- # interface for Bridgetown core components to trigger hooks
92
75
  def self.trigger(owner, event, *args)
93
76
  # proceed only if there are hooks to call
94
- hooks = @registry.dig(owner, event)
77
+ hooks = @registry[owner]&.select { |item| item.event == event }
95
78
  return if hooks.nil? || hooks.empty?
96
79
 
97
- # sort and call hooks according to priority and load order
98
- hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
99
- hook.call(*args)
80
+ prioritized_hooks(hooks).each do |hook|
81
+ if ENV["BRIDGETOWN_LOG_LEVEL"] == "debug"
82
+ hook_info = args[0]&.respond_to?(:url) ? args[0].relative_path : hook.block
83
+ Bridgetown.logger.debug("Triggering hook:", "#{owner}:#{event} for #{hook_info}")
84
+ end
85
+ hook.block.call(*args)
86
+ end
87
+ end
88
+
89
+ def self.prioritized_hooks(hooks)
90
+ # sort hooks according to priority and load order
91
+ grouped_hooks = hooks.group_by(&:priority)
92
+ grouped_hooks.keys.sort.reverse.map { |priority| grouped_hooks[priority] }.flatten
93
+ end
94
+
95
+ def self.clear_reloadable_hooks
96
+ Bridgetown.logger.debug("Clearing reloadable hooks")
97
+
98
+ @registry.each_value do |hooks|
99
+ hooks.delete_if(&:reloadable)
100
100
  end
101
101
  end
102
102
  end
@@ -29,14 +29,20 @@ module Bridgetown
29
29
  #
30
30
  # site - The Site.
31
31
  # base - The String path to the source.
32
- # name - The String filename of the post file.
33
- def initialize(site, base, name)
32
+ # name - The String filename of the layout file.
33
+ # from_plugin - true if the layout comes from a Gem-based plugin folder.
34
+ def initialize(site, base, name, from_plugin: false)
34
35
  @site = site
35
36
  @base = base
36
37
  @name = name
37
38
 
38
- @base_dir = site.source
39
- @path = site.in_source_dir(base, name)
39
+ if from_plugin
40
+ @base_dir = base.sub("/layouts", "")
41
+ @path = File.join(base, name)
42
+ else
43
+ @base_dir = site.source
44
+ @path = site.in_source_dir(base, name)
45
+ end
40
46
  @relative_path = @path.sub(@base_dir, "")
41
47
 
42
48
  self.data = {}
@@ -26,6 +26,7 @@ module Bridgetown
26
26
  def reset
27
27
  @stats = {}
28
28
  @cache = {}
29
+ Renderer.cached_partials = {}
29
30
  end
30
31
 
31
32
  def file(filename)
@@ -10,11 +10,8 @@ module Bridgetown
10
10
 
11
11
  def parse(content)
12
12
  measure_time do
13
- # Remove extraneous indentation for rendercontent tags
14
- processed_content = content.gsub(%r!^[ \t]+{%-? rendercontent!, "{% rendercontent")
15
-
16
13
  @renderer.cache[@filename] ||= Liquid::Template.parse(
17
- processed_content, line_numbers: true
14
+ content, line_numbers: true
18
15
  )
19
16
  end
20
17
  @template = @renderer.cache[@filename]
@@ -31,7 +31,9 @@ module Bridgetown
31
31
  raise Liquid::FileSystemError, "No such template '#{template_path}'" if found_paths.empty?
32
32
 
33
33
  # Last path in the list wins
34
- ::File.read(found_paths.last, site.file_read_opts)
34
+ LiquidComponent.parse(
35
+ ::File.read(found_paths.last, site.file_read_opts)
36
+ ).content
35
37
  end
36
38
  end
37
39
  end
@@ -35,12 +35,18 @@ module Bridgetown
35
35
  # base - The String path to the source.
36
36
  # dir - The String path between the source and the file.
37
37
  # name - The String filename of the file.
38
- def initialize(site, base, dir, name)
38
+ # from_plugin - true if the Page file is located in a Gem-based plugin folder
39
+ # rubocop:disable Metrics/ParameterLists
40
+ def initialize(site, base, dir, name, from_plugin: false)
39
41
  @site = site
40
42
  @base = base
41
43
  @dir = dir
42
44
  @name = name
43
- @path = site.in_source_dir(base, dir, name)
45
+ @path = if from_plugin
46
+ File.join(base, dir, name)
47
+ else
48
+ site.in_source_dir(base, dir, name)
49
+ end
44
50
 
45
51
  process(name)
46
52
  read_yaml(PathManager.join(base, dir), name)
@@ -51,6 +57,7 @@ module Bridgetown
51
57
 
52
58
  Bridgetown::Hooks.trigger :pages, :post_init, self
53
59
  end
60
+ # rubocop:enable Metrics/ParameterLists
54
61
 
55
62
  # The generated directory into which the page will be placed
56
63
  # upon generation. This is derived from the permalink or, if
@@ -66,18 +73,6 @@ module Bridgetown
66
73
  end
67
74
  end
68
75
 
69
- # For backwards-compatibility in subclasses that do not redefine
70
- # the `:to_liquid` method, stash existing definition under a new name
71
- #
72
- # TODO: Remove in Bridgetown 5.0
73
- alias_method :legacy_to_liquid, :to_liquid
74
- private :legacy_to_liquid
75
-
76
- # Private
77
- # Subclasses can choose to optimize their `:to_liquid` method by wrapping
78
- # it around this definition.
79
- #
80
- # TODO: Remove in Bridgetown 5.0
81
76
  def liquid_drop
82
77
  @liquid_drop ||= begin
83
78
  defaults = site.frontmatter_defaults.all(relative_path, type)
@@ -87,15 +82,12 @@ module Bridgetown
87
82
  Drops::PageDrop.new(self)
88
83
  end
89
84
  end
90
- private :liquid_drop
91
85
 
92
86
  # Public
93
87
  #
94
88
  # Liquid representation of current page
95
- #
96
- # TODO: Remove optional parameter in Bridgetown 5.0
97
- def to_liquid(attrs = nil)
98
- self.class == Bridgetown::Page ? liquid_drop : legacy_to_liquid(attrs)
89
+ def to_liquid
90
+ liquid_drop
99
91
  end
100
92
 
101
93
  # The full path and filename of the post. Defined in the YAML of the post