jekyll_href 1.0.12 → 1.0.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33ad179cd8d6770d3828246bdc51cdbe398f7fd3f50b0101481e2dacd597191e
4
- data.tar.gz: 543a944799bdb811fd84f3afb4ce0b8239e2008208feb0612360307bd1843c69
3
+ metadata.gz: e77bc04770e6fae8eeb1a595be3e3faf81fe6c77c023a9a711a3a97d51b3a828
4
+ data.tar.gz: c9ff34ea2c4611fefb8f36e99471ad55a490b60d68c255fbcc06ce3791acc90e
5
5
  SHA512:
6
- metadata.gz: be0fd539b076cedb893cee10a528f4a3c71d2ec01e84c16a26455ec4c4aacded321bc11f96ac8ff5d6647b2cdf3692aba9bb843e419d67c54aab9a47edb63ff9
7
- data.tar.gz: f2d3abce3b08922eefa404905d1f61f319150662f891dd03171e716479d16e068e4e2a02b9975b5f609716f942876ee9b7d542d14236dba141fb4fcaa6e3ce66
6
+ metadata.gz: 9727eed971f210d18ab6826a297b5563bf899ab1c14bbd9a988fa31405d1521357ca6fa48894ab3dfacfda11371f025c83908ed5ef1519ce7fb04ba9f25605d9
7
+ data.tar.gz: e82fbe117198467601a940fbc24fd9a2a40ec802b263e43324cfaaca7467f8b704f16ad1dc6214a02154ba88f04006de7f9830c08a2857c49a8614515f016f4e
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,21 @@
1
+ ## 1.0.14 / 2023-01-09
2
+ * Added `blank` parameter.
3
+
4
+ ## 1.0.13 / 2022-12-14
5
+ * Links with embedded spaces are now supported when the new 'url' named parameter is used. For example, instead of specifying:
6
+ ```
7
+ {% href http://link.com with space.html some text %}
8
+ ```
9
+ Instead specify (single and double quotes are equally valid):
10
+ ```
11
+ {% href url="http://link.com with space.html" some text %}
12
+ ```
13
+ * URLs can now contain environment variable references. For example, if $domain and $uri are environment variables:
14
+ ```
15
+ {% href url="http://$domain.html" some text %}
16
+ {% href url="$uri" some text %}
17
+ ```
18
+
1
19
  ## 1.0.12 / 2022-08-09
2
20
  * No longer abends if `plugin-vars` is not present in `_config.yml`
3
21
 
data/README.md CHANGED
@@ -3,24 +3,134 @@
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`.
14
+ Instead, use &apos; (`&apos;`), &quot; (`&quot;`), &lsquo; (`&lsquo;`), &rsquo; (`&rsquo;`), &ldquo; (`&ldquo;`), and &rdquo; (`&rdquo;`)
15
+
16
+ In `_config.yml`, if a section called `plugin-vars` exists,
17
+ then its name/value pairs are available for substitution.
18
+ ```yaml
19
+ plugin-vars:
20
+ django-github: 'https://github.com/django/django/blob/3.1.7'
21
+ django-oscar-github: 'https://github.com/django-oscar/django-oscar/blob/3.0.2'
22
+ ```
10
23
 
11
24
 
12
- ### Syntax:
25
+ ## Syntax 1 (requires `url` does not have embedded spaces):
13
26
  ```
