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 +4 -4
- data/.rubocop.yml +11 -5
- data/CHANGELOG.md +18 -0
- data/README.md +124 -17
- data/jekyll_href.gemspec +4 -2
- data/lib/jekyll_href/version.rb +1 -1
- data/lib/jekyll_href.rb +109 -98
- data/lib/jekyll_tag_helper2.rb +96 -0
- data/spec/href_spec.rb +209 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/status_persistence.txt +11 -0
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6a4d85b016759a94f6242ea415a19cb5870f1e3b51e7f4c5141f95f6c817877
|
4
|
+
data.tar.gz: 072b91a1e7919924daecb086549caaad4f30a071a88b9ad4b32c81d92056d946
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 ‘, ’, “, ”
|
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
|
-
|
24
|
+
## Syntax 1 (requires `url` does not have embedded spaces):
|
13
25
|
```
|
14
|
-
{% href [match | [follow] [notarget]]
|
26
|
+
{% href [match | [follow] [notarget]] url text to display %}
|
15
27
|
```
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
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
|
-
###
|
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
|
-
|
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
|
data/lib/jekyll_href/version.rb
CHANGED
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
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
58
|
-
# @param
|
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,
|
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
|
-
@
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
@
|
90
|
-
@
|
91
|
-
|
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
|
97
|
-
@link
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
@
|
102
|
-
@
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@
|
116
|
-
|
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
|
-
|
115
|
+
|
116
|
+
return if @link.start_with? "http"
|
117
|
+
|
118
|
+
@follow = ''
|
119
|
+
@target = ''
|
119
120
|
end
|
120
121
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
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}': #{
|
153
|
+
abort "Error: More than one url matched '#{path}': #{url_matches.join(", ")}"
|
147
154
|
end
|
148
155
|
end
|
149
156
|
|
150
|
-
def replace_vars(
|
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
|
-
|
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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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.
|
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-
|
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:
|
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: :
|
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:
|
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: :
|
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.
|
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
|
+
...
|