bridgetown-core 0.12.1 → 0.15.0.beta2

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 +7 -2
  5. data/lib/bridgetown-core.rb +20 -4
  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 +107 -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 -38
  16. data/lib/bridgetown-core/commands/doctor.rb +125 -135
  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 -217
  21. data/lib/bridgetown-core/{convertible.rb → concerns/convertible.rb} +2 -2
  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 +50 -0
  27. data/lib/bridgetown-core/concerns/site/writable.rb +31 -0
  28. data/lib/bridgetown-core/configuration.rb +98 -108
  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/bridgetown_drop.rb +6 -1
  32. data/lib/bridgetown-core/drops/page_drop.rb +1 -1
  33. data/lib/bridgetown-core/drops/site_drop.rb +1 -2
  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 +2 -0
  40. data/lib/bridgetown-core/liquid_renderer/file_system.rb +5 -1
  41. data/lib/bridgetown-core/page.rb +11 -19
  42. data/lib/bridgetown-core/plugin.rb +2 -0
  43. data/lib/bridgetown-core/plugin_manager.rb +68 -14
  44. data/lib/bridgetown-core/reader.rb +5 -0
  45. data/lib/bridgetown-core/readers/data_reader.rb +15 -2
  46. data/lib/bridgetown-core/readers/layout_reader.rb +9 -2
  47. data/lib/bridgetown-core/readers/plugin_content_reader.rb +48 -0
  48. data/lib/bridgetown-core/renderer.rb +51 -32
  49. data/lib/bridgetown-core/site.rb +20 -449
  50. data/lib/bridgetown-core/static_file.rb +1 -5
  51. data/lib/bridgetown-core/tags/include.rb +12 -0
  52. data/lib/bridgetown-core/tags/render_content.rb +27 -16
  53. data/lib/bridgetown-core/tags/with.rb +15 -0
  54. data/lib/bridgetown-core/utils.rb +2 -27
  55. data/lib/bridgetown-core/utils/ruby_exec.rb +66 -0
  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/{_components/.keep → favicon.ico} +0 -0
  67. data/lib/site_template/src/posts.md +15 -0
  68. data/lib/site_template/start.js +1 -1
  69. data/lib/site_template/webpack.config.js +3 -3
  70. metadata +106 -18
  71. data/lib/bridgetown-core/command.rb +0 -106
  72. data/lib/bridgetown-core/commands/help.rb +0 -34
  73. data/lib/site_template/src/_includes/footer.html +0 -3
  74. data/lib/site_template/src/_includes/head.html +0 -9
@@ -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 = {}
@@ -17,6 +17,7 @@ module Bridgetown
17
17
  Liquid::Template.file_system = LiquidRenderer::FileSystem.new(
18
18
  @site.components_load_paths, "%s.liquid"
19
19
  )
20
+ Liquid::Template.file_system.site = site
20
21
 
21
22
  Liquid::Template.error_mode = @site.config["liquid"]["error_mode"].to_sym
22
23
  reset
@@ -25,6 +26,7 @@ module Bridgetown
25
26
  def reset
26
27
  @stats = {}
27
28
  @cache = {}
29
+ Renderer.cached_partials = {}
28
30
  end
29
31
 
30
32
  def file(filename)
@@ -3,6 +3,8 @@
3
3
  module Bridgetown
4
4
  class LiquidRenderer
5
5
  class FileSystem < Liquid::LocalFileSystem
6
+ attr_accessor :site
7
+
6
8
  def read_template_file(template_path)
7
9
  load_paths = root
8
10
  found_paths = []
@@ -29,7 +31,9 @@ module Bridgetown
29
31
  raise Liquid::FileSystemError, "No such template '#{template_path}'" if found_paths.empty?
30
32
 
31
33
  # Last path in the list wins
32
- ::File.read(found_paths.last)
34
+ LiquidComponent.parse(
35
+ ::File.read(found_paths.last, site.file_read_opts)
36
+ ).content
33
37
  end
34
38
  end
35
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
@@ -10,6 +10,8 @@ module Bridgetown
10
10
  high: 10,
11
11
  }.freeze
12
12
 
13
+ SourceManifest = Struct.new(:origin, :components, :content, :layouts, keyword_init: true)
14
+
13
15
  #
14
16
 
15
17
  def self.inherited(const)
@@ -2,8 +2,33 @@
2
2
 
3
3
  module Bridgetown
4
4
  class PluginManager
5
+ PLUGINS_GROUP = :bridgetown_plugins
6
+
5
7
  attr_reader :site
6
8
 
