jekyll_href 1.0.12 → 1.0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33ad179cd8d6770d3828246bdc51cdbe398f7fd3f50b0101481e2dacd597191e
4
- data.tar.gz: 543a944799bdb811fd84f3afb4ce0b8239e2008208feb0612360307bd1843c69
3
+ metadata.gz: f6a4d85b016759a94f6242ea415a19cb5870f1e3b51e7f4c5141f95f6c817877
4
+ data.tar.gz: 072b91a1e7919924daecb086549caaad4f30a071a88b9ad4b32c81d92056d946
5
5
  SHA512:
6
- metadata.gz: be0fd539b076cedb893cee10a528f4a3c71d2ec01e84c16a26455ec4c4aacded321bc11f96ac8ff5d6647b2cdf3692aba9bb843e419d67c54aab9a47edb63ff9
7
- data.tar.gz: f2d3abce3b08922eefa404905d1f61f319150662f891dd03171e716479d16e068e4e2a02b9975b5f609716f942876ee9b7d542d14236dba141fb4fcaa6e3ce66
6
+ metadata.gz: e816084465970281cfc16416f2cfcd30807d6f318833d9dfa39e2a48f178f3e0aa865f1ef5e53b4b6be90160fe9d0a6647cbb2eb166254953711e210c82e1fd4
7
+ data.tar.gz: 146e99be071aeae009e47dff919265b569bc4d314a4239a87d02606d4a8d72522096be01379fda529fbe194120202ae1f298b4a541686e554f95dd2632a8f40b
data/.rubocop.yml CHANGED
@@ -1,6 +1,6 @@
1
- require: rubocop-jekyll
2
- inherit_gem:
3
- rubocop-jekyll: .rubocop.yml
1
+ #require: rubocop-jekyll
2
+ #inherit_gem:
3
+ # rubocop-jekyll: .rubocop.yml
4
4
 
5
5
  AllCops:
6
6
  Exclude:
@@ -9,8 +9,11 @@ AllCops:
9
9
  NewCops: enable
10
10
  TargetRubyVersion: 2.6
11
11
 
12
- Gemspec/RequireMFA:
13
- Enabled: false
12
+ # Gemspec/RequireMFA:
13
+ # Enabled: false
14
+
15
+ Layout/HashAlignment:
16
+ EnforcedHashRocketStyle: table
14
17
 
15
18
  Layout/LineLength:
16
19
  Max: 150
@@ -32,3 +35,6 @@ Style/StringLiterals:
32
35
 
33
36
  Style/StringLiteralsInInterpolation:
34
37
  Enabled: false
38
+
39
+ Style/TrailingCommaInHashLiteral:
40
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 1.0.13 / 2022-12-14
2
+ * Links with embedded spaces are now supported when the new 'url' named parameter is used. For example, instead of specifying:
3
+ ```
4
+ {% href http://link.com with space.html some text %}
5
+ ```
6
+ Instead specify (single and double quotes are equally valid):
7
+ ```
8
+ {% href url="http://link.com with space.html" some text %}
9
+ ```
10
+ * URLs can now contain environment variable references. For example, if $domain and $uri are environment variables:
11
+ ```
12
+ {% href url="http://$domain.html" some text %}
13
+ {% href url="$uri" some text %}
14
+ ```
15
+
1
16
  ## 1.0.12 / 2022-08-09
2
17
  * No longer abends if `plugin-vars` is not present in `_config.yml`
3
18
 
data/README.md CHANGED
@@ -3,24 +3,114 @@
3
3
  ===========
4
4
 
5
5
  `Jekyll_href` is a Jekyll plugin that provides a new Liquid tag: `href`.
6
- The Liquid tag generates and `<a href>` HTML tag, by default containing `target="_blank"` and `rel=nofollow`.
7
- To suppress the `nofollow` attribute, preface the link with the word `follow`.
8
- To suppress the `target` attribute, preface the link with the word `notarget`.
9
- Also provides a convenient way to generate formatted and clickable URIs.
6
+ It provides a convenient way to generate formatted and clickable URIs.
7
+ The Liquid tag generates an `a href` HTML tag, which by default contains `target="_blank"` and `rel=nofollow`.
8
+
9
+ If the url starts with `http`, or the `match` keyword is specified:
10
+ - The url will open in a new tab or window.
11
+ - The url will include `rel=nofollow` for SEO purposes.
12
+
13
+ CAUTION: if linked text contains a single or double quote you will see the error message: `Liquid Exception: Unmatched quote`. Instead, use &lsquo;, &rsquo;, &ldquo;, &rdquo;
14
+
15
+ In `_config.yml`, if a section called `plugin-vars` exists,
16
+ then its name/value pairs are available for substitution.
17
+ ```yaml
18
+ plugin-vars:
19
+ django-github: 'https://github.com/django/django/blob/3.1.7'
20
+ django-oscar-github: 'https://github.com/django-oscar/django-oscar/blob/3.0.2'
21
+ ```
10
22
 