14
- {% href [match | [follow] [notarget]] [url] text to display %}
27
+ {% href [match | [follow] [blank|notarget]] url text to display %}
15
28
  ```
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.
29
+ 1. The url must be a single token, without embedded spaces.
30
+ 2. The url need not be enclosed in quotes.
31
+ 3. The square brackets denote optional keyword parameters, and should not be typed.
18
32
 
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.
33
+
34
+ ## Syntax 2 (always works):
35
+ This syntax is recommended when the URL contains a colon (:).
36
+ ```
37
+ {% href [match | [follow] [blank|notarget]]
38
+ url="http://link.com with space.html" some text %}
39
+
40
+ {% href [match | [follow] [blank|notarget]]
41
+ url='http://link.com with space.html' some text %}
42
+ ```
43
+ 1. Each of the above examples contain an embedded newline, which is legal.
44
+ 2. The url must be enclosed by either single or double quotes.
45
+ 3. The square brackets denote optional keyword parameters, and should not be typed.
46
+
47
+
48
+ ## Syntax 3 (implicit URL):
49
+ ```
50
+ {% href [match | [follow] [blank|notarget]] www.domain.com %}
51
+ ```
52
+ The URI provided, for example `www.domain.com`,
53
+ is used to form the URL by prepending `https://`,
54
+ in this case the result would be `https://www.domain.com`.
55
+ The displayed URI is enclosed in `<code></code>`,
56
+ so the resulting text is `<code>www.domain.com</code>`.
57
+
58
+
59
+ ## Environment Variable Expansion
60
+ URLs can contain environment variable references.
61
+ For example, if `$domain`, `$uri` and `$USER` are environment variables:
62
+ ```
63
+ {% href http://$domain.html some text %}
64
+
65
+ {% href url="$uri" some text %}
66
+
67
+ {% href https://mslinn.html <code>USER=$USER</code> %}
68
+ ```
69
+
70
+ ## Optional Parameters
71
+ ### `blank`
72
+ The `target='_blank'` attribute is not normally generated for relative links.
73
+ To enforce the generation of this attribute, preface the link with the word `blank`.
74
+ The `blank` and `notarget` parameters are mutually exclusive.
75
+ If both are specified, `blank` prevails.
76
+
77
+ ### `follow`
78
+ To suppress the `nofollow` attribute, preface the link with the word `follow`.
79
+
80
+
81
+ ### `notarget`
82
+ To suppress the `target` attribute, preface the link with the word `notarget`.
83
+ The `blank` and `notarget` parameters are mutually exclusive.
84
+ If both are specified, `blank` prevails.
21
85
 
22
86
 
