bridgetown-core 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +42 -0
  3. data/bridgetown-core.gemspec +46 -0
  4. data/lib/bridgetown-core.rb +202 -0
  5. data/lib/bridgetown-core/cache.rb +190 -0
  6. data/lib/bridgetown-core/cleaner.rb +111 -0
  7. data/lib/bridgetown-core/collection.rb +279 -0
  8. data/lib/bridgetown-core/command.rb +106 -0
  9. data/lib/bridgetown-core/commands/build.rb +96 -0
  10. data/lib/bridgetown-core/commands/clean.rb +43 -0
  11. data/lib/bridgetown-core/commands/console.rb +56 -0
  12. data/lib/bridgetown-core/commands/doctor.rb +172 -0
  13. data/lib/bridgetown-core/commands/help.rb +34 -0
  14. data/lib/bridgetown-core/commands/new.rb +148 -0
  15. data/lib/bridgetown-core/commands/serve.rb +273 -0
  16. data/lib/bridgetown-core/commands/serve/servlet.rb +68 -0
  17. data/lib/bridgetown-core/configuration.rb +323 -0
  18. data/lib/bridgetown-core/converter.rb +54 -0
  19. data/lib/bridgetown-core/converters/identity.rb +39 -0
  20. data/lib/bridgetown-core/converters/markdown.rb +108 -0
  21. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +132 -0
  22. data/lib/bridgetown-core/converters/smartypants.rb +69 -0
  23. data/lib/bridgetown-core/convertible.rb +237 -0
  24. data/lib/bridgetown-core/deprecator.rb +50 -0
  25. data/lib/bridgetown-core/document.rb +475 -0
  26. data/lib/bridgetown-core/drops/bridgetown_drop.rb +32 -0
  27. data/lib/bridgetown-core/drops/collection_drop.rb +20 -0
  28. data/lib/bridgetown-core/drops/document_drop.rb +69 -0
  29. data/lib/bridgetown-core/drops/drop.rb +215 -0
  30. data/lib/bridgetown-core/drops/excerpt_drop.rb +19 -0
  31. data/lib/bridgetown-core/drops/page_drop.rb +14 -0
  32. data/lib/bridgetown-core/drops/site_drop.rb +62 -0
  33. data/lib/bridgetown-core/drops/static_file_drop.rb +14 -0
  34. data/lib/bridgetown-core/drops/unified_payload_drop.rb +26 -0
  35. data/lib/bridgetown-core/drops/url_drop.rb +132 -0
  36. data/lib/bridgetown-core/entry_filter.rb +108 -0
  37. data/lib/bridgetown-core/errors.rb +20 -0
  38. data/lib/bridgetown-core/excerpt.rb +202 -0
  39. data/lib/bridgetown-core/external.rb +62 -0
  40. data/lib/bridgetown-core/filters.rb +467 -0
  41. data/lib/bridgetown-core/filters/date_filters.rb +110 -0
  42. data/lib/bridgetown-core/filters/grouping_filters.rb +64 -0
  43. data/lib/bridgetown-core/filters/url_filters.rb +79 -0
  44. data/lib/bridgetown-core/frontmatter_defaults.rb +238 -0
  45. data/lib/bridgetown-core/generator.rb +5 -0
  46. data/lib/bridgetown-core/hooks.rb +103 -0
  47. data/lib/bridgetown-core/layout.rb +57 -0
  48. data/lib/bridgetown-core/liquid_extensions.rb +22 -0
  49. data/lib/bridgetown-core/liquid_renderer.rb +71 -0
  50. data/lib/bridgetown-core/liquid_renderer/file.rb +67 -0
  51. data/lib/bridgetown-core/liquid_renderer/table.rb +75 -0
  52. data/lib/bridgetown-core/log_adapter.rb +151 -0
  53. data/lib/bridgetown-core/log_writer.rb +60 -0
  54. data/lib/bridgetown-core/mime.types +867 -0
  55. data/lib/bridgetown-core/page.rb +214 -0
  56. data/lib/bridgetown-core/page_without_a_file.rb +14 -0
  57. data/lib/bridgetown-core/path_manager.rb +31 -0
  58. data/lib/bridgetown-core/plugin.rb +80 -0
  59. data/lib/bridgetown-core/plugin_manager.rb +60 -0
  60. data/lib/bridgetown-core/publisher.rb +23 -0
  61. data/lib/bridgetown-core/reader.rb +185 -0
  62. data/lib/bridgetown-core/readers/collection_reader.rb +22 -0
  63. data/lib/bridgetown-core/readers/data_reader.rb +75 -0
  64. data/lib/bridgetown-core/readers/layout_reader.rb +48 -0
  65. data/lib/bridgetown-core/readers/page_reader.rb +24 -0
  66. data/lib/bridgetown-core/readers/post_reader.rb +74 -0
  67. data/lib/bridgetown-core/readers/static_file_reader.rb +24 -0
  68. data/lib/bridgetown-core/regenerator.rb +195 -0
  69. data/lib/bridgetown-core/related_posts.rb +52 -0
  70. data/lib/bridgetown-core/renderer.rb +261 -0
  71. data/lib/bridgetown-core/site.rb +469 -0
  72. data/lib/bridgetown-core/static_file.rb +205 -0
  73. data/lib/bridgetown-core/tags/component.rb +34 -0
  74. data/lib/bridgetown-core/tags/highlight.rb +111 -0
  75. data/lib/bridgetown-core/tags/include.rb +220 -0
  76. data/lib/bridgetown-core/tags/link.rb +41 -0
  77. data/lib/bridgetown-core/tags/post_url.rb +107 -0
  78. data/lib/bridgetown-core/url.rb +164 -0
  79. data/lib/bridgetown-core/utils.rb +367 -0
  80. data/lib/bridgetown-core/utils/ansi.rb +57 -0
  81. data/lib/bridgetown-core/utils/exec.rb +26 -0
  82. data/lib/bridgetown-core/utils/internet.rb +37 -0
  83. data/lib/bridgetown-core/utils/platforms.rb +80 -0
  84. data/lib/bridgetown-core/utils/thread_event.rb +31 -0
  85. data/lib/bridgetown-core/utils/win_tz.rb +75 -0
  86. data/lib/bridgetown-core/version.rb +5 -0
  87. data/lib/bridgetown-core/watcher.rb +139 -0
  88. data/lib/site_template/.gitignore +6 -0
  89. data/lib/site_template/bridgetown.config.yml +21 -0
  90. data/lib/site_template/frontend/javascript/index.js +3 -0
  91. data/lib/site_template/frontend/styles/index.scss +17 -0
  92. data/lib/site_template/package.json +23 -0
  93. data/lib/site_template/src/404.html +9 -0
  94. data/lib/site_template/src/_data/site_metadata.yml +11 -0
  95. data/lib/site_template/src/_includes/footer.html +3 -0
  96. data/lib/site_template/src/_includes/head.html +9 -0
  97. data/lib/site_template/src/_includes/navbar.html +4 -0
  98. data/lib/site_template/src/_layouts/default.html +15 -0
  99. data/lib/site_template/src/_layouts/home.html +7 -0
  100. data/lib/site_template/src/_layouts/page.html +7 -0
  101. data/lib/site_template/src/_layouts/post.html +7 -0
  102. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +26 -0
  103. data/lib/site_template/src/about.md +11 -0
  104. data/lib/site_template/src/index.md +7 -0
  105. data/lib/site_template/webpack.config.js +60 -0
  106. data/rake/release.rake +30 -0
  107. metadata +106 -1
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class StaticFile
5
+ extend Forwardable
6
+
7
+ attr_reader :relative_path, :extname, :name, :data
8
+
9
+ def_delegator :to_liquid, :to_json, :to_json
10
+
11
+ class << self
12
+ # The cache of last modification times [path] -> mtime.
13
+ def mtimes
14
+ @mtimes ||= {}
15
+ end
16
+
17
+ def reset_cache
18
+ @mtimes = nil
19
+ end
20
+ end
21
+
22
+ # Initialize a new StaticFile.
23
+ #
24
+ # site - The Site.
25
+ # base - The String path to the <source>.
26
+ # dir - The String path between <source> and the file.
27
+ # name - The String filename of the file.
28
+ # rubocop: disable Metrics/ParameterLists
29
+ def initialize(site, base, dir, name, collection = nil)
30
+ @site = site
31
+ @base = base
32
+ @dir = dir
33
+ @name = name
34
+ @collection = collection
35
+ @relative_path = File.join(*[@dir, @name].compact)
36
+ @extname = File.extname(@name)
37
+ @data = @site.frontmatter_defaults.all(relative_path, type)
38
+ end
39
+ # rubocop: enable Metrics/ParameterLists
40
+
41
+ # Returns source file path.
42
+ def path
43
+ @path ||= begin
44
+ # Static file is from a collection inside custom collections directory
45
+ if !@collection.nil? && !@site.config["collections_dir"].empty?
46
+ File.join(*[@base, @site.config["collections_dir"], @dir, @name].compact)
47
+ else
48
+ File.join(*[@base, @dir, @name].compact)
49
+ end
50
+ end
51
+ end
52
+
53
+ # Obtain destination path.
54
+ #
55
+ # dest - The String path to the destination dir.
56
+ #
57
+ # Returns destination file path.
58
+ def destination(dest)
59
+ dest = @site.in_dest_dir(dest)
60
+ @site.in_dest_dir(dest, Bridgetown::URL.unescape_path(url))
61
+ end
62
+
63
+ def destination_rel_dir
64
+ if @collection
65
+ File.dirname(url)
66
+ else
67
+ @dir
68
+ end
69
+ end
70
+
71
+ def modified_time
72
+ @modified_time ||= File.stat(path).mtime
73
+ end
74
+
75
+ # Returns last modification time for this file.
76
+ def mtime
77
+ modified_time.to_i
78
+ end
79
+
80
+ # Is source path modified?
81
+ #
82
+ # Returns true if modified since last write.
83
+ def modified?
84
+ self.class.mtimes[path] != mtime
85
+ end
86
+
87
+ # Whether to write the file to the filesystem
88
+ #
89
+ # Returns true unless the defaults for the destination path from
90
+ # bridgetown.config.yml contain `published: false`.
91
+ def write?
92
+ publishable = defaults.fetch("published", true)
93
+ return publishable unless @collection
94
+
95
+ publishable && @collection.write?
96
+ end
97
+
98
+ # Write the static file to the destination directory (if modified).
99
+ #
100
+ # dest - The String path to the destination dir.
101
+ #
102
+ # Returns false if the file was not modified since last time (no-op).
103
+ def write(dest)
104
+ dest_path = destination(dest)
105
+ return false if File.exist?(dest_path) && !modified?
106
+
107
+ self.class.mtimes[path] = mtime
108
+
109
+ FileUtils.mkdir_p(File.dirname(dest_path))
110
+ FileUtils.rm(dest_path) if File.exist?(dest_path)
111
+ copy_file(dest_path)
112
+
113
+ true
114
+ end
115
+
116
+ def to_liquid
117
+ @to_liquid ||= Drops::StaticFileDrop.new(self)
118
+ end
119
+
120
+ # Generate "basename without extension" and strip away any trailing periods.
121
+ # NOTE: `String#gsub` removes all trailing periods (in comparison to `String#chomp`)
122
+ def basename
123
+ @basename ||= File.basename(name, extname).gsub(%r!\.*\z!, "")
124
+ end
125
+
126
+ def placeholders
127
+ {
128
+ :collection => @collection.label,
129
+ :path => cleaned_relative_path,
130
+ :output_ext => "",
131
+ :name => "",
132
+ :title => "",
133
+ }
134
+ end
135
+
136
+ # Similar to Bridgetown::Document#cleaned_relative_path.
137
+ # Generates a relative path with the collection's directory removed when applicable
138
+ # and additionally removes any multiple periods in the string.
139
+ #
140
+ # NOTE: `String#gsub!` removes all trailing periods (in comparison to `String#chomp!`)
141
+ #
142
+ # Examples:
143
+ # When `relative_path` is "_methods/site/my-cool-avatar...png":
144
+ # cleaned_relative_path
145
+ # # => "/site/my-cool-avatar"
146
+ #
147
+ # Returns the cleaned relative path of the static file.
148
+ def cleaned_relative_path
149
+ @cleaned_relative_path ||= begin
150
+ cleaned = relative_path[0..-extname.length - 1]
151
+ cleaned.gsub!(%r!\.*\z!, "")
152
+ cleaned.sub!(@collection.relative_directory, "") if @collection
153
+ cleaned
154
+ end
155
+ end
156
+
157
+ # Applies a similar URL-building technique as Bridgetown::Document that takes
158
+ # the collection's URL template into account. The default URL template can
159
+ # be overriden in the collection's configuration in bridgetown.config.yml.
160
+ def url
161
+ @url ||= begin
162
+ base = if @collection.nil?
163
+ cleaned_relative_path
164
+ else
165
+ Bridgetown::URL.new(
166
+ :template => @collection.url_template,
167
+ :placeholders => placeholders
168
+ )
169
+ end.to_s.chomp("/")
170
+ base << extname
171
+ end
172
+ end
173
+
174
+ # Returns the type of the collection if present, nil otherwise.
175
+ def type
176
+ @type ||= @collection.nil? ? nil : @collection.label.to_sym
177
+ end
178
+
179
+ # Returns the front matter defaults defined for the file's URL and/or type
180
+ # as defined in bridgetown.config.yml.
181
+ def defaults
182
+ @defaults ||= @site.frontmatter_defaults.all url, type
183
+ end
184
+
185
+ # Returns a debug string on inspecting the static file.
186
+ # Includes only the relative path of the object.
187
+ def inspect
188
+ "#<#{self.class} @relative_path=#{relative_path.inspect}>"
189
+ end
190
+
191
+ private
192
+
193
+ def copy_file(dest_path)
194
+ if Bridgetown.env == "production"
195
+ FileUtils.cp(path, dest_path)
196
+ else
197
+ FileUtils.copy_entry(path, dest_path)
198
+ end
199
+
200
+ unless File.symlink?(dest_path)
201
+ File.utime(self.class.mtimes[path], self.class.mtimes[path], dest_path)
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class BlockComponentTag < Liquid::Block
6
+ def initialize(tag_name, markup, tokens)
7
+ super
8
+
9
+ filename, @extra_params = markup.strip.split(" ", 2)
10
+ @component_path = "_components/#{filename.strip}.html"
11
+
12
+ @tag_name = tag_name
13
+ end
14
+
15
+ def render(context)
16
+ markdown_content = super
17
+
18
+ site = context.registers[:site]
19
+ converter = site.find_converter_instance(Bridgetown::Converters::Markdown)
20
+ markdownified_content = converter.convert(markdown_content)
21
+
22
+ context.stack do
23
+ context["componentcontent"] = markdownified_content
24
+ include_params = "#{@component_path} content=componentcontent"
25
+ include_params = "#{include_params} #{@extra_params}" if @extra_params
26
+ include_tag = IncludeTag.parse("include", include_params, [], @parse_context)
27
+ include_tag.render(context)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Liquid::Template.register_tag("component", Bridgetown::Tags::BlockComponentTag)
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class HighlightBlock < Liquid::Block
6
+ include Liquid::StandardFilters
7
+
8
+ # The regular expression syntax checker. Start with the language specifier.
9
+ # Follow that by zero or more space separated options that take one of three
10
+ # forms: name, name=value, or name="<quoted list>"
11
+ #
12
+ # <quoted list> is a space-separated list of numbers
13
+ SYNTAX = %r!^([a-zA-Z0-9.+#_-]+)((\s+\w+(=(\w+|"([0-9]+\s)*[0-9]+"))?)*)$!.freeze
14
+
15
+ def initialize(tag_name, markup, tokens)
16
+ super
17
+ if markup.strip =~ SYNTAX
18
+ @lang = Regexp.last_match(1).downcase
19
+ @highlight_options = parse_options(Regexp.last_match(2))
20
+ else
21
+ raise SyntaxError, <<~MSG
22
+ Syntax Error in tag 'highlight' while parsing the following markup:
23
+
24
+ #{markup}
25
+
26
+ Valid syntax: highlight <lang> [linenos]
27
+ MSG
28
+ end
29
+ end
30
+
31
+ LEADING_OR_TRAILING_LINE_TERMINATORS = %r!\A(\n|\r)+|(\n|\r)+\z!.freeze
32
+
33
+ def render(context)
34
+ prefix = context["highlighter_prefix"] || ""
35
+ suffix = context["highlighter_suffix"] || ""
36
+ code = super.to_s.gsub(LEADING_OR_TRAILING_LINE_TERMINATORS, "")
37
+
38
+ output =
39
+ case context.registers[:site].highlighter
40
+ when "rouge"
41
+ render_rouge(code)
42
+ when "pygments"
43
+ render_pygments(code, context)
44
+ else
45
+ render_codehighlighter(code)
46
+ end
47
+
48
+ rendered_output = add_code_tag(output)
49
+ prefix + rendered_output + suffix
50
+ end
51
+
52
+ private
53
+
54
+ OPTIONS_REGEX = %r!(?:\w="[^"]*"|\w=\w|\w)+!.freeze
55
+
56
+ def parse_options(input)
57
+ options = {}
58
+ return options if input.empty?
59
+
60
+ # Split along 3 possible forms -- key="<quoted list>", key=value, or key
61
+ input.scan(OPTIONS_REGEX) do |opt|
62
+ key, value = opt.split("=")
63
+ # If a quoted list, convert to array
64
+ if value&.include?('"')
65
+ value.delete!('"')
66
+ value = value.split
67
+ end
68
+ options[key.to_sym] = value || true
69
+ end
70
+
71
+ options[:linenos] = "inline" if options[:linenos] == true
72
+ options
73
+ end
74
+
75
+ def render_pygments(code, _context)
76
+ Bridgetown.logger.warn "Warning:", "Highlight Tag no longer supports" \
77
+ " rendering with Pygments."
78
+ Bridgetown.logger.warn "", "Using the default highlighter, Rouge, instead."
79
+ render_rouge(code)
80
+ end
81
+
82
+ def render_rouge(code)
83
+ require "rouge"
84
+ formatter = ::Rouge::Formatters::HTMLLegacy.new(
85
+ :line_numbers => @highlight_options[:linenos],
86
+ :wrap => false,
87
+ :css_class => "highlight",
88
+ :gutter_class => "gutter",
89
+ :code_class => "code"
90
+ )
91
+ lexer = ::Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
92
+ formatter.format(lexer.lex(code))
93
+ end
94
+
95
+ def render_codehighlighter(code)
96
+ h(code).strip
97
+ end
98
+
99
+ def add_code_tag(code)
100
+ code_attributes = [
101
+ "class=\"language-#{@lang.to_s.tr("+", "-")}\"",
102
+ "data-lang=\"#{@lang}\"",
103
+ ].join(" ")
104
+ "<figure class=\"highlight\"><pre><code #{code_attributes}>"\
105
+ "#{code.chomp}</code></pre></figure>"
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ Liquid::Template.register_tag("highlight", Bridgetown::Tags::HighlightBlock)
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Tags
5
+ class IncludeTag < Liquid::Tag
6
+ VALID_SYNTAX = %r!
7
+ ([\w-]+)\s*=\s*
8
+ (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))
9
+ !x.freeze
10
+ VARIABLE_SYNTAX = %r!
11
+ (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
12
+ (?<params>.*)
13
+ !mx.freeze
14
+
15
+ FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!.freeze
16
+ VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!.freeze
17
+ INVALID_SEQUENCES = %r![./]{2,}!.freeze
18
+
19
+ def initialize(tag_name, markup, tokens)
20
+ super
21
+ matched = markup.strip.match(VARIABLE_SYNTAX)
22
+ if matched
23
+ @file = matched["variable"].strip
24
+ @params = matched["params"].strip
25
+ else
26
+ @file, @params = markup.strip.split(%r!\s+!, 2)
27
+ end
28
+ validate_params if @params
29
+ @tag_name = tag_name
30
+ end
31
+
32
+ def syntax_example
33
+ "{% #{@tag_name} file.ext param='value' param2='value' %}"
34
+ end
35
+
36
+ def parse_params(context)
37
+ params = {}
38
+ markup = @params
39
+
40
+ while (match = VALID_SYNTAX.match(markup))
41
+ markup = markup[match.end(0)..-1]
42
+
43
+ value = if match[2]
44
+ match[2].gsub('\\"', '"')
45
+ elsif match[3]
46
+ match[3].gsub("\\'", "'")
47
+ elsif match[4]
48
+ context[match[4]]
49
+ end
50
+
51
+ params[match[1]] = value
52
+ end
53
+ params
54
+ end
55
+
56
+ def validate_file_name(file)
57
+ if INVALID_SEQUENCES.match?(file) || !VALID_FILENAME_CHARS.match?(file)
58
+ raise ArgumentError, <<~MSG
59
+ Invalid syntax for include tag. File contains invalid characters or sequences:
60
+
61
+ #{file}
62
+
63
+ Valid syntax:
64
+
65
+ #{syntax_example}
66
+
67
+ MSG
68
+ end
69
+ end
70
+
71
+ def validate_params
72
+ unless FULL_VALID_SYNTAX.match?(@params)
73
+ raise ArgumentError, <<~MSG
74
+ Invalid syntax for include tag:
75
+
76
+ #{@params}
77
+
78
+ Valid syntax:
79
+
80
+ #{syntax_example}
81
+
82
+ MSG
83
+ end
84
+ end
85
+
86
+ # Grab file read opts in the context
87
+ def file_read_opts(context)
88
+ context.registers[:site].file_read_opts
89
+ end
90
+
91
+ # Render the variable if required
92
+ def render_variable(context)
93
+ Liquid::Template.parse(@file).render(context) if VARIABLE_SYNTAX.match?(@file)
94
+ end
95
+
96
+ def tag_includes_dirs(context)
97
+ context.registers[:site].includes_load_paths.freeze
98
+ end
99
+
100
+ def locate_include_file(context, file)
101
+ includes_dirs = tag_includes_dirs(context)
102
+ # TODO: component folder should be configured separately
103
+ if file.start_with?("_components")
104
+ first_dir = includes_dirs.first
105
+ path = PathManager.join(first_dir.sub(%r!/_includes$!, ""), file)
106
+ return path if valid_include_file?(path, first_dir.to_s)
107
+ else
108
+ includes_dirs.each do |dir|
109
+ path = PathManager.join(dir, file)
110
+ return path if valid_include_file?(path, dir.to_s)
111
+ end
112
+ end
113
+ raise IOError, could_not_locate_message(file, includes_dirs)
114
+ end
115
+
116
+ def render(context)
117
+ site = context.registers[:site]
118
+
119
+ file = render_variable(context) || @file
120
+ validate_file_name(file)
121
+
122
+ path = locate_include_file(context, file)
123
+ return unless path
124
+
125
+ add_include_to_dependency(site, path, context)
126
+
127
+ partial = load_cached_partial(path, context)
128
+
129
+ context.stack do
130
+ context["include"] = parse_params(context) if @params
131
+ begin
132
+ partial.render!(context)
133
+ rescue Liquid::Error => e
134
+ e.template_name = path
135
+ e.markup_context = "included " if e.markup_context.nil?
136
+ raise e
137
+ end
138
+ end
139
+ end
140
+
141
+ def add_include_to_dependency(site, path, context)
142
+ if context.registers[:page]&.key?("path")
143
+ site.regenerator.add_dependency(
144
+ site.in_source_dir(context.registers[:page]["path"]),
145
+ path
146
+ )
147
+ end
148
+ end
149
+
150
+ def load_cached_partial(path, context)
151
+ context.registers[:cached_partials] ||= {}
152
+ cached_partial = context.registers[:cached_partials]
153
+
154
+ if cached_partial.key?(path)
155
+ cached_partial[path]
156
+ else
157
+ unparsed_file = context.registers[:site]
158
+ .liquid_renderer
159
+ .file(path)
160
+ begin
161
+ cached_partial[path] = unparsed_file.parse(read_file(path, context))
162
+ rescue Liquid::Error => e
163
+ e.template_name = path
164
+ e.markup_context = "included " if e.markup_context.nil?
165
+ raise e
166
+ end
167
+ end
168
+ end
169
+
170
+ def valid_include_file?(path, _dir)
171
+ File.file?(path)
172
+ end
173
+
174
+ def realpath_prefixed_with?(path, dir)
175
+ File.exist?(path) && File.realpath(path).start_with?(dir)
176
+ rescue StandardError
177
+ false
178
+ end
179
+
180
+ # This method allows to modify the file content by inheriting from the class.
181
+ def read_file(file, context)
182
+ File.read(file, **file_read_opts(context))
183
+ end
184
+
185
+ private
186
+
187
+ # TODO: update this to support components as well
188
+ def could_not_locate_message(file, includes_dirs)
189
+ "Could not locate the included file '#{file}' in any of #{includes_dirs}." \
190
+ " Ensure it exists in one of those directories."
191
+ end
192
+ end
193
+
194
+ class IncludeRelativeTag < IncludeTag
195
+ def tag_includes_dirs(context)
196
+ Array(page_path(context)).freeze
197
+ end
198
+
199
+ def page_path(context)
200
+ if context.registers[:page].nil?
201
+ context.registers[:site].source
202
+ else
203
+ site = context.registers[:site]
204
+ page_payload = context.registers[:page]
205
+ resource_path = \
206
+ if page_payload["collection"].nil?
207
+ page_payload["path"]
208
+ else
209
+ File.join(site.config["collections_dir"], page_payload["path"])
210
+ end
211
+ resource_path.sub!(%r!/#excerpt\z!, "")
212
+ site.in_source_dir File.dirname(resource_path)
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ Liquid::Template.register_tag("include", Bridgetown::Tags::IncludeTag)
220
+ Liquid::Template.register_tag("include_relative", Bridgetown::Tags::IncludeRelativeTag)