11
23
 
12
- ### Syntax:
24
+ ## Syntax 1 (requires `url` does not have embedded spaces):
13
25
  ```
14
- {% href [match | [follow] [notarget]] [url] text to display %}
26
+ {% href [match | [follow] [notarget]] url text to display %}
15
27
  ```
16
- Note that the url should not be enclosed in quotes.
17
- Also please note that the square brackets denote optional parameters, and should not be typed.
28
+ 1. The url must be a single token, without embedded spaces.
29
+ 2. The url need not be enclosed in quotes.
30
+ 3. The square brackets denote optional keyword parameters, and should not be typed.
18
31
 
19
- `match` will attempt to match the url fragment (specified as a regex) to a URL in any collection.
20
- If multiple documents have matching URL an error is thrown.
32
+
33
+ ## Syntax 2 (always works):
34
+ This syntax is recommended when the URL contains a colon (:).
35
+ ```
36
+ {% href [match | [follow] [notarget]]
37
+ url="http://link.com with space.html" some text %}
38
+
39
+ {% href [match | [follow] [notarget]]
40
+ url='http://link.com with space.html' some text %}
41
+ ```
42
+ 1. Each of the above examples contain an embedded newline, which is legal.
43
+ 2. The url must be enclosed by either single or double quotes.
44
+ 3. The square brackets denote optional keyword parameters, and should not be typed.
45
+
46
+
47
+ ## Environment Variable Expansion
48
+ URLs can contain environment variable references.
49
+ For example, if `$domain`, `$uri` and `$USER` are environment variables:
50
+ ```
51
+ {% href http://$domain.html some text %}
52
+
53
+ {% href url="$uri" some text %}
54
+
55
+ {% href https://mslinn.html <code>USER=$USER</code> %}
56
+ ```
57
+
58
+ ## Optional Parameters
59
+ ### `follow`
60
+ To suppress the `nofollow` attribute, preface the link with the word `follow`.
61
+
62
+
63
+ ### `notarget`
64
+ To suppress the `target` attribute, preface the link with the word `notarget`.
21
65
 
22
66
 