23
- ### Additional Information
87
+ ### `match`
88
+ `match` will attempt to match the url fragment (specified as a regex) to a URL in any collection.
89
+ If multiple documents have matching URL an error is thrown.
90
+ The `match` option looks through the pages collection for a URL with containing the provided substring.
91
+ `Match` implies `follow` and `notarget`.
92
+
93
+
94
+ ## Examples
95
+ 1. Generates `nofollow` and `target` attributes:
96
+ ```
97
+ {% href https://mslinn.com The Awesome %}
98
+ ```
99
+
100
+ 2. Does not generate `nofollow` or `target` attributes.
101
+ ```
102
+ {% href follow notarget https://mslinn.com The Awesome %}
103
+ ```
104
+
105
+ 3. Does not generate `nofollow` attribute.
106
+ ```
107
+ {% href follow https://mslinn.com The Awesome %}
108
+ ```
109
+
110
+ 4. Does not generate `target` attribute.
111
+ ```
112
+ {% href notarget https://mslinn.com The Awesome %}
113
+ ```
114
+
115
+ 5. Matches page with URL containing abc.
116
+ ```
117
+ {% href match abc The Awesome %}
118
+ ```
119
+
120
+ 6. Matches page with URL containing abc.
121
+ ```
122
+ {% href match abc.html#tag The Awesome %}
123
+ ```
124
+
125
+ 7. Substitute name/value pair for the `django-github` variable defined above:
126
+ ```
127
+ {% href {{django-github}}/django/core/management/__init__.py#L398-L401
128
+ <code>django.core.management.execute_from_command_line</code> %}
129
+ ```
130
+ Substitutions are only made to the URL, not to the linked text.
131
+
132
+
133
+ ## Additional Information
24
134
  More information is available on my web site about [my Jekyll plugins](https://www.mslinn.com/blog/2020/10/03/jekyll-plugins.html).
25
135
 
26
136
 
@@ -42,9 +152,10 @@ Or install it yourself as:
42
152
 
43
153
  $ gem install jekyll_href
44
154
 
45
- ## Usage
46
155
 
47
- ### Defaults
156
+ ## Generated HTML
157
+
158
+ ### Without Keywords
48
159
  ```
49
160
  {% href https://mslinn.com The Awesome %}
50
161
  ```
@@ -56,7 +167,7 @@ Expands to this:
56
167
 
57
168
  Which renders as this: <a href='https://mslinn.com' target='_blank' rel='nofollow'>The Awesome</a>
58
169
 
59
- ### `follow`
170
+ ### With `follow`
60
171
  ```
61
172
  {% href follow https://mslinn.com The Awesome %}
62
173
  ```
@@ -67,7 +178,7 @@ Expands to this:
67
178
  ```
68
179
 
69
180
 
70
- ### `notarget`
181
+ ### With `notarget`
71
182
  ```
72
183
  {% href notarget https://mslinn.com The Awesome %}
73
184
  ```
@@ -78,7 +189,7 @@ Expands to this:
78
189
  ```
79
190
 
80
191
 
81
- ### `follow notarget`
192
+ ### With `follow notarget`
82
193
  ```
83
194
  {% href follow notarget https://mslinn.com The Awesome %}
84
195
  ```
@@ -88,7 +199,7 @@ Expands to this:
88
199
  <a href='https://mslinn.com'>The Awesome</a>
89
200
  ```
90
201
 
91
- ### `match`
202
+ ### With `match`
92
203
  Looks for a post with a matching URL.
93
204
  ```
94
205
  {% href match setting-up-django-oscar.html tutorial site %}
@@ -109,6 +220,7 @@ Expands to this:
109
220
  ```
110
221
  Which renders as: [`mslinn.com`](https://mslinn.com)
111
222
 
223
+
112
224
  ## Development
113
225
 
114
226
  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 +235,21 @@ To install this gem onto your local machine, run:
123
235
  $ bundle exec rake install
124
236
  ```
125
237
 
238
+ ## Test
239
+ A test web site is provided in the `demo` directory.
240
+ 1. Set breakpoints.
241
+
242
+ 2. Initiate a debug session from the command line:
243
+ ```shell
244
+ $ bin/attach demo
245
+ ```
246
+
247
+ 3. Once the `Fast Debugger` signon appears, launch the test configuration called `Attach rdebug-ide`.
248
+
249
+ 4. View the generated website at [`http://localhost:4444`](http://localhost:4444)
250
+
251
+
252
+ ## Release
126
253
  To release a new version,
127
254
  1. Update the version number in `version.rb`.
128
255
  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.14"
5
5
  end
data/lib/jekyll_href.rb CHANGED
@@ -1,136 +1,145 @@
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) # rubocop:disable Metrics/MethodLength
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
+ @blank = @helper.parameter_specified?('blank')
92
+ @target = @blank ? " target='_blank'" : nil
93
+ @target ||= @helper.parameter_specified?('notarget') ? '' : " target='_blank'"
94
+ @url = @helper.parameter_specified?('url')
95
+ end
96
+
97
+ def globals_update(tokens, linkk) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
98
+ # Might set @follow, @linkk, @target, and @text
99
+ if linkk.start_with? 'mailto:'
100
+ @link = linkk
101
+ @target = @follow = ''
102
+ @text = @helper.argv.join(' ')
103
+ if @text.empty?
104
+ text = linkk.delete_prefix('mailto:')
105
+ @text = "<code>#{text}</code>"
106
+ end
107
+ return
108
+ else
109
+ @text = tokens.join(" ").strip
110
+ if @text.to_s.empty?
111
+ @text = "<code>#{linkk}</code>"
112
+ @link = "https://#{linkk}"
113
+ else
114
+ @link = linkk
115
+ end
117
116
  end
118
- value
117
+
118
+ return if @link.start_with? "http"
119
+
120
+ @follow = ''
121
+ @target = '' unless @blank
119
122
  end
120
123
 
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'
124
+ def handle_match
125
+ match_post
126
+ @follow = ''
127
+ @target = '' unless @blank
128
+ end
124
129
 
130
+ def match_post # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
131
+ # Might set @link and @text
132
+ config = @site.config['href']
133
+ die_if_nomatch = !config.nil? && config['nomatch'] && config['nomatch'] == 'fatal'
125
134
  path, fragment = @link.split('#')
126
135
 
127
- @logger.debug {
136
+ @logger.debug do
128
137
  <<~END_DEBUG
129
138
  @link=#{@link}
130
139
  @site.posts.docs[0].url = #{@site.posts.docs[0].url}
131
140
  @site.posts.docs[0].path = #{@site.posts.docs[0].path}
132
141
  END_DEBUG
133
- }
142
+ end
134
143
 
135
144
  all_urls = @site.all_collections.map(&:url)
136
145
  url_matches = all_urls.select { |url| url.include? path }
@@ -143,20 +152,22 @@ class ExternalHref < Liquid::Tag
143
152
  @link = url_matches.first
144
153
  @link = "#{@link}\##{fragment}" if fragment
145
154
  else
146
- abort "Error: More than one url matched '#{path}': #{ url_matches.join(", ")}"
155
+ abort "Error: More than one url matched '#{path}': #{url_matches.join(", ")}"
147
156
  end
148
157
  end
149
158
 
150
- def replace_vars(liquid_context, link)
159
+ def replace_vars(text)
160
+ # Replace names in plugin-vars with values
151
161
  variables = @site.config['plugin-vars']
152
- return link unless variables
162
+ return text unless variables
153
163
 
154
164
  variables.each do |name, value|
155
- link = link.gsub "{{#{name}}}", value
165
+ text = text.gsub "{{#{name}}}", value
156
166
  end
157
- link
167
+ @logger.debug { "@link=#{@link}" }
168
+ text
158
169
  end
159
170
  end
160
171
 
161
- PluginMetaLogger.instance.info { "Loaded jeykll_href v#{JekyllHrefVersion::VERSION} plugin." }
172
+ PluginMetaLogger.instance.info { "Loaded jekyll_href v#{JekyllHrefVersion::VERSION} plugin." }
162
173
  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,273 @@
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 internal link with blank" do
65
+ href = ExternalHref.send(
66
+ :new,
67
+ 'href',
68
+ 'blank ./path/page.html internal link text'.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('')
75
+ expect(href.link).to eq('./path/page.html')
76
+ expect(href.target).to eq(" target='_blank'")
77
+ expect(href.text).to eq('internal link text')
78
+ end
79
+
80
+ it "Obtains external link with text" do
81
+ href = ExternalHref.send(
82
+ :new,
83
+ 'href',
84
+ '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 using url parameter with text" do
97
+ href = ExternalHref.send(
98
+ :new,
99
+ 'href',
100
+ 'url="https://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss" SoundCloud RSS Feed'.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://feeds.soundcloud.com/users/soundcloud:users:7143896/sounds.rss')
108
+ expect(href.target).to eq(" target='_blank'")
109
+ expect(href.text).to eq('SoundCloud RSS Feed')
110
+ end
111
+
112
+ it "Obtains external link without scheme or text" do
113
+ href = ExternalHref.send(
114
+ :new,
115
+ 'href',
116
+ 'super-fake-merger.com'.dup,
117
+ parse_context
118
+ )
119
+ href.send(:globals_initial, parse_context)
120
+ linkk = href.send(:compute_linkk)
121
+ href.send(:globals_update, href.helper.argv, linkk)
122
+ expect(href.follow).to eq(" rel='nofollow'")
123
+ expect(href.link).to eq('https://super-fake-merger.com')
124
+ expect(href.target).to eq(" target='_blank'")
125
+ expect(href.text).to eq('<code>super-fake-merger.com</code>')
126
+ end
127
+
128
+ it "Expands YAML hash with link text" do
129
+ href = ExternalHref.send(
130
+ :new,
131
+ 'href',
132
+ '{{github}}/diasks2/confidential_info_redactor <code>confidential_info_redactor</code>'.dup,
133
+ parse_context
134
+ )
135
+ href.send(:globals_initial, parse_context)
136
+ linkk = href.send(:compute_linkk)
137
+ linkk = href.send(:replace_vars, linkk)
138
+ href.send(:globals_update, href.helper.argv, linkk)
139
+ expect(href.follow).to eq(" rel='nofollow'")
140
+ expect(href.link).to eq('https://github.com/diasks2/confidential_info_redactor')
141
+ expect(href.target).to eq(" target='_blank'")
142
+ expect(href.text).to eq('<code>confidential_info_redactor</code>')
143
+ end
144
+
145
+ it "Obtains external link with follow" do
146
+ href = ExternalHref.send(
147
+ :new,
148
+ 'href',
149
+ 'follow 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(" target='_blank'")
158
+ expect(href.text).to eq('Awesome')
159
+ end
160
+
161
+ it "Obtains external link with follow and notarget" do
162
+ href = ExternalHref.send(
163
+ :new,
164
+ 'href',
165
+ 'follow notarget https://www.mslinn.com Awesome'.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('Awesome')
175
+ end
176
+
177
+ it "Obtains external link with blank" do
178
+ href = ExternalHref.send(
179
+ :new,
180
+ 'href',
181
+ 'blank https://www.mslinn.com Awesome'.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(" rel='nofollow'")
188
+ expect(href.link).to eq('https://www.mslinn.com')
189
+ expect(href.target).to eq(" target='_blank'")
190
+ expect(href.text).to eq('Awesome')
191
+ end
192
+
193
+ it "Implicitly computes external link from text" do
194
+ href = ExternalHref.send(
195
+ :new,
196
+ 'href',
197
+ 'www.mslinn.com'.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(" rel='nofollow'")
204
+ expect(href.link).to eq('https://www.mslinn.com')
205
+ expect(href.target).to eq(" target='_blank'")
206
+ expect(href.text).to eq('<code>www.mslinn.com</code>')
207
+ end
208
+
209
+ it "Implicitly computes external link from text with follow and notarget" do
210
+ href = ExternalHref.send(
211
+ :new,
212
+ 'href',
213
+ 'follow notarget www.mslinn.com'.dup,
214
+ parse_context
215
+ )
216
+ href.send(:globals_initial, parse_context)
217
+ linkk = href.send(:compute_linkk)
218
+ href.send(:globals_update, href.helper.argv, linkk)
219
+ expect(href.follow).to eq('')
220
+ expect(href.link).to eq('https://www.mslinn.com')
221
+ expect(href.target).to eq('')
222
+ expect(href.text).to eq('<code>www.mslinn.com</code>')
223
+ end
224
+
225
+ it "Implicitly computes external link from text with blank" do
226
+ href = ExternalHref.send(
227
+ :new,
228
+ 'href',
229
+ 'follow blank www.mslinn.com'.dup,
230
+ parse_context
231
+ )
232
+ href.send(:globals_initial, parse_context)
233
+ linkk = href.send(:compute_linkk)
234
+ href.send(:globals_update, href.helper.argv, linkk)
235
+ expect(href.follow).to eq('')
236
+ expect(href.link).to eq('https://www.mslinn.com')
237
+ expect(href.target).to eq(" target='_blank'")
238
+ expect(href.text).to eq('<code>www.mslinn.com</code>')
239
+ end
240
+
241
+ it "Obtains mailto without text" do
242
+ href = ExternalHref.send(
243
+ :new,
244
+ 'href',
245
+ 'mailto:mslinn@mslinn.com'.dup,
246
+ parse_context
247
+ )
248
+ href.send(:globals_initial, parse_context)
249
+ linkk = href.send(:compute_linkk)
250
+ href.send(:globals_update, href.helper.argv, linkk)
251
+ expect(href.follow).to eq('')
252
+ expect(href.link).to eq('mailto:mslinn@mslinn.com')
253
+ expect(href.target).to eq('')
254
+ expect(href.text).to eq('<code>mslinn@mslinn.com</code>')
255
+ end
256
+
257
+ it "Obtains mailto with text" do
258
+ href = ExternalHref.send(
259
+ :new,
260
+ 'href',
261
+ 'mailto:mslinn@mslinn.com Mike Slinn'.dup,
262
+ parse_context
263
+ )
264
+ href.send(:globals_initial, parse_context)
265
+ linkk = href.send(:compute_linkk)
266
+ href.send(:globals_update, href.helper.argv, linkk)
267
+ expect(href.follow).to eq('')
268
+ expect(href.link).to eq('mailto:mslinn@mslinn.com')
269
+ expect(href.target).to eq('')
270
+ expect(href.text).to eq('Mike Slinn')
271
+ end
272
+ end
273
+ 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,15 @@
1
+ example_id | status | run_time |
2
+ ----------------------------------------------------------------- | ------ | --------------- |
3
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:1] | passed | 0.00817 seconds |
4
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:2] | passed | 0.00349 seconds |
5
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:3] | passed | 0.0033 seconds |
6
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:4] | passed | 0.00307 seconds |
7
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:5] | passed | 0.00315 seconds |
8
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:6] | passed | 0.0032 seconds |
9
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:7] | passed | 0.0035 seconds |
10
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:8] | passed | 0.00323 seconds |
11
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:9] | passed | 0.0032 seconds |
12
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:10] | passed | 0.00307 seconds |
13
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:11] | passed | 0.00317 seconds |
14
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:12] | passed | 0.00357 seconds |
15
+ /mnt/f/work/jekyll/my_plugins/jekyll_href/spec/href_spec.rb[1:13] | passed | 0.0035 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.14
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: 2023-01-09 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
  ...