ngage 0.0.0

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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/exe/ngage +55 -0
  4. data/lib/ngage.rb +3 -0
  5. data/lib/ngage/jekyll.rb +204 -0
  6. data/lib/ngage/jekyll/cleaner.rb +111 -0
  7. data/lib/ngage/jekyll/collection.rb +235 -0
  8. data/lib/ngage/jekyll/command.rb +103 -0
  9. data/lib/ngage/jekyll/commands/build.rb +93 -0
  10. data/lib/ngage/jekyll/commands/clean.rb +45 -0
  11. data/lib/ngage/jekyll/commands/doctor.rb +173 -0
  12. data/lib/ngage/jekyll/commands/help.rb +34 -0
  13. data/lib/ngage/jekyll/commands/new.rb +157 -0
  14. data/lib/ngage/jekyll/commands/new_theme.rb +42 -0
  15. data/lib/ngage/jekyll/commands/serve.rb +354 -0
  16. data/lib/ngage/jekyll/commands/serve/live_reload_reactor.rb +122 -0
  17. data/lib/ngage/jekyll/commands/serve/livereload_assets/livereload.js +1183 -0
  18. data/lib/ngage/jekyll/commands/serve/servlet.rb +203 -0
  19. data/lib/ngage/jekyll/commands/serve/websockets.rb +81 -0
  20. data/lib/ngage/jekyll/configuration.rb +391 -0
  21. data/lib/ngage/jekyll/converter.rb +54 -0
  22. data/lib/ngage/jekyll/converters/identity.rb +41 -0
  23. data/lib/ngage/jekyll/converters/markdown.rb +116 -0
  24. data/lib/ngage/jekyll/converters/markdown/kramdown_parser.rb +122 -0
  25. data/lib/ngage/jekyll/converters/smartypants.rb +70 -0
  26. data/lib/ngage/jekyll/convertible.rb +253 -0
  27. data/lib/ngage/jekyll/deprecator.rb +50 -0
  28. data/lib/ngage/jekyll/document.rb +503 -0
  29. data/lib/ngage/jekyll/drops/collection_drop.rb +20 -0
  30. data/lib/ngage/jekyll/drops/document_drop.rb +69 -0
  31. data/lib/ngage/jekyll/drops/drop.rb +209 -0
  32. data/lib/ngage/jekyll/drops/excerpt_drop.rb +15 -0
  33. data/lib/ngage/jekyll/drops/jekyll_drop.rb +32 -0
  34. data/lib/ngage/jekyll/drops/site_drop.rb +56 -0
  35. data/lib/ngage/jekyll/drops/static_file_drop.rb +14 -0
  36. data/lib/ngage/jekyll/drops/unified_payload_drop.rb +26 -0
  37. data/lib/ngage/jekyll/drops/url_drop.rb +89 -0
  38. data/lib/ngage/jekyll/entry_filter.rb +127 -0
  39. data/lib/ngage/jekyll/errors.rb +20 -0
  40. data/lib/ngage/jekyll/excerpt.rb +180 -0
  41. data/lib/ngage/jekyll/external.rb +76 -0
  42. data/lib/ngage/jekyll/filters.rb +390 -0
  43. data/lib/ngage/jekyll/filters/date_filters.rb +110 -0
  44. data/lib/ngage/jekyll/filters/grouping_filters.rb +64 -0
  45. data/lib/ngage/jekyll/filters/url_filters.rb +68 -0
  46. data/lib/ngage/jekyll/frontmatter_defaults.rb +233 -0
  47. data/lib/ngage/jekyll/generator.rb +5 -0
  48. data/lib/ngage/jekyll/hooks.rb +106 -0
  49. data/lib/ngage/jekyll/layout.rb +62 -0
  50. data/lib/ngage/jekyll/liquid_extensions.rb +22 -0
  51. data/lib/ngage/jekyll/liquid_renderer.rb +63 -0
  52. data/lib/ngage/jekyll/liquid_renderer/file.rb +56 -0
  53. data/lib/ngage/jekyll/liquid_renderer/table.rb +98 -0
  54. data/lib/ngage/jekyll/log_adapter.rb +151 -0
  55. data/lib/ngage/jekyll/mime.types +825 -0
  56. data/lib/ngage/jekyll/page.rb +185 -0
  57. data/lib/ngage/jekyll/page_without_a_file.rb +14 -0
  58. data/lib/ngage/jekyll/plugin.rb +92 -0
  59. data/lib/ngage/jekyll/plugin_manager.rb +115 -0
  60. data/lib/ngage/jekyll/publisher.rb +23 -0
  61. data/lib/ngage/jekyll/reader.rb +154 -0
  62. data/lib/ngage/jekyll/readers/collection_reader.rb +22 -0
  63. data/lib/ngage/jekyll/readers/data_reader.rb +75 -0
  64. data/lib/ngage/jekyll/readers/layout_reader.rb +70 -0
  65. data/lib/ngage/jekyll/readers/page_reader.rb +25 -0
  66. data/lib/ngage/jekyll/readers/post_reader.rb +72 -0
  67. data/lib/ngage/jekyll/readers/static_file_reader.rb +25 -0
  68. data/lib/ngage/jekyll/readers/theme_assets_reader.rb +51 -0
  69. data/lib/ngage/jekyll/regenerator.rb +195 -0
  70. data/lib/ngage/jekyll/related_posts.rb +52 -0
  71. data/lib/ngage/jekyll/renderer.rb +266 -0
  72. data/lib/ngage/jekyll/site.rb +476 -0
  73. data/lib/ngage/jekyll/static_file.rb +169 -0
  74. data/lib/ngage/jekyll/stevenson.rb +60 -0
  75. data/lib/ngage/jekyll/tags/highlight.rb +108 -0
  76. data/lib/ngage/jekyll/tags/include.rb +226 -0
  77. data/lib/ngage/jekyll/tags/link.rb +40 -0
  78. data/lib/ngage/jekyll/tags/post_url.rb +104 -0
  79. data/lib/ngage/jekyll/theme.rb +73 -0
  80. data/lib/ngage/jekyll/theme_builder.rb +121 -0
  81. data/lib/ngage/jekyll/url.rb +160 -0
  82. data/lib/ngage/jekyll/utils.rb +370 -0
  83. data/lib/ngage/jekyll/utils/ansi.rb +57 -0
  84. data/lib/ngage/jekyll/utils/exec.rb +26 -0
  85. data/lib/ngage/jekyll/utils/internet.rb +37 -0
  86. data/lib/ngage/jekyll/utils/platforms.rb +82 -0
  87. data/lib/ngage/jekyll/utils/thread_event.rb +31 -0
  88. data/lib/ngage/jekyll/utils/win_tz.rb +75 -0
  89. data/lib/ngage/site_template/.gitignore +5 -0
  90. data/lib/ngage/site_template/404.html +25 -0
  91. data/lib/ngage/site_template/_config.yml +47 -0
  92. data/lib/ngage/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -0
  93. data/lib/ngage/site_template/about.markdown +18 -0
  94. data/lib/ngage/site_template/index.markdown +6 -0
  95. data/lib/ngage/theme_template/CODE_OF_CONDUCT.md.erb +74 -0
  96. data/lib/ngage/theme_template/Gemfile +4 -0
  97. data/lib/ngage/theme_template/LICENSE.txt.erb +21 -0
  98. data/lib/ngage/theme_template/README.md.erb +52 -0
  99. data/lib/ngage/theme_template/_layouts/default.html +1 -0
  100. data/lib/ngage/theme_template/_layouts/page.html +5 -0
  101. data/lib/ngage/theme_template/_layouts/post.html +5 -0
  102. data/lib/ngage/theme_template/example/_config.yml.erb +1 -0
  103. data/lib/ngage/theme_template/example/_post.md +12 -0
  104. data/lib/ngage/theme_template/example/index.html +14 -0
  105. data/lib/ngage/theme_template/example/style.scss +7 -0
  106. data/lib/ngage/theme_template/gitignore.erb +6 -0
  107. data/lib/ngage/theme_template/theme.gemspec.erb +19 -0
  108. data/lib/ngage/version.rb +5 -0
  109. metadata +328 -0
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
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 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 ParameterLists
40
+
41
+ # Returns source file path.
42
+ def path
43
+ # Static file is from a collection inside custom collections directory
44
+ if !@collection.nil? && !@site.config["collections_dir"].empty?
45
+ File.join(*[@base, @site.config["collections_dir"], @dir, @name].compact)
46
+ else
47
+ File.join(*[@base, @dir, @name].compact)
48
+ end
49
+ end
50
+
51
+ # Obtain destination path.
52
+ #
53
+ # dest - The String path to the destination dir.
54
+ #
55
+ # Returns destination file path.
56
+ def destination(dest)
57
+ @site.in_dest_dir(*[dest, destination_rel_dir, @name].compact)
58
+ end
59
+
60
+ def destination_rel_dir
61
+ if @collection
62
+ File.dirname(url)
63
+ else
64
+ @dir
65
+ end
66
+ end
67
+
68
+ def modified_time
69
+ @modified_time ||= File.stat(path).mtime
70
+ end
71
+
72
+ # Returns last modification time for this file.
73
+ def mtime
74
+ modified_time.to_i
75
+ end
76
+
77
+ # Is source path modified?
78
+ #
79
+ # Returns true if modified since last write.
80
+ def modified?
81
+ self.class.mtimes[path] != mtime
82
+ end
83
+
84
+ # Whether to write the file to the filesystem
85
+ #
86
+ # Returns true unless the defaults for the destination path from
87
+ # _config.yml contain `published: false`.
88
+ def write?
89
+ defaults.fetch("published", true)
90
+ end
91
+
92
+ # Write the static file to the destination directory (if modified).
93
+ #
94
+ # dest - The String path to the destination dir.
95
+ #
96
+ # Returns false if the file was not modified since last time (no-op).
97
+ def write(dest)
98
+ dest_path = destination(dest)
99
+
100
+ return false if File.exist?(dest_path) && !modified?
101
+
102
+ self.class.mtimes[path] = mtime
103
+
104
+ FileUtils.mkdir_p(File.dirname(dest_path))
105
+ FileUtils.rm(dest_path) if File.exist?(dest_path)
106
+ copy_file(dest_path)
107
+
108
+ true
109
+ end
110
+
111
+ def to_liquid
112
+ @to_liquid ||= Drops::StaticFileDrop.new(self)
113
+ end
114
+
115
+ def basename
116
+ File.basename(name, extname)
117
+ end
118
+
119
+ def placeholders
120
+ {
121
+ :collection => @collection.label,
122
+ :path => relative_path[
123
+ @collection.relative_directory.size..relative_path.size],
124
+ :output_ext => "",
125
+ :name => "",
126
+ :title => "",
127
+ }
128
+ end
129
+
130
+ # Applies a similar URL-building technique as Jekyll::Document that takes
131
+ # the collection's URL template into account. The default URL template can
132
+ # be overriden in the collection's configuration in _config.yml.
133
+ def url
134
+ @url ||= if @collection.nil?
135
+ relative_path
136
+ else
137
+ ::Jekyll::URL.new(
138
+ :template => @collection.url_template,
139
+ :placeholders => placeholders
140
+ )
141
+ end.to_s.chomp("/")
142
+ end
143
+
144
+ # Returns the type of the collection if present, nil otherwise.
145
+ def type
146
+ @type ||= @collection.nil? ? nil : @collection.label.to_sym
147
+ end
148
+
149
+ # Returns the front matter defaults defined for the file's URL and/or type
150
+ # as defined in _config.yml.
151
+ def defaults
152
+ @defaults ||= @site.frontmatter_defaults.all url, type
153
+ end
154
+
155
+ private
156
+
157
+ def copy_file(dest_path)
158
+ if @site.safe || Jekyll.env == "production"
159
+ FileUtils.cp(path, dest_path)
160
+ else
161
+ FileUtils.copy_entry(path, dest_path)
162
+ end
163
+
164
+ unless File.symlink?(dest_path)
165
+ File.utime(self.class.mtimes[path], self.class.mtimes[path], dest_path)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class Stevenson < ::Logger
5
+ def initialize
6
+ @progname = nil
7
+ @level = DEBUG
8
+ @default_formatter = Formatter.new
9
+ @logdev = $stdout
10
+ @formatter = proc do |_, _, _, msg|
11
+ msg.to_s
12
+ end
13
+ end
14
+
15
+ def add(severity, message = nil, progname = nil)
16
+ severity ||= UNKNOWN
17
+ @logdev = logdevice(severity)
18
+
19
+ return true if @logdev.nil? || severity < @level
20
+
21
+ progname ||= @progname
22
+ if message.nil?
23
+ if block_given?
24
+ message = yield
25
+ else
26
+ message = progname
27
+ progname = @progname
28
+ end
29
+ end
30
+ @logdev.puts(
31
+ format_message(format_severity(severity), Time.now, progname, message)
32
+ )
33
+ true
34
+ end
35
+
36
+ # Log a +WARN+ message
37
+ def warn(progname = nil, &block)
38
+ add(WARN, nil, progname.yellow, &block)
39
+ end
40
+
41
+ # Log an +ERROR+ message
42
+ def error(progname = nil, &block)
43
+ add(ERROR, nil, progname.red, &block)
44
+ end
45
+
46
+ def close
47
+ # No LogDevice in use
48
+ end
49
+
50
+ private
51
+
52
+ def logdevice(severity)
53
+ if severity > INFO
54
+ $stderr
55
+ else
56
+ $stdout
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
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]+"))?)*)$!
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
+ def render(context)
32
+ prefix = context["highlighter_prefix"] || ""
33
+ suffix = context["highlighter_suffix"] || ""
34
+ code = super.to_s.gsub(%r!\A(\n|\r)+|(\n|\r)+\z!, "")
35
+
36
+ output =
37
+ case context.registers[:site].highlighter
38
+ when "rouge"
39
+ render_rouge(code)
40
+ when "pygments"
41
+ render_pygments(code, context)
42
+ else
43
+ render_codehighlighter(code)
44
+ end
45
+
46
+ rendered_output = add_code_tag(output)
47
+ prefix + rendered_output + suffix
48
+ end
49
+
50
+ private
51
+
52
+ OPTIONS_REGEX = %r!(?:\w="[^"]*"|\w=\w|\w)+!
53
+
54
+ def parse_options(input)
55
+ options = {}
56
+ return options if input.empty?
57
+
58
+ # Split along 3 possible forms -- key="<quoted list>", key=value, or key
59
+ input.scan(OPTIONS_REGEX) do |opt|
60
+ key, value = opt.split("=")
61
+ # If a quoted list, convert to array
62
+ if value&.include?('"')
63
+ value.delete!('"')
64
+ value = value.split
65
+ end
66
+ options[key.to_sym] = value || true
67
+ end
68
+
69
+ options[:linenos] = "inline" if options[:linenos] == true
70
+ options
71
+ end
72
+
73
+ def render_pygments(code, _context)
74
+ Jekyll.logger.warn "Warning:", "Highlight Tag no longer supports rendering with Pygments."
75
+ Jekyll.logger.warn "", "Using the default highlighter, Rouge, instead."
76
+ render_rouge(code)
77
+ end
78
+
79
+ def render_rouge(code)
80
+ require "rouge"
81
+ formatter = ::Rouge::Formatters::HTMLLegacy.new(
82
+ :line_numbers => @highlight_options[:linenos],
83
+ :wrap => false,
84
+ :css_class => "highlight",
85
+ :gutter_class => "gutter",
86
+ :code_class => "code"
87
+ )
88
+ lexer = ::Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
89
+ formatter.format(lexer.lex(code))
90
+ end
91
+
92
+ def render_codehighlighter(code)
93
+ h(code).strip
94
+ end
95
+
96
+ def add_code_tag(code)
97
+ code_attributes = [
98
+ "class=\"language-#{@lang.to_s.tr("+", "-")}\"",
99
+ "data-lang=\"#{@lang}\"",
100
+ ].join(" ")
101
+ "<figure class=\"highlight\"><pre><code #{code_attributes}>"\
102
+ "#{code.chomp}</code></pre></figure>"
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ Liquid::Template.register_tag("highlight", Jekyll::Tags::HighlightBlock)
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Tags
5
+ class IncludeTag < Liquid::Tag
6
+ VALID_SYNTAX = %r!
7
+ ([\w-]+)\s*=\s*
8
+ (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))
9
+ !x
10
+ VARIABLE_SYNTAX = %r!
11
+ (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
12
+ (?<params>.*)
13
+ !mx
14
+
15
+ FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!
16
+ VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!
17
+ INVALID_SEQUENCES = %r![./]{2,}!
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 file =~ INVALID_SEQUENCES || file !~ VALID_FILENAME_CHARS
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 @params =~ FULL_VALID_SYNTAX
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
+ if @file =~ VARIABLE_SYNTAX
94
+ partial = context.registers[:site]
95
+ .liquid_renderer
96
+ .file("(variable)")
97
+ .parse(@file)
98
+ partial.render!(context)
99
+ end
100
+ end
101
+
102
+ def tag_includes_dirs(context)
103
+ context.registers[:site].includes_load_paths.freeze
104
+ end
105
+
106
+ def locate_include_file(context, file, safe)
107
+ includes_dirs = tag_includes_dirs(context)
108
+ includes_dirs.each do |dir|
109
+ path = File.join(dir.to_s, file.to_s)
110
+ return path if valid_include_file?(path, dir.to_s, safe)
111
+ end
112
+ raise IOError, could_not_locate_message(file, includes_dirs, safe)
113
+ end
114
+
115
+ def render(context)
116
+ site = context.registers[:site]
117
+
118
+ file = render_variable(context) || @file
119
+ validate_file_name(file)
120
+
121
+ path = locate_include_file(context, file, site.safe)
122
+ return unless path
123
+
124
+ add_include_to_dependency(site, path, context)
125
+
126
+ partial = load_cached_partial(path, context)
127
+
128
+ context.stack do
129
+ context["include"] = parse_params(context) if @params
130
+ begin
131
+ partial.render!(context)
132
+ rescue Liquid::Error => e
133
+ e.template_name = path
134
+ e.markup_context = "included " if e.markup_context.nil?
135
+ raise e
136
+ end
137
+ end
138
+ end
139
+
140
+ def add_include_to_dependency(site, path, context)
141
+ if context.registers[:page]&.key?("path")
142
+ site.regenerator.add_dependency(
143
+ site.in_source_dir(context.registers[:page]["path"]),
144
+ path
145
+ )
146
+ end
147
+ end
148
+
149
+ def load_cached_partial(path, context)
150
+ context.registers[:cached_partials] ||= {}
151
+ cached_partial = context.registers[:cached_partials]
152
+
153
+ if cached_partial.key?(path)
154
+ cached_partial[path]
155
+ else
156
+ unparsed_file = context.registers[:site]
157
+ .liquid_renderer
158
+ .file(path)
159
+ begin
160
+ cached_partial[path] = unparsed_file.parse(read_file(path, context))
161
+ rescue Liquid::Error => e
162
+ e.template_name = path
163
+ e.markup_context = "included " if e.markup_context.nil?
164
+ raise e
165
+ end
166
+ end
167
+ end
168
+
169
+ def valid_include_file?(path, dir, safe)
170
+ !outside_site_source?(path, dir, safe) && File.file?(path)
171
+ end
172
+
173
+ def outside_site_source?(path, dir, safe)
174
+ safe && !realpath_prefixed_with?(path, dir)
175
+ end
176
+
177
+ def realpath_prefixed_with?(path, dir)
178
+ File.exist?(path) && File.realpath(path).start_with?(dir)
179
+ rescue StandardError
180
+ false
181
+ end
182
+
183
+ # This method allows to modify the file content by inheriting from the class.
184
+ def read_file(file, context)
185
+ File.read(file, file_read_opts(context))
186
+ end
187
+
188
+ private
189
+
190
+ def could_not_locate_message(file, includes_dirs, safe)
191
+ message = "Could not locate the included file '#{file}' in any of "\
192
+ "#{includes_dirs}. Ensure it exists in one of those directories and"
193
+ message + if safe
194
+ " is not a symlink as those are not allowed in safe mode."
195
+ else
196
+ ", if it is a symlink, does not point outside your site source."
197
+ end
198
+ end
199
+ end
200
+
201
+ class IncludeRelativeTag < IncludeTag
202
+ def tag_includes_dirs(context)
203
+ Array(page_path(context)).freeze
204
+ end
205
+
206
+ def page_path(context)
207
+ if context.registers[:page].nil?
208
+ context.registers[:site].source
209
+ else
210
+ site = context.registers[:site]
211
+ page_payload = context.registers[:page]
212
+ resource_path = \
213
+ if page_payload["collection"].nil?
214
+ page_payload["path"]
215
+ else
216
+ File.join(site.config["collections_dir"], page_payload["path"])
217
+ end
218
+ site.in_source_dir File.dirname(resource_path)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ Liquid::Template.register_tag("include", Jekyll::Tags::IncludeTag)
226
+ Liquid::Template.register_tag("include_relative", Jekyll::Tags::IncludeRelativeTag)