23
- ### Additional Information
67
+ ### `match`
68
+ `match` will attempt to match the url fragment (specified as a regex) to a URL in any collection.
69
+ If multiple documents have matching URL an error is thrown.
70
+ The `match` option looks through the pages collection for a URL with containing the provided substring.
71
+ `Match` implies `follow` and `notarget`.
72
+
73
+
74
+ ## Examples
75
+ 1. Generates `nofollow` and `target` attributes:
76
+ ```
77
+ {% href https://mslinn.com The Awesome %}
78
+ ```
79
+
80
+ 2. Does not generate `nofollow` or `target` attributes.
81
+ ```
82
+ {% href follow notarget https://mslinn.com The Awesome %}
83
+ ```
84
+
85
+ 3. Does not generate `nofollow` attribute.
86
+ ```
87
+ {% href follow https://mslinn.com The Awesome %}
88
+ ```
89
+
90
+ 4. Does not generate `target` attribute.
91
+ ```
92
+ {% href notarget https://mslinn.com The Awesome %}
93
+ ```
94
+
95
+ 5. Matches page with URL containing abc.
96
+ ```
97
+ {% href match abc The Awesome %}
98
+ ```
99
+
100
+ 6. Matches page with URL containing abc.
101
+ ```
102
+ {% href match abc.html#tag The Awesome %}
103
+ ```
104
+
105
+ 7. Substitute name/value pair for the `django-github` variable defined above:
106
+ ```
107
+ {% href {{django-github}}/django/core/management/__init__.py#L398-L401
108
+ <code>django.core.management.execute_from_command_line</code> %}
109
+ ```
110
+ Substitutions are only made to the URL, not to the linked text.
111
+
112
+
113
+ ## Additional Information
24
114
  More information is available on my web site about [my Jekyll plugins](https://www.mslinn.com/blog/2020/10/03/jekyll-plugins.html).
25
115
 
26
116
 
@@ -42,9 +132,10 @@ Or install it yourself as:
42
132
 
43
133
  $ gem install jekyll_href
44
134
 
45
- ## Usage
46
135
 
47
- ### Defaults
136
+ ## Generated HTML
137
+
138
+ ### Without Keywords
48
139
  ```
49
140
  {% href https://mslinn.com The Awesome %}
50
141
  ```
@@ -56,7 +147,7 @@ Expands to this:
56
147
 
57
148
  Which renders as this: <a href='https://mslinn.com' target='_blank' rel='nofollow'>The Awesome</a>
58
149
 
59
- ### `follow`
150
+ ### With `follow`
60
151
  ```
61
152
  {% href follow https://mslinn.com The Awesome %}
62
153
  ```
@@ -67,7 +158,7 @@ Expands to this:
67
158
  ```
68
159
 
69
160
 
70
- ### `notarget`
161
+ ### With `notarget`
71
162
  ```
72
163
  {% href notarget https://mslinn.com The Awesome %}
73
164
  ```
@@ -78,7 +169,7 @@ Expands to this:
78
169
  ```
79
170
 
80
171
 
81
- ### `follow notarget`
172
+ ### With `follow notarget`
82
173
  ```
83
174
  {% href follow notarget https://mslinn.com The Awesome %}
84
175
  ```
@@ -88,7 +179,7 @@ Expands to this:
88
179
  <a href='https://mslinn.com'>The Awesome</a>
89
180
  ```
90
181
 
91
- ### `match`
182
+ ### With `match`
92
183
  Looks for a post with a matching URL.
93
184
  ```
94
185
  {% href match setting-up-django-oscar.html tutorial site %}
@@ -109,6 +200,7 @@ Expands to this:
109
200
  ```
110
201
  Which renders as: [`mslinn.com`](https://mslinn.com)
111
202
 
203
+
112
204
  ## Development
113
205
 
114
206
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -123,6 +215,21 @@ To install this gem onto your local machine, run:
123
215
  $ bundle exec rake install
124
216
  ```
125
217
 
218
+ ## Test
219
+ A test web site is provided in the `demo` directory.
220
+ 1. Set breakpoints.
221
+
222
+ 2. Initiate a debug session from the command line:
223
+ ```shell
224
+ $ bin/attach demo
225
+ ```
226
+
227
+ 3. Once the `Fast Debugger` signon appears, launch the test configuration called `Attach rdebug-ide`.
228
+
229
+ 4. View the generated website at [`http://localhost:4444`](http://localhost:4444)
230
+
231
+
232
+ ## Release
126
233
  To release a new version,
127
234
  1. Update the version number in `version.rb`.
128
235
  2. Commit all changes to git; if you don't the next step might fail with an unexplainable error message.
data/jekyll_href.gemspec CHANGED
@@ -36,6 +36,8 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency 'jekyll', '>= 3.5.0'
37
37
  spec.add_dependency 'jekyll_all_collections'
38
38
  spec.add_dependency 'jekyll_plugin_logger'
39
+ spec.add_dependency 'key-value-parser'
40
+ spec.add_dependency 'shellwords'
39
41
 
40
42
  # spec.add_development_dependency 'debase'
41
43
  # spec.add_development_dependency 'rubocop-jekyll'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllHrefVersion
4
- VERSION = "1.0.12"
4
+ VERSION = "1.0.13"
5
5
  end
data/lib/jekyll_href.rb CHANGED
@@ -1,136 +1,143 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "jekyll_plugin_logger"
4
3
  require "jekyll_all_collections"
4
+ require "jekyll_plugin_logger"
5
5
  require "liquid"
6
6
  require_relative "jekyll_href/version"
7
+ require_relative './jekyll_tag_helper2'
7
8
 
8
9
  # @author Copyright 2020 Michael Slinn
9
10
  # @license SPDX-License-Identifier: Apache-2.0
10
- #
11
11
  # Generates an href.
12
- # Note that the url should not be enclosed in quotes.
13
- #
14
- # If the link starts with 'http' or `match` is specified:
15
- # The link will open in a new tab or window
16
- # The link will include `rel=nofollow` for SEO purposes.
17
- #
18
- # To suppress the `nofollow` attribute, preface the link with the word `follow`.
19
- # To suppress the `target` attribute, preface the link with the word `notarget`.
20
- #
21
- # The `match` option looks through the pages collection for a URL with containing the provided substring.
22
- # Match implies follow and notarget.
23
- #
24
- # If a section called plugin-vars exists then its name/value pairs are available for substitution.
25
- # plugin-vars:
26
- # django-github: 'https://github.com/django/django/blob/3.1.7'
27
- # django-oscar-github: 'https://github.com/django-oscar/django-oscar/blob/3.0.2'
28
- #
29
- #
30
- # @example General form
31
- # {% href [follow] [notarget] [match] url text to display %}
32
- #
33
- # @example Generates `nofollow` and `target` attributes.
34
- # {% href https://mslinn.com The Awesome %}
35
- #
36
- # @example Does not generate `nofollow` or `target` attributes.
37
- # {% href follow notarget https://mslinn.com The Awesome %}
38
- #
39
- # @example Does not generate `nofollow` attribute.
40
- # {% href follow https://mslinn.com The Awesome %}
41
- #
42
- # @example Does not generate `target` attribute.
43
- # {% href notarget https://mslinn.com The Awesome %}
44
- #
45
- # @example Matches page with URL containing abc.
46
- # {% href match abc The Awesome %}
47
- # @example Matches page with URL containing abc.
48
- # {% href match abc.html#tag The Awesome %}
49
- #
50
- # @example Substitute name/value pair for the django-github variable:
51
- # {% href {{django-github}}/django/core/management/__init__.py#L398-L401
52
- # <code>django.core.management.execute_from_command_line</code> %}
53
-
54
- class ExternalHref < Liquid::Tag
12
+
13
+ # Implements href Jekyll tag
14
+ class ExternalHref < Liquid::Tag # rubocop:disable Metrics/ClassLength
15
+ attr_reader :follow, :helper, :line_number, :match, :page, :path, :site, :text, :target, :url
16
+ attr_accessor :link
55
17
 
56
18
  # @param tag_name [String] is the name of the tag, which we already know.
57
- # @param command_line [Hash, String, Liquid::Tag::Parser] the arguments from the web page.
58
- # @param _parse_context [Liquid::ParseContext] tokenized command line
19
+ # @param markup [String] the arguments from the web page.
20
+ # @param _tokens [Liquid::ParseContext] tokenized command line
21
+ # By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
22
+ # a boolean parameter that determines if error messages should display the line number the error occurred.
23
+ # This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
24
+ # See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
59
25
  # @return [void]
60
- def initialize(tag_name, command_line, _parse_context)
26
+ def initialize(tag_name, markup, _tokens)
61
27
  super
28
+ markup = '' if markup.nil?
29
+ markup.strip!
62
30
 
63
31
  @logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
64
- @match = false
65
- @tokens = command_line.strip.split
66
- @follow = get_value("follow", " rel='nofollow'")
67
- @target = get_value("notarget", " target='_blank'")
68
-
69
- match_index = @tokens.index("match")
70
- if match_index
71
- @tokens.delete_at(match_index)
72
- @follow = ""
73
- @match = true
74
- @target = ""
75
- end
76
-
77
- finalize @tokens
32
+ @helper = JekyllTagHelper2.new(tag_name, markup, @logger)
78
33
  end
79
34
 
80
35
  # Method prescribed by the Jekyll plugin lifecycle.
81
36
  # @param liquid_context [Liquid::Context]
82
37
  # @return [String]
83
38
  def render(liquid_context)
84
- @site = liquid_context.registers[:site]
85
- JekyllAllCollections::maybe_compute_all_collections(@site)
86
-
87
- match(liquid_context) if @match
88
- link = replace_vars(liquid_context, @link)
89
- @target = @follow = "" if link.start_with? "mailto:"
90
- @logger.debug { "@link=#{@link}; link=#{link}" }
91
- "<a href='#{link}'#{@target}#{@follow}>#{@text}</a>"
39
+ super
40
+ globals_initial(liquid_context)
41
+ linkk = compute_linkk
42
+ linkk = replace_vars(linkk)
43
+ @link_save = linkk
44
+ @helper_save = @helper.clone
45
+ globals_update(@helper.argv, linkk) # Sets @link and @text, might clear @follow and @target
46
+ handle_match if @match
47
+ "<a href='#{@link}'#{@target}#{@follow}>#{@text}</a>"
92
48
  end
93
49
 
94
50
  private
95
51
 
96
- def finalize(tokens)
97
- @link = tokens.shift
98
-
99
- @text = tokens.join(" ").strip
100
- if @text.empty?
101
- @text = "<code>#{@link}</code>"
102
- @link = "https://#{@link}"
52
+ def compute_linkk # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
53
+ # Does not look at or compute @link
54
+ linkk = @url
55
+ if linkk.nil? || !linkk
56
+ linkk = @helper.argv&.shift
57
+ @helper.params&.shift
58
+ @helper.keys_values&.delete(linkk)
59
+ dump_linkk_relations(linkk) if linkk.nil?
60
+ elsif @url.to_s.empty?
61
+ dump_linkk_relations(linkk)
103
62
  end
63
+ linkk
64
+ end
104
65
 
105
- unless @link.start_with? "http"
106
- @follow = ""
107
- @target = ""
108
- end
66
+ def dump_linkk_relations(linkk) # rubocop:disable Metrics/MethodLength
67
+ msg = <<~END_MESSAGE
68
+ jekyll_href error: no url was provided on #{@path}:#{@line_number}.
69
+ @helper.markup=#{@helper.markup}
70
+ @helper.argv='#{@helper.argv}'
71
+ linkk='#{linkk}'
72
+ @match='#{@match}'
73
+ @url='#{@url}'
74
+ @follow='#{@follow}
75
+ @target='#{@target}'
76
+ END_MESSAGE
77
+ abort msg.red
109
78
  end
110
79
 
111
- def get_value(token, default_value)
112
- value = default_value
113
- target_index = @tokens.index(token)
114
- if target_index
115
- @tokens.delete_at(target_index)
116
- value = ""
80
+ def globals_initial(liquid_context)
81
+ # Sets @follow, @helper, @match, @page, @path, @site, @target, @url
82
+ @helper.liquid_context = liquid_context
83
+
84
+ @page = liquid_context.registers[:page]
85
+ @path = @page['path']
86
+ @site = liquid_context.registers[:site]
87
+ JekyllAllCollections.maybe_compute_all_collections(@site)
88
+
89
+ @follow = @helper.parameter_specified?('follow') ? '' : " rel='nofollow'"
90
+ @match = @helper.parameter_specified?('match')
91
+ @target = @helper.parameter_specified?('notarget') ? '' : " target='_blank'"
92
+ @url = @helper.parameter_specified?('url')
93
+ end
94
+
95
+ def globals_update(tokens, linkk) # rubocop:disable Metrics/MethodLength
96
+ # Might set @follow, @linkk, @target, and @text
97
+ if linkk.start_with? 'mailto:'
98
+ @link = linkk
99
+ @target = @follow = ''
100
+ @text = @helper.argv.join(' ')
101
+ if @text.empty?
102
+ text = linkk.delete_prefix('mailto:')
103
+ @text = "<code>#{text}</code>"
104
+ end
105
+ return
106
+ else
107
+ @text = tokens.join(" ").strip
108
+ if @text.to_s.empty?
109
+ @text = "<code>#{linkk}</code>"
110
+ @link = "https://#{linkk}"
111
+ else
112
+ @link = linkk
113
+ end
117
114
  end
118
- value
115
+
116
+ return if @link.start_with? "http"
117
+
118
+ @follow = ''
119
+ @target = ''
119
120
  end
120
121
 
121
- def match(liquid_context) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
122
- config = @site.config['href']
123
- die_if_nomatch = !config.nil? && config['nomatch'] && config['nomatch']=='fatal'
122
+ def handle_match
123
+ match_post
124
+ @follow = ''
125
+ @target = ''
126
+ end
124
127
 
128
+ def match_post # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
129
+ # Might set @link and @text
130
+ config = @site.config['href']
131
+ die_if_nomatch = !config.nil? && config['nomatch'] && config['nomatch'] == 'fatal'
125
132
  path, fragment = @link.split('#')
126
133
 
127
- @logger.debug {
134
+ @logger.debug do
128
135
  <<~END_DEBUG
129
136
  @link=#{@link}
130
137
  @site.posts.docs[0].url = #{@site.posts.docs[0].url}
131
138
  @site.posts.docs[0].path = #{@site.posts.docs[0].path}
132
139
  END_DEBUG
133
- }
140
+ end
134
141
 
135
142
  all_urls = @site.all_collections.map(&:url)
136
143
  url_matches = all_urls.select { |url| url.include? path }
@@ -143,20 +150,22 @@ class ExternalHref < Liquid::Tag
143
150
  @link = url_matches.first
144
151
  @link = "#{@link}\##{fragment}" if fragment
145
152
  else
146
- abort "Error: More than one url matched '#{path}': #{ url_matches.join(", ")}"
153
+ abort "Error: More than one url matched '#{path}': #{url_matches.join(", ")}"
147
154
  end
148
155
  end
149
156
 
150
- def replace_vars(liquid_context, link)
157
+ def replace_vars(text)
158
+ # Replace names in plugin-vars with values
151
159
  variables = @site.config['plugin-vars']
152
- return link unless variables
160
+ return text unless variables
153
161
 
154
162
  variables.each do |name, value|
155
- link = link.gsub "{{#{name}}}", value
163
+ text = text.gsub "{{#{name}}}", value
156
164
  end
157
- link
165
+ @logger.debug { "@link=#{@link}" }
166
+ text
158
167
  end
159
168
  end
160
169
 
161
- PluginMetaLogger.instance.info { "Loaded jeykll_href v#{JekyllHrefVersion::VERSION} plugin." }
170
+ PluginMetaLogger.instance.info { "Loaded jekyll_href v#{JekyllHrefVersion::VERSION} plugin." }
162
171
  Liquid::Template.register_tag('href', ExternalHref)
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'key_value_parser'
5
+
6
+ # Parses arguments and options
7
+ class JekyllTagHelper2
8
+ attr_reader :argv, :keys_values, :liquid_context, :logger, :markup, :params, :tag_name
9
+
10
+ # Expand a environment variable reference
11
+ def self.expand_env(str, die_if_undefined: false)
12
+ str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
13
+ envar = Regexp.last_match(1)
14
+ raise HrefError, "jekyll_href error: #{envar} is undefined".red, [] \
15
+ if !ENV.key?(envar) && die_if_undefined # Suppress stack trace
16
+
17
+ ENV[envar]
18
+ end
19
+ end
20
+
21
+ # strip leading and trailing quotes if present
22
+ def self.remove_quotes(string)
23
+ string.strip.gsub(/\A'|\A"|'\Z|"\Z/, '').strip if string
24
+ end
25
+
26
+ def initialize(tag_name, markup, logger)
27
+ # @keys_values was a Hash[Symbol, String|Boolean] but now it is Hash[String, String|Boolean]
28
+ @tag_name = tag_name
29
+ @markup = markup # Useful for debugging
30
+ @argv = Shellwords.split(JekyllTagHelper2.expand_env(markup))
31
+ @keys_values = KeyValueParser \
32
+ .new({}, { array_values: false, normalize_keys: false, separator: /=/ }) \
33
+ .parse(@argv)
34
+ @logger = logger
35
+ @logger.debug { "@keys_values='#{@keys_values}'" }
36
+ end
37
+
38
+ def delete_parameter(key)
39
+ return if @keys_values.empty?
40
+
41
+ @params.delete(key)
42
+ @argv.delete_if { |x| x == key or x.start_with?("#{key}=") }
43
+ @keys_values.delete(key)
44
+ end
45
+
46
+ # @return if parameter was specified, removes it from the available tokens and returns value
47
+ def parameter_specified?(name)
48
+ return false if @keys_values.empty?
49
+
50
+ key = name
51
+ key = name.to_sym if @keys_values.first.first.instance_of?(Symbol)
52
+ value = @keys_values[key]
53
+ delete_parameter(name)
54
+ value
55
+ end
56
+
57
+ PREDEFINED_SCOPE_KEYS = %i[include page].freeze
58
+
59
+ # Finds variables defined in an invoking include, or maybe somewhere else
60
+ # @return variable value or nil
61
+ def dereference_include_variable(name)
62
+ @liquid_context.scopes.each do |scope|
63
+ next if PREDEFINED_SCOPE_KEYS.include? scope.keys.first
64
+
65
+ value = scope[name]
66
+ return value if value
67
+ end
68
+ nil
69
+ end
70
+
71
+ # @return value of variable, or the empty string
72
+ def dereference_variable(name)
73
+ value = @liquid_context[name] # Finds variables named like 'include.my_variable', found in @liquid_context.scopes.first
74
+ value ||= @page[name] if @page # Finds variables named like 'page.my_variable'
75
+ value ||= dereference_include_variable(name)
76
+ value ||= ''
77
+ value
78
+ end
79
+
80
+ # Sets @params by replacing any Liquid variable names with their values
81
+ def liquid_context=(context)
82
+ @liquid_context = context
83
+ @params = @keys_values.map { |k, _v| lookup_variable(k) }
84
+ end
85
+
86
+ def lookup_variable(symbol)
87
+ string = symbol.to_s
88
+ return string unless string.start_with?('{{') && string.end_with?('}}')
89
+
90
+ dereference_variable(string.delete_prefix('{{').delete_suffix('}}'))
91
+ end
92
+
93
+ def page
94
+ @liquid_context.registers[:page]
95
+ end
96
+ end
data/spec/href_spec.rb ADDED
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+ require 'jekyll_plugin_logger'
5
+ require 'yaml'
6
+ require_relative '../lib/jekyll_href'
7
+
8
+ Registers = Struct.new(:page, :site)
9
+
10
+ # Mock for Collections
11
+ class Collections
12
+ def values
13
+ []
14
+ end
15
+ end
16
+
17
+ # Mock for Site
18
+ class SiteMock
19
+ attr_reader :config
20
+
21
+ def initialize
22
+ @config = YAML.safe_load(File.read('_config.yml'))
23
+ end
24
+
25
+ def collections
26
+ Collections.new
27
+ end
28
+ end
29
+
30
+ # Mock for Liquid::ParseContent
31
+ class TestParseContext < Liquid::ParseContext
32
+ attr_reader :line_number, :registers
33
+
34
+ def initialize
35
+ super
36
+ @line_number = 123
37
+
38
+ @registers = Registers.new(
39
+ { 'path' => 'https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss' },
40
+ SiteMock.new
41
+ )
42
+ end
43
+ end
44
+
45
+ # Lets get this party started
46
+ class MyTest # rubocop:disable Metrics/ClassLength
47
+ Dir.chdir 'demo'
48
+
49
+ RSpec.describe ExternalHref do
50
+ let(:logger) do
51
+ PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
52
+ end
53
+
54
+ let(:parse_context) { TestParseContext.new }
55
+
56
+ let(:helper) do
57
+ JekyllTagHelper2.new(
58
+ 'href',
59
+ 'mailto:j.smith@super-fake-merger.com',
60
+ logger
61
+ )
62
+ end
63
+
64
+ it "Obtains external link with text" do
65
+ href = ExternalHref.send(
66
+ :new,
67
+ 'href',
68
+ 'https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss SoundCloud RSS Feed'.dup,
69
+ parse_context
70
+ )
71
+ href.send(:globals_initial, parse_context)
72
+ linkk = href.send(:compute_linkk)
73
+ href.send(:globals_update, href.helper.argv, linkk)
74
+ expect(href.follow).to eq(" rel='nofollow'")
75
+ expect(href.link).to eq('https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss')
76
+ expect(href.target).to eq(" target='_blank'")
77
+ expect(href.text).to eq('SoundCloud RSS Feed')
78
+ end
79
+
80
+ it "Obtains external link using url parameter with text" do
81
+ href = ExternalHref.send(
82
+ :new,
83
+ 'href',
84
+ 'url="https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss" SoundCloud RSS Feed'.dup,
85
+ parse_context
86
+ )
87
+ href.send(:globals_initial, parse_context)
88
+ linkk = href.send(:compute_linkk)
89
+ href.send(:globals_update, href.helper.argv, linkk)
90
+ expect(href.follow).to eq(" rel='nofollow'")
91
+ expect(href.link).to eq('https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss')
92
+ expect(href.target).to eq(" target='_blank'")
93
+ expect(href.text).to eq('SoundCloud RSS Feed')
94
+ end
95
+
96
+ it "Obtains external link without scheme or text" do
97
+ href = ExternalHref.send(
98
+ :new,
99
+ 'href',
100
+ 'super-fake-merger.com'.dup,
101
+ parse_context
102
+ )
103
+ href.send(:globals_initial, parse_context)
104
+ linkk = href.send(:compute_linkk)
105
+ href.send(:globals_update, href.helper.argv, linkk)
106
+ expect(href.follow).to eq(" rel='nofollow'")
107
+ expect(href.link).to eq('https://super-fake-merger.com')
108
+ expect(href.target).to eq(" target='_blank'")
109
+ expect(href.text).to eq('<code>super-fake-merger.com</code>')
110
+ end
111
+
112
+ it "Expands YAML hash with link text" do
113
+ href = ExternalHref.send(
114
+ :new,
115
+ 'href',
116
+ '{{github}}/diasks2/confidential_info_redactor <code>confidential_info_redactor</code>'.dup,
117
+ parse_context
118
+ )
119
+ href.send(:globals_initial, parse_context)
120
+ linkk = href.send(:compute_linkk)
121
+ linkk = href.send(:replace_vars, linkk)
122
+ href.send(:globals_update, href.helper.argv, linkk)
123
+ expect(href.follow).to eq(" rel='nofollow'")
124
+ expect(href.link).to eq('https://github.com/diasks2/confidential_info_redactor')
125
+ expect(href.target).to eq(" target='_blank'")
126
+ expect(href.text).to eq('<code>confidential_info_redactor</code>')
127
+ end
128
+
129
+ it "Obtains external link with follow" do
130
+ href = ExternalHref.send(
131
+ :new,
132
+ 'href',
133
+ 'follow https://www.mslinn.com Awesome'.dup,
134
+ parse_context
135
+ )
136
+ href.send(:globals_initial, parse_context)
137
+ linkk = href.send(:compute_linkk)
138
+ href.send(:globals_update, href.helper.argv, linkk)
139
+ expect(href.follow).to eq('')
140
+ expect(href.link).to eq('https://www.mslinn.com')
141
+ expect(href.target).to eq(" target='_blank'")
142
+ expect(href.text).to eq('Awesome')
143
+ end
144
+
145
+ it "Obtains external link with follow and notarget" do
146
+ href = ExternalHref.send(
147
+ :new,
148
+ 'href',
149
+ 'follow notarget https://www.mslinn.com Awesome'.dup,
150
+ parse_context
151
+ )
152
+ href.send(:globals_initial, parse_context)
153
+ linkk = href.send(:compute_linkk)
154
+ href.send(:globals_update, href.helper.argv, linkk)
155
+ expect(href.follow).to eq('')
156
+ expect(href.link).to eq('https://www.mslinn.com')
157
+ expect(href.target).to eq('')
158
+ expect(href.text).to eq('Awesome')
159
+ end
160
+
161
+ it "Obtains external link with follow and notarget but without text" do
162
+ href = ExternalHref.send(
163
+ :new,
164
+ 'href',
165
+ 'follow notarget www.mslinn.com'.dup,
166
+ parse_context
167
+ )
168
+ href.send(:globals_initial, parse_context)
169
+ linkk = href.send(:compute_linkk)
170
+ href.send(:globals_update, href.helper.argv, linkk)
171
+ expect(href.follow).to eq('')
172
+ expect(href.link).to eq('https://www.mslinn.com')
173
+ expect(href.target).to eq('')
174
+ expect(href.text).to eq('<code>www.mslinn.com</code>')
175
+ end
176
+
177
+ it "Obtains mailto without text" do
178
+ href = ExternalHref.send(
179
+ :new,
180
+ 'href',
181
+ 'mailto:mslinn@mslinn.com'.dup,
182
+ parse_context
183
+ )
184
+ href.send(:globals_initial, parse_context)
185
+ linkk = href.send(:compute_linkk)
186
+ href.send(:globals_update, href.helper.argv, linkk)
187
+ expect(href.follow).to eq('')
188
+ expect(href.link).to eq('mailto:mslinn@mslinn.com')
189
+ expect(href.target).to eq('')
190
+ expect(href.text).to eq('<code>mslinn@mslinn.com</code>')
191
+ end
192
+
193
+ it "Obtains mailto with text" do
194
+ href = ExternalHref.send(
195
+ :new,
196
+ 'href',
197
+ 'mailto:mslinn@mslinn.com Mike Slinn'.dup,
198
+ parse_context
199
+ )
200
+ href.send(:globals_initial, parse_context)
201
+ linkk = href.send(:compute_linkk)
202
+ href.send(:globals_update, href.helper.argv, linkk)
203
+ expect(href.follow).to eq('')
204
+ expect(href.link).to eq('mailto:mslinn@mslinn.com')
205
+ expect(href.target).to eq('')
206
+ expect(href.text).to eq('Mike Slinn')
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+ require "fileutils"
5
+ require_relative "../lib/jekyll_href"
6
+
7
+ RSpec.configure do |config|
8
+ config.filter_run :focus
9
+ config.order = "random"
10
+ config.run_all_when_everything_filtered = true
11
+
12
+ # See https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures
13
+ config.example_status_persistence_file_path = "../spec/status_persistence.txt"
14
+
15
+ config.filter_run_when_matching focus: true
16
+ end
@@ -0,0 +1,11 @@
1
+ example_id | status | run_time |
2
+ ---------------------------------------------------------------- | ------ | --------------- |
3
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:1] | passed | 0.06693 seconds |
4
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:2] | passed | 0.06422 seconds |
5
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:3] | passed | 0.03492 seconds |
6
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:4] | passed | 0.05285 seconds |
7
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:5] | passed | 0.04698 seconds |
8
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:6] | passed | 0.05677 seconds |
9
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:7] | passed | 0.04366 seconds |
10
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:8] | passed | 0.0328 seconds |
11
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:9] | passed | 0.05795 seconds |
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll_href
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.12
4
+ version: 1.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Slinn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-09 00:00:00.000000000 Z
11
+ date: 2022-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: key-value-parser
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: shellwords
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  description: 'Generates an ''a href'' tag, possibly with target=''_blank'' and rel=''nofollow''.
56
84
 
57
85
  '
@@ -69,6 +97,10 @@ files:
69
97
  - jekyll_href.gemspec
70
98
  - lib/jekyll_href.rb
71
99
  - lib/jekyll_href/version.rb
100
+ - lib/jekyll_tag_helper2.rb
101
+ - spec/href_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/status_persistence.txt
72
104
  homepage: https://www.mslinn.com/blog/2020/10/03/jekyll-plugins.html#href
73
105
  licenses:
74
106
  - MIT
@@ -100,5 +132,8 @@ rubygems_version: 3.3.3
100
132
  signing_key:
101
133
  specification_version: 4
102
134
  summary: Generates an 'a href' tag, possibly with target='_blank' and rel='nofollow'.
103
- test_files: []
135
+ test_files:
136
+ - spec/href_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/status_persistence.txt
104
139
  ...