bridgetown-core 0.12.0 → 0.15.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/bin/bridgetown +9 -48
  4. data/bridgetown-core.gemspec +10 -5
  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 +95 -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/site_drop.rb +1 -2
  33. data/lib/bridgetown-core/external.rb +17 -21
  34. data/lib/bridgetown-core/filters.rb +10 -0
  35. data/lib/bridgetown-core/generators/prototype_generator.rb +3 -1
  36. data/lib/bridgetown-core/hooks.rb +62 -62
  37. data/lib/bridgetown-core/layout.rb +10 -4
  38. data/lib/bridgetown-core/liquid_renderer.rb +2 -0
  39. data/lib/bridgetown-core/liquid_renderer/file_system.rb +5 -1
  40. data/lib/bridgetown-core/page.rb +9 -2
  41. data/lib/bridgetown-core/plugin.rb +2 -0
  42. data/lib/bridgetown-core/plugin_manager.rb +68 -14
  43. data/lib/bridgetown-core/reader.rb +5 -0
  44. data/lib/bridgetown-core/readers/data_reader.rb +15 -2
  45. data/lib/bridgetown-core/readers/layout_reader.rb +9 -2
  46. data/lib/bridgetown-core/readers/plugin_content_reader.rb +48 -0
  47. data/lib/bridgetown-core/renderer.rb +51 -32
  48. data/lib/bridgetown-core/site.rb +20 -449
  49. data/lib/bridgetown-core/static_file.rb +1 -5
  50. data/lib/bridgetown-core/tags/include.rb +12 -0
  51. data/lib/bridgetown-core/tags/render_content.rb +27 -16
  52. data/lib/bridgetown-core/tags/with.rb +15 -0
  53. data/lib/bridgetown-core/utils.rb +2 -27
  54. data/lib/bridgetown-core/utils/ruby_exec.rb +66 -0
  55. data/lib/bridgetown-core/version.rb +2 -2
  56. data/lib/bridgetown-core/watcher.rb +21 -10
  57. data/lib/site_template/Gemfile.erb +19 -0
  58. data/lib/site_template/plugins/{.keep → builders/.keep} +0 -0
  59. data/lib/site_template/plugins/site_builder.rb +4 -0
  60. data/lib/site_template/src/_components/footer.html +3 -0
  61. data/lib/site_template/src/_components/head.html +9 -0
  62. data/lib/site_template/src/{_includes → _components}/navbar.html +1 -0
  63. data/lib/site_template/src/_layouts/default.html +3 -3
  64. data/lib/site_template/src/{_components/.keep → favicon.ico} +0 -0
  65. data/lib/site_template/src/posts.md +15 -0
  66. data/lib/site_template/start.js +1 -1
  67. metadata +107 -19
  68. data/lib/bridgetown-core/command.rb +0 -106
  69. data/lib/bridgetown-core/commands/help.rb +0 -34
  70. data/lib/site_template/src/_includes/footer.html +0 -3
  71. 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
@@ -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
 
@@ -11,6 +11,7 @@ module Bridgetown
11
11
  # Read Site data from disk and load it into internal data structures.
12
12
  #
13
13
  # Returns nothing.
14
+ # rubocop:disable Metrics/AbcSize
14
15
  def read
15
16
  @site.layouts = LayoutReader.new(site).read
16
17
  read_directories
@@ -18,7 +19,11 @@ module Bridgetown
18
19
  sort_files!
19
20
  @site.data = DataReader.new(site).read(site.config["data_dir"])
20
21
  CollectionReader.new(site).read
22
+ Bridgetown::PluginManager.source_manifests.map(&:content).compact.each do |plugin_content_dir|
23
+ PluginContentReader.new(site, plugin_content_dir).read
24
+ end
21
25
  end
26
+ # rubocop:enable Metrics/AbcSize
22
27
 
23
28
  # Sorts posts, pages, and static files.
24
29
  def sort_files!
@@ -5,7 +5,7 @@ module Bridgetown
5
5
  attr_reader :site, :content
6
6
  def initialize(site)
7
7
  @site = site
8
- @content = {}
8
+ @content = ActiveSupport::HashWithIndifferentAccess.new
9
9
  @entry_filter = EntryFilter.new(site)
10
10
  end
11
11
 
@@ -18,6 +18,7 @@ module Bridgetown
18
18
  def read(dir)
19
19
  base = site.in_source_dir(dir)
20
20
  read_data_to(base, @content)
21
+ merge_environment_specific_metadata!
21
22
  @content
22
23
  end
23
24
 
@@ -40,7 +41,10 @@ module Bridgetown
40
41
  next if @entry_filter.symlink?(path)
41
42
 
42
43
  if File.directory?(path)
43
- read_data_to(path, data[sanitize_filename(entry)] = {})
44
+ read_data_to(
45
+ path,
46
+ data[sanitize_filename(entry)] = ActiveSupport::HashWithIndifferentAccess.new
47
+ )
44
48
  else
45
49
  key = sanitize_filename(File.basename(entry, ".*"))
46
50
  data[key] = read_data_file(path)
@@ -67,6 +71,15 @@ module Bridgetown
67
71
  end
68
72
  end
69
73
 
74
+ def merge_environment_specific_metadata!
75
+ if @content["site_metadata"]
76
+ @content["site_metadata"][Bridgetown.environment]&.each_key do |k|
77
+ @content["site_metadata"][k] = @content["site_metadata"][Bridgetown.environment][k]
78
+ end
79
+ @content["site_metadata"].delete(Bridgetown.environment)
80
+ end
81
+ end
82
+
70
83
  def sanitize_filename(name)
71
84
  name.gsub(%r![^\w\s-]+|(?<=^|\b\s)\s+(?=$|\s?\b)!, "")
72
85
  .gsub(%r!\s+!, "_")