jekyll_href 1.0.11 → 1.0.13

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: 15480940e220781ca558c0f95d22688a87ea55f6b55f8bac9af6f93a1bbe5b33
4
- data.tar.gz: 1314f1deb1e739899c5eb686bf9834d3f88bc46fe3df14bff72f44d737a3064c
3
+ metadata.gz: f6a4d85b016759a94f6242ea415a19cb5870f1e3b51e7f4c5141f95f6c817877
4
+ data.tar.gz: 072b91a1e7919924daecb086549caaad4f30a071a88b9ad4b32c81d92056d946
5
5
  SHA512:
6
- metadata.gz: d15df7a457f5da7181525240ee63ea5a21a404eb8c0fdaf20a6f687378ef4947ff7a32e769376e3825aa95cad2862d09746907635c2320d517f270e2cd1156de
7
- data.tar.gz: 14c3ba6b074a3da884ff50f82519ee054625c5340afae444fddc93ec90414b962a1300ebac4fa3c8904d3bdeedc04f5c0aa26fdc520bafc23463f415f9d67c60
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,21 @@
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
+
16
+ ## 1.0.12 / 2022-08-09
17
+ * No longer abends if `plugin-vars` is not present in `_config.yml`
18
+
1
19
  ## 1.0.11 / 2022-04-27
2
20
  * Suppresses target and rel=nofollow attributes for mailto: links.
3
21
 
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,10 +36,12 @@ 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
- spec.add_development_dependency 'debase'
42
+ # spec.add_development_dependency 'debase'
41
43
  # spec.add_development_dependency 'rubocop-jekyll'
42
44
  # spec.add_development_dependency 'rubocop-rake'
43
45
  # spec.add_development_dependency 'rubocop-rspec'
44
- spec.add_development_dependency 'ruby-debug-ide'
46
+ # spec.add_development_dependency 'ruby-debug-ide'
45
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllHrefVersion
4
- VERSION = "1.0.11"
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
78
+ end
79
+
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')
109
93
  end
110
94
 
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 = ""
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,18 +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']
160
+ return text unless variables
161
+
152
162
  variables.each do |name, value|
153
- link = link.gsub "{{#{name}}}", value
163
+ text = text.gsub "{{#{name}}}", value
154
164
  end
155
- link
165
+ @logger.debug { "@link=#{@link}" }
166
+ text
156
167
  end
157
168
  end
158
169
 
159
- PluginMetaLogger.instance.info { "Loaded jeykll_href v#{JekyllHrefVersion::VERSION} plugin." }
170
+ PluginMetaLogger.instance.info { "Loaded jekyll_href v#{JekyllHrefVersion::VERSION} plugin." }
160
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.11
4
+ version: 1.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Slinn
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-27 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
@@ -53,13 +53,13 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: debase
56
+ name: key-value-parser
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- type: :development
62
+ type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
@@ -67,13 +67,13 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: ruby-debug-ide
70
+ name: shellwords
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
- type: :development
76
+ type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
@@ -82,7 +82,7 @@ dependencies:
82
82
  version: '0'
83
83
  description: 'Generates an ''a href'' tag, possibly with target=''_blank'' and rel=''nofollow''.
84
84
 
85
- '
85
+ '
86
86
  email:
87
87
  - mslinn@mslinn.com
88
88
  executables: []
@@ -97,6 +97,10 @@ files:
97
97
  - jekyll_href.gemspec
98
98
  - lib/jekyll_href.rb
99
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
100
104
  homepage: https://www.mslinn.com/blog/2020/10/03/jekyll-plugins.html#href
101
105
  licenses:
102
106
  - MIT
@@ -124,8 +128,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
128
  - !ruby/object:Gem::Version
125
129
  version: '0'
126
130
  requirements: []
127
- rubygems_version: 3.1.4
128
- signing_key:
131
+ rubygems_version: 3.3.3
132
+ signing_key:
129
133
  specification_version: 4
130
134
  summary: Generates an 'a href' tag, possibly with target='_blank' and rel='nofollow'.
131
- test_files: []
135
+ test_files:
136
+ - spec/href_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/status_persistence.txt
139
+ ...