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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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