liquidoc 0.12.0.pre.rc5 → 0.12.0.pre.rc6

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.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Filters
5
+ module URLFilters
6
+ # Produces an absolute URL based on site.url and site.baseurl.
7
+ #
8
+ # input - the URL to make absolute.
9
+ #
10
+ # Returns the absolute URL as a String.
11
+ def absolute_url(input)
12
+ return if input.nil?
13
+ input = input.url if input.respond_to?(:url)
14
+ return input if Addressable::URI.parse(input.to_s).absolute?
15
+ site = @context.registers[:site]
16
+ return relative_url(input) if site.config["url"].nil?
17
+ Addressable::URI.parse(
18
+ site.config["url"].to_s + relative_url(input)
19
+ ).normalize.to_s
20
+ end
21
+
22
+ # Produces a URL relative to the domain root based on site.baseurl
23
+ # unless it is already an absolute url with an authority (host).
24
+ #
25
+ # input - the URL to make relative to the domain root
26
+ #
27
+ # Returns a URL relative to the domain root as a String.
28
+ def relative_url(input)
29
+ return if input.nil?
30
+ input = input.url if input.respond_to?(:url)
31
+ return input if Addressable::URI.parse(input.to_s).absolute?
32
+
33
+ parts = [sanitized_baseurl, input]
34
+ Addressable::URI.parse(
35
+ parts.compact.map { |part| ensure_leading_slash(part.to_s) }.join
36
+ ).normalize.to_s
37
+ end
38
+
39
+ # Strips trailing `/index.html` from URLs to create pretty permalinks
40
+ #
41
+ # input - the URL with a possible `/index.html`
42
+ #
43
+ # Returns a URL with the trailing `/index.html` removed
44
+ def strip_index(input)
45
+ return if input.nil? || input.to_s.empty?
46
+ input.sub(%r!/index\.html?$!, "/")
47
+ end
48
+
49
+ private
50
+
51
+ def sanitized_baseurl
52
+ site = @context.registers[:site]
53
+ site.config["baseurl"].to_s.chomp("/")
54
+ end
55
+
56
+ def ensure_leading_slash(input)
57
+ return input if input.nil? || input.empty? || input.start_with?("/")
58
+ "/#{input}"
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,131 @@
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
+ "WORKS!"
18
+ end
19
+
20
+ def render(context)
21
+ prefix = context["highlighter_prefix"] || ""
22
+ suffix = context["highlighter_suffix"] || ""
23
+ code = super.to_s.gsub(%r!\A(\n|\r)+|(\n|\r)+\z!, "")
24
+
25
+ is_safe = !!context.registers[:site].safe
26
+
27
+ output =
28
+ case context.registers[:site].highlighter
29
+ when "pygments"
30
+ render_pygments(code, is_safe)
31
+ when "rouge"
32
+ render_rouge(code)
33
+ else
34
+ render_codehighlighter(code)
35
+ end
36
+
37
+ rendered_output = add_code_tag(output)
38
+ prefix + rendered_output + suffix
39
+ end
40
+
41
+ def sanitized_opts(opts, is_safe)
42
+ if is_safe
43
+ Hash[[
44
+ [:startinline, opts.fetch(:startinline, nil)],
45
+ [:hl_lines, opts.fetch(:hl_lines, nil)],
46
+ [:linenos, opts.fetch(:linenos, nil)],
47
+ [:encoding, opts.fetch(:encoding, "utf-8")],
48
+ [:cssclass, opts.fetch(:cssclass, nil)],
49
+ ].reject { |f| f.last.nil? }]
50
+ else
51
+ opts
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ OPTIONS_REGEX = %r!(?:\w="[^"]*"|\w=\w|\w)+!
58
+
59
+ def parse_options(input)
60
+ options = {}
61
+ return options if input.empty?
62
+
63
+ # Split along 3 possible forms -- key="<quoted list>", key=value, or key
64
+ input.scan(OPTIONS_REGEX) do |opt|
65
+ key, value = opt.split("=")
66
+ # If a quoted list, convert to array
67
+ if value && value.include?('"')
68
+ value.delete!('"')
69
+ value = value.split
70
+ end
71
+ options[key.to_sym] = value || true
72
+ end
73
+
74
+ options[:linenos] = "inline" if options[:linenos] == true
75
+ options
76
+ end
77
+
78
+ def render_pygments(code, is_safe)
79
+ Jekyll::External.require_with_graceful_fail("pygments") unless defined?(Pygments)
80
+
81
+ highlighted_code = Pygments.highlight(
82
+ code,
83
+ :lexer => @lang,
84
+ :options => sanitized_opts(@highlight_options, is_safe)
85
+ )
86
+
87
+ if highlighted_code.nil?
88
+ Jekyll.logger.error <<-MSG
89
+ There was an error highlighting your code:
90
+
91
+ #{code}
92
+
93
+ While attempting to convert the above code, Pygments.rb returned an unacceptable value.
94
+ This is usually a timeout problem solved by running `jekyll build` again.
95
+ MSG
96
+ raise ArgumentError, "Pygments.rb returned an unacceptable value "\
97
+ "when attempting to highlight some code."
98
+ end
99
+
100
+ highlighted_code.sub('<div class="highlight"><pre>', "").sub("</pre></div>", "")
101
+ end
102
+
103
+ def render_rouge(code)
104
+ formatter = Jekyll::Utils::Rouge.html_formatter(
105
+ :line_numbers => @highlight_options[:linenos],
106
+ :wrap => false,
107
+ :css_class => "highlight",
108
+ :gutter_class => "gutter",
109
+ :code_class => "code"
110
+ )
111
+ lexer = ::Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
112
+ formatter.format(lexer.lex(code))
113
+ end
114
+
115
+ def render_codehighlighter(code)
116
+ h(code).strip
117
+ end
118
+
119
+ def add_code_tag(code)
120
+ code_attributes = [
121
+ "class=\"language-#{@lang.to_s.tr("+", "-")}\"",
122
+ "data-lang=\"#{@lang}\"",
123
+ ].join(" ")
124
+ "<figure class=\"highlight\"><pre><code #{code_attributes}>"\
125
+ "#{code.chomp}</code></pre></figure>"
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ Liquid::Template.register_tag("highlight", Jekyll::Tags::HighlightBlock)
@@ -0,0 +1,219 @@
1
+ # (C) Jekyll, this file contains some of Jekyll's custom Liquid filters
2
+ # It is placed in lib/liquid/jekyll for this reason, even though the module/class
3
+ # path is Jekyll::Tags::, so as not to conflict with the Liquid:: path.
4
+
5
+ # frozen_string_literal: true
6
+
7
+ module Jekyll
8
+ module Tags
9
+ class IncludeTagError < StandardError
10
+ attr_accessor :path
11
+
12
+ def initialize(msg, path)
13
+ super(msg)
14
+ @path = path
15
+ end
16
+ end
17
+
18
+ class IncludeTag < Liquid::Tag
19
+ # matches proper params format only
20
+ # 1(key-formatted str) = (2(double-quoted str) or 3(single-quoted str) 4(var-formatted str))
21
+ VALID_SYNTAX = %r!
22
+ ([\w-]+)\s*=\s*
23
+ (?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([a-z][\w'"\[\]\.-]*))
24
+ !x
25
+ # extracts filename as #{variable} and k/v pairs as #{params}
26
+ VARIABLE_SYNTAX = %r!
27
+ (?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)
28
+ (?<params>.*)
29
+ !mx
30
+
31
+ FULL_VALID_SYNTAX = %r!\A\s*(?:#{VALID_SYNTAX}(?=\s|\z)\s*)*\z!
32
+ VALID_FILENAME_CHARS = %r!^[\w/\.-]+$!
33
+ INVALID_SEQUENCES = %r![./]{2,}!
34
+
35
+ def initialize(tag_name, markup, tokens)
36
+ super
37
+ matched = markup.strip.match(VARIABLE_SYNTAX)
38
+ if matched # include passes filename as variable
39
+ @file = matched["variable"].strip # The file to include (as a var)
40
+ @params = matched["params"].strip # The paired vars to load
41
+ else # if the filename isn't a variable, just grab the first arg as filename and rest as params
42
+ @file, @params = markup.strip.split(%r!\s+!, 2)
43
+ end
44
+ validate_params if @params
45
+ @tag_name = tag_name
46
+ end
47
+
48
+ def syntax_example
49
+ "{% #{@tag_name} file.ext param='value' param2='value' %}"
50
+ end
51
+
52
+ def parse_params(context)
53
+ params = {}
54
+ markup = @params
55
+ while (match = VALID_SYNTAX.match(markup))
56
+ # run until syntax no longer matches parameters
57
+ markup = markup[match.end(0)..-1]
58
+ # set val by which group matched in VALID_SYNTAX
59
+ # either a quoted string (2,3) or a variable (4)
60
+ value = if match[2]
61
+ match[2].gsub(%r!\\"!, '"')
62
+ elsif match[3]
63
+ match[3].gsub(%r!\\'!, "'")
64
+ elsif match[4] # val is resolved context var
65
+ context[match[4]]
66
+ end
67
+ params[match[1]] = value # inserts param
68
+ end
69
+ params # returns hash for the include scope
70
+ end
71
+
72
+ def validate_file_name(file)
73
+ if file =~ INVALID_SEQUENCES || file !~ VALID_FILENAME_CHARS
74
+ raise ArgumentError, <<-MSG
75
+ Invalid syntax for include tag. File contains invalid characters or sequences:
76
+
77
+ #{file}
78
+
79
+ Valid syntax:
80
+
81
+ #{syntax_example}
82
+
83
+ MSG
84
+ end
85
+ end
86
+
87
+ def validate_params
88
+ unless @params =~ FULL_VALID_SYNTAX
89
+ raise ArgumentError, <<-MSG
90
+ Invalid syntax for include tag:
91
+
92
+ #{@params}
93
+
94
+ Valid syntax:
95
+
96
+ #{syntax_example}
97
+
98
+ MSG
99
+ end
100
+ end
101
+
102
+ # # Grab file read opts in the context
103
+ # def file_read_opts(context)
104
+ # context.registers[:site].file_read_opts
105
+ # end
106
+
107
+ # Express the filename from the variable
108
+ # Passes along the context in which it was called, from the parent file
109
+ def render_variable(context)
110
+ Liquid::Template.parse(@file).render(context) if @file =~ VARIABLE_SYNTAX
111
+ end
112
+
113
+ # Array of directories where includes are stored
114
+ def tag_includes_dirs(context)
115
+ # context[:includes_dirs]
116
+ ['_templates','_templates/liquid','_templates/liquid/ops','theme/_includes','_theme/layouts']
117
+ end
118
+
119
+ # Traverse includes dirs, setting paths for includes
120
+ def locate_include_file(context, file, safe)
121
+ includes_dirs = ['_templates','_templates/liquid','_templates/liquid/ops','_templates/ops','theme/_includes','theme/_layouts']
122
+ includes_dirs.each do |dir|
123
+ path = File.join(dir.to_s, file.to_s)
124
+ return path if File.exist?(path)
125
+ end
126
+ raise IOError, could_not_locate_message(file, includes_dirs, safe)
127
+ end
128
+
129
+ # recall/render the included partial and place it in the parent doc
130
+ def render(context)
131
+ file = render_variable(context) || @file # use parsed variable filename unless passed explicit filename
132
+ validate_file_name(file)
133
+ path = locate_include_file(context, file, true) # ensure file exists in safe path
134
+ return unless path
135
+ # # ???????
136
+ # add_include_to_dependency(site, path, context)
137
+ #
138
+ # Load the partial if it's identical to one we've already loaded ???
139
+ partial = File.read(path) # reads the template file
140
+ partial = Liquid::Template.parse(partial) # compiles template
141
+ # setup and perform render
142
+ context.stack do
143
+ # create a hash object for any passed k/v pair args
144
+ # by parsing passed parameters using the parent file's scope
145
+ context["include"] = parse_params(context) if @params
146
+ begin # render the include for output
147
+ partial.render!(context)
148
+ rescue Liquid::Error => e
149
+ e.template_name = path
150
+ e.markup_context = "included " if e.markup_context.nil?
151
+ raise e
152
+ end
153
+ end
154
+ end
155
+
156
+ # #
157
+ # def add_include_to_dependency(site, path, context)
158
+ # if context.registers[:page] && context.registers[:page].key?("path")
159
+ # site.regenerator.add_dependency(
160
+ # site.in_source_dir(context.registers[:page]["path"]),
161
+ # path
162
+ # )
163
+ # end
164
+ # end
165
+
166
+ def load_cached_partial(path, context)
167
+ context.registers[:cached_partials] ||= {}
168
+ cached_partial = context.registers[:cached_partials]
169
+
170
+ if cached_partial.key?(path)
171
+ cached_partial[path]
172
+ else
173
+ unparsed_file = context.registers[:globals]
174
+ .liquid_renderer
175
+ .file(path)
176
+ begin
177
+ # Cache a version of the
178
+ cached_partial[path] = unparsed_file.parse(read_file(path, context))
179
+ rescue Liquid::Error => e
180
+ e.template_name = path
181
+ e.markup_context = "included " if e.markup_context.nil?
182
+ raise e
183
+ end
184
+ end
185
+ end
186
+
187
+ def outside_site_source?(path, dir, safe)
188
+ safe && !realpath_prefixed_with?(path, dir)
189
+ end
190
+
191
+ def realpath_prefixed_with?(path, dir)
192
+ File.exist?(path) && File.realpath(path).start_with?(dir)
193
+ rescue StandardError
194
+ false
195
+ end
196
+
197
+ # This method allows to modify the file content by inheriting from the class.
198
+ def read_file(file, context)
199
+ File.read(file)
200
+ end
201
+
202
+ private
203
+
204
+ def could_not_locate_message(file, includes_dirs, safe)
205
+ message = "Could not locate the included file '#{file}' in any of "\
206
+ "#{includes_dirs}. Ensure it exists in one of those directories and"
207
+ message + if safe
208
+ " is not a symlink as those are not allowed in safe mode."
209
+ else
210
+ ", if it is a symlink, does not point outside your site source."
211
+ end
212
+ end
213
+ end
214
+
215
+ end # Tags
216
+
217
+ end
218
+
219
+ Liquid::Template.register_tag("include", Jekyll::Tags::IncludeTag)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Tags
5
+ class Link < Liquid::Tag
6
+ class << self
7
+ def tag_name
8
+ self.name.split("::").last.downcase
9
+ end
10
+ end
11
+
12
+ def initialize(tag_name, relative_path, tokens)
13
+ super
14
+
15
+ @relative_path = relative_path.strip
16
+ end
17
+
18
+ def render(context)
19
+ site = context.registers[:site]
20
+
21
+ site.each_site_file do |item|
22
+ return item.url if item.relative_path == @relative_path
23
+ # This takes care of the case for static files that have a leading /
24
+ return item.url if item.relative_path == "/#{@relative_path}"
25
+ end
26
+
27
+ raise ArgumentError, <<-MSG
28
+ Could not find document '#{@relative_path}' in tag '#{self.class.tag_name}'.
29
+
30
+ Make sure the document exists and the path is correct.
31
+ MSG
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ Liquid::Template.register_tag(Jekyll::Tags::Link.tag_name, Jekyll::Tags::Link)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Tags
5
+ class PostComparer
6
+ MATCHER = %r!^(.+/)*(\d+-\d+-\d+)-(.*)$!
7
+
8
+ attr_reader :path, :date, :slug, :name
9
+
10
+ def initialize(name)
11
+ @name = name
12
+
13
+ all, @path, @date, @slug = *name.sub(%r!^/!, "").match(MATCHER)
14
+ unless all
15
+ raise Jekyll::Errors::InvalidPostNameError,
16
+ "'#{name}' does not contain valid date and/or title."
17
+ end
18
+
19
+ escaped_slug = Regexp.escape(slug)
20
+ @name_regex = %r!^_posts/#{path}#{date}-#{escaped_slug}\.[^.]+|
21
+ ^#{path}_posts/?#{date}-#{escaped_slug}\.[^.]+!x
22
+ end
23
+
24
+ def post_date
25
+ @post_date ||= Utils.parse_date(date,
26
+ "\"#{date}\" does not contain valid date and/or title.")
27
+ end
28
+
29
+ def ==(other)
30
+ other.relative_path.match(@name_regex)
31
+ end
32
+
33
+ def deprecated_equality(other)
34
+ slug == post_slug(other) &&
35
+ post_date.year == other.date.year &&
36
+ post_date.month == other.date.month &&
37
+ post_date.day == other.date.day
38
+ end
39
+
40
+ private
41
+ # Construct the directory-aware post slug for a Jekyll::Post
42
+ #
43
+ # other - the Jekyll::Post
44
+ #
45
+ # Returns the post slug with the subdirectory (relative to _posts)
46
+ def post_slug(other)
47
+ path = other.basename.split("/")[0...-1].join("/")
48
+ if path.nil? || path == ""
49
+ other.data["slug"]
50
+ else
51
+ path + "/" + other.data["slug"]
52
+ end
53
+ end
54
+ end
55
+
56
+ class PostUrl < Liquid::Tag
57
+ def initialize(tag_name, post, tokens)
58
+ super
59
+ @orig_post = post.strip
60
+ begin
61
+ @post = PostComparer.new(@orig_post)
62
+ rescue StandardError => e
63
+ raise Jekyll::Errors::PostURLError, <<-MSG
64
+ Could not parse name of post "#{@orig_post}" in tag 'post_url'.
65
+
66
+ Make sure the post exists and the name is correct.
67
+
68
+ #{e.class}: #{e.message}
69
+ MSG
70
+ end
71
+ end
72
+
73
+ def render(context)
74
+ site = context.registers[:site]
75
+
76
+ site.posts.docs.each do |p|
77
+ return p.url if @post == p
78
+ end
79
+
80
+ # New matching method did not match, fall back to old method
81
+ # with deprecation warning if this matches
82
+
83
+ site.posts.docs.each do |p|
84
+ next unless @post.deprecated_equality p
85
+ Jekyll::Deprecator.deprecation_message "A call to "\
86
+ "'{% post_url #{@post.name} %}' did not match " \
87
+ "a post using the new matching method of checking name " \
88
+ "(path-date-slug) equality. Please make sure that you " \
89
+ "change this tag to match the post's name exactly."
90
+ return p.url
91
+ end
92
+
93
+ raise Jekyll::Errors::PostURLError, <<-MSG
94
+ Could not find post "#{@orig_post}" in tag 'post_url'.
95
+
96
+ Make sure the post exists and the name is correct.
97
+ MSG
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ Liquid::Template.register_tag("post_url", Jekyll::Tags::PostUrl)
@@ -1,3 +1,3 @@
1
1
  module Liquidoc
2
- VERSION = "0.12.0-rc5"
2
+ VERSION = "0.12.0-rc6"
3
3
  end