9
+ @source_manifests = Set.new
10
+ @registered_plugins = Set.new
11
+
12
+ def self.add_source_manifest(source_manifest)
13
+ unless source_manifest.is_a?(Bridgetown::Plugin::SourceManifest)
14
+ raise "You must add a SourceManifest instance"
15
+ end
16
+
17
+ @source_manifests << source_manifest
18
+ end
19
+
20
+ def self.new_source_manifest(*args)
21
+ add_source_manifest(Bridgetown::Plugin::SourceManifest.new(*args))
22
+ end
23
+
24
+ def self.add_registered_plugin(gem_or_plugin_file)
25
+ @registered_plugins << gem_or_plugin_file
26
+ end
27
+
28
+ class << self
29
+ attr_reader :source_manifests, :registered_plugins
30
+ end
31
+
7
32
  # Create an instance of this class.
8
33
  #
9
34
  # site - the instance of Bridgetown::Site we're concerned with
@@ -13,22 +38,23 @@ module Bridgetown
13
38
  @site = site
14
39
  end
15
40
 
16
- # Require all the plugins which are allowed.
17
- #
18
- # Returns nothing
19
- def conscientious_require
20
- require_plugin_files
21
- end
22
-
23
41
  def self.require_from_bundler
24
42
  if !ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
25
43
  require "bundler"
26
44
 
27
- Bundler.setup
28
- required_gems = Bundler.require(:bridgetown_plugins)
45
+ required_gems = Bundler.require PLUGINS_GROUP
46
+ required_gems.select! do |dep|
47
+ (dep.groups & [PLUGINS_GROUP]).any? && dep.should_include?
48
+ end
49
+
29
50
  install_yarn_dependencies(required_gems)
30
- message = "Required #{required_gems.map(&:name).join(", ")}"
31
- Bridgetown.logger.debug("PluginManager:", message)
51
+
52
+ required_gems.each do |installed_gem|
53
+ add_registered_plugin installed_gem
54
+ end
55
+
56
+ Bridgetown.logger.debug("PluginManager:",
57
+ "Required #{required_gems.map(&:name).join(", ")}")
32
58
  ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] = "true"
33
59
 
34
60
  true
@@ -41,6 +67,7 @@ module Bridgetown
41
67
  # If that exact package hasn't been installed, execute yarn add
42
68
  #
43
69
  # Returns nothing.
70
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
44
71
  def self.install_yarn_dependencies(required_gems)
45
72
  return unless File.exist?("package.json")
46
73
 
@@ -53,14 +80,17 @@ module Bridgetown
53
80
  next unless yarn_add_dependency.length == 2
54
81
 
55
82
  # check matching version number is see if it's already installed
56
- current_package = package_json["dependencies"].dig(yarn_add_dependency.first)
57
- next unless current_package.nil? || current_package != yarn_add_dependency.last
83
+ if package_json["dependencies"]
84
+ current_package = package_json["dependencies"].dig(yarn_add_dependency.first)
85
+ next unless current_package.nil? || current_package != yarn_add_dependency.last
86
+ end
58
87
 
59
88
  # all right, time to install the package
60
89
  cmd = "yarn add #{yarn_add_dependency.join("@")}"
61
90
  system cmd
62
91
  end
63
92
  end
93
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
64
94
 
65
95
  # Require all .rb files
66
96
  #
@@ -68,7 +98,31 @@ module Bridgetown
68
98
  def require_plugin_files
69
99
  plugins_path.each do |plugin_search_path|
70
100
  plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
71
- Bridgetown::External.require_with_graceful_fail(plugin_files)
101
+
102
+ # Require "site_builder.rb" first if present so subclasses can all
103
+ # inherit from SiteBuilder without needing explicit require statements
104
+ sorted_plugin_files = plugin_files.select do |path|
105
+ path.include?("site_builder.rb")
106
+ end + plugin_files.reject do |path|
107
+ path.include?("site_builder.rb")
108
+ end
109
+
110
+ sorted_plugin_files.each do |plugin_file|
111
+ self.class.add_registered_plugin plugin_file
112
+ end
113
+ Bridgetown::External.require_with_graceful_fail(sorted_plugin_files)
114
+ end
115
+ end
116
+
117
+ # Reload .rb plugin files via the watcher
118
+ def reload_plugin_files
119
+ plugins_path.each do |plugin_search_path|
120
+ plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
121
+ Array(plugin_files).each do |name|
122
+ Bridgetown.logger.debug "Reloading:", name.to_s
123
+ self.class.add_registered_plugin name
124
+ load name
125
+ end
72
126
  end
73
127
  end
74
128