jekyll-shopsavvy 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/lib/jekyll/shopsavvy/client.rb +60 -0
- data/lib/jekyll/shopsavvy/tags.rb +177 -0
- data/lib/jekyll/shopsavvy/version.rb +5 -0
- data/lib/jekyll-shopsavvy.rb +9 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 105a94a928827ffde68ed93ff535c84638f1fb95c7976ecb94c1a4cdec6ccb6f
|
|
4
|
+
data.tar.gz: 49deeab2b516ba83a90f49035de488d82631a54d6674be06d997aeceaac620e6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0e43d86bc0764ac5731a52f36ffe28de5d2b5616814245203e5f997adcb490afaf373956334b345851407bb93fbc5cc0ec0cfca63f3d652d3b11117057533a62
|
|
7
|
+
data.tar.gz: 9a1cc160af8db9dc163dfb6022de1936b26698771074bf945f0a46dec7214dc39c492422bb63b7bd658ce87989c54756119f2f038a3ca45d989f60a363908dc2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShopSavvy by Monolith Technologies, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# jekyll-shopsavvy
|
|
2
|
+
|
|
3
|
+
Jekyll plugin that adds Liquid tags and filters for embedding **live product cards, deal feeds, and price lookups** powered by the [ShopSavvy Data API](https://shopsavvy.com/data). Everything runs at build time, so the generated site stays static.
|
|
4
|
+
|
|
5
|
+
[Documentation](https://shopsavvy.com/integrations/jekyll) · [Get an API key](https://shopsavvy.com/data) · [Other integrations](https://shopsavvy.com/integrations)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
group :jekyll_plugins do
|
|
13
|
+
gem "jekyll-shopsavvy"
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Register the plugin in `_config.yml`:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
plugins:
|
|
27
|
+
- jekyll-shopsavvy
|
|
28
|
+
|
|
29
|
+
shopsavvy:
|
|
30
|
+
api_key: ENV["SHOPSAVVY_API_KEY"]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> **Heads up:** GitHub Pages safe mode whitelists only specific plugins, and `jekyll-shopsavvy` is not on that list. To deploy a Jekyll site that uses this gem to GitHub Pages, build with GitHub Actions and publish the resulting `_site/` directory — that path supports any plugin.
|
|
34
|
+
|
|
35
|
+
## Tags
|
|
36
|
+
|
|
37
|
+
### `{% shopsavvy_product %}`
|
|
38
|
+
|
|
39
|
+
Renders a product with offers from across retailers.
|
|
40
|
+
|
|
41
|
+
```liquid
|
|
42
|
+
{% shopsavvy_product "012345678905" %}
|
|
43
|
+
{% shopsavvy_product "B0DGHYDZSB" layout="inline" %}
|
|
44
|
+
{% shopsavvy_product "012345678905" layout="table" retailer="amazon" limit=10 %}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Layouts: `card` (default), `inline`, `table`.
|
|
48
|
+
|
|
49
|
+
### `{% shopsavvy_deals %}`
|
|
50
|
+
|
|
51
|
+
Renders a grid of trending deals.
|
|
52
|
+
|
|
53
|
+
```liquid
|
|
54
|
+
{% shopsavvy_deals category="electronics" limit=8 %}
|
|
55
|
+
{% shopsavvy_deals sort="discount" grade="A" %}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `{% shopsavvy_price %}`
|
|
59
|
+
|
|
60
|
+
Inline price text — useful for headlines or sentences.
|
|
61
|
+
|
|
62
|
+
```liquid
|
|
63
|
+
The current best price is {% shopsavvy_price "012345678905" %}.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Filter
|
|
67
|
+
|
|
68
|
+
```liquid
|
|
69
|
+
{% assign product = "012345678905" | shopsavvy_lookup %}
|
|
70
|
+
<h2>{{ product.data[0].name }}</h2>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Read in this order:
|
|
76
|
+
|
|
77
|
+
1. `ENV["SHOPSAVVY_API_KEY"]`
|
|
78
|
+
2. `_config.yml` → `shopsavvy.api_key`
|
|
79
|
+
|
|
80
|
+
## Test
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
./test.sh
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "uri"
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module Shopsavvy
|
|
7
|
+
# Thin HTTP wrapper that calls the ShopSavvy Data API at build time.
|
|
8
|
+
# Caches responses per Jekyll site build to keep rebuilds fast.
|
|
9
|
+
class Client
|
|
10
|
+
DEFAULT_BASE_URL = "https://api.shopsavvy.com/v1".freeze
|
|
11
|
+
|
|
12
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, cache: nil)
|
|
13
|
+
raise ArgumentError, "ShopSavvy API key is required" if api_key.nil? || api_key.empty?
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@base_url = base_url
|
|
16
|
+
@cache = cache || {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def product_details(identifier)
|
|
20
|
+
get("/products/details", id: identifier)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def current_offers(identifier, retailer: nil)
|
|
24
|
+
params = { id: identifier }
|
|
25
|
+
params[:retailer] = retailer if retailer
|
|
26
|
+
get("/products/offers", **params)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def price_history(identifier, days: 90)
|
|
30
|
+
get("/products/history", id: identifier, days: days)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def deals(category: nil, limit: 10, sort: "trending", grade: nil)
|
|
34
|
+
params = { limit: limit, sort: sort }
|
|
35
|
+
params[:category] = category if category
|
|
36
|
+
params[:min_grade] = grade if grade
|
|
37
|
+
get("/deals", **params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def get(path, **params)
|
|
43
|
+
cache_key = [path, params].to_s
|
|
44
|
+
return @cache[cache_key] if @cache.key?(cache_key)
|
|
45
|
+
|
|
46
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
47
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
48
|
+
|
|
49
|
+
request = Net::HTTP::Get.new(uri)
|
|
50
|
+
request["Authorization"] = "Bearer #{@api_key}"
|
|
51
|
+
request["User-Agent"] = "jekyll-shopsavvy/#{VERSION}"
|
|
52
|
+
|
|
53
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
54
|
+
body = JSON.parse(response.body)
|
|
55
|
+
@cache[cache_key] = body
|
|
56
|
+
body
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Shopsavvy
|
|
5
|
+
# Parses key=value style arguments out of a Liquid tag's markup string.
|
|
6
|
+
module ArgsParser
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def parse(markup)
|
|
10
|
+
tokens = Shellwords.split(markup.to_s)
|
|
11
|
+
positional = []
|
|
12
|
+
named = {}
|
|
13
|
+
tokens.each do |t|
|
|
14
|
+
if t.include?("=")
|
|
15
|
+
k, v = t.split("=", 2)
|
|
16
|
+
named[k.to_sym] = v
|
|
17
|
+
else
|
|
18
|
+
positional << t
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
[positional, named]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Site-level helpers: shared client and cache.
|
|
26
|
+
module SiteHelpers
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
def client_for(context)
|
|
30
|
+
site = context.registers[:site]
|
|
31
|
+
site.config["__shopsavvy_client"] ||= begin
|
|
32
|
+
api_key = ENV["SHOPSAVVY_API_KEY"] || site.config.dig("shopsavvy", "api_key")
|
|
33
|
+
Client.new(api_key: api_key, cache: (site.config["__shopsavvy_cache"] ||= {}))
|
|
34
|
+
rescue ArgumentError => e
|
|
35
|
+
Jekyll.logger.warn("ShopSavvy:", e.message)
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# {% shopsavvy_product <identifier> [layout="card"] [retailer="..."] [limit=5] %}
|
|
42
|
+
class ProductTag < Liquid::Tag
|
|
43
|
+
LAYOUTS = %w[card inline table].freeze
|
|
44
|
+
|
|
45
|
+
def initialize(tag_name, markup, tokens)
|
|
46
|
+
super
|
|
47
|
+
positional, @named = ArgsParser.parse(markup)
|
|
48
|
+
@identifier = positional.first || @named[:upc] || @named[:asin] || @named[:id] || @named[:url]
|
|
49
|
+
@layout = (@named[:layout] || "card").to_s
|
|
50
|
+
@layout = "card" unless LAYOUTS.include?(@layout)
|
|
51
|
+
@retailer = @named[:retailer]
|
|
52
|
+
@limit = (@named[:limit] || 5).to_i
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render(context)
|
|
56
|
+
client = SiteHelpers.client_for(context)
|
|
57
|
+
return "" unless client && @identifier
|
|
58
|
+
|
|
59
|
+
identifier = Liquid::Template.parse(@identifier).render(context)
|
|
60
|
+
product = client.product_details(identifier)
|
|
61
|
+
offers = client.current_offers(identifier, retailer: @retailer)
|
|
62
|
+
offer_list = (offers["data"] || []).first(@limit)
|
|
63
|
+
|
|
64
|
+
case @layout
|
|
65
|
+
when "inline" then render_inline(product, offer_list)
|
|
66
|
+
when "table" then render_table(product, offer_list)
|
|
67
|
+
else render_card(product, offer_list)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def render_card(product, offers)
|
|
74
|
+
item = (product["data"] || []).first || {}
|
|
75
|
+
cheapest = offers.min_by { |o| o["price"].to_f } || {}
|
|
76
|
+
offers_html = offers.map { |o| %(<li><span>#{o["retailer"]}</span><span>$#{o["price"]}</span></li>) }.join
|
|
77
|
+
<<~HTML
|
|
78
|
+
<div class="shopsavvy-card">
|
|
79
|
+
#{item["image"] ? %(<img class="shopsavvy-card__image" src="#{item["image"]}" alt="#{item["name"]}" loading="lazy" />) : ""}
|
|
80
|
+
<div class="shopsavvy-card__body">
|
|
81
|
+
<h3 class="shopsavvy-card__name">#{item["name"]}</h3>
|
|
82
|
+
<p class="shopsavvy-card__price">$#{cheapest["price"]} <span class="shopsavvy-card__retailer">at #{cheapest["retailer"]}</span></p>
|
|
83
|
+
<ul class="shopsavvy-card__offers">#{offers_html}</ul>
|
|
84
|
+
<a class="shopsavvy-card__cta" href="https://shopsavvy.com" rel="noopener">Compare on ShopSavvy</a>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
HTML
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def render_inline(product, offers)
|
|
91
|
+
item = (product["data"] || []).first || {}
|
|
92
|
+
cheapest = offers.min_by { |o| o["price"].to_f } || {}
|
|
93
|
+
%(<span class="shopsavvy-inline"><strong>#{item["name"]}</strong> — <span class="shopsavvy-inline__price">$#{cheapest["price"]} at #{cheapest["retailer"]}</span></span>)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def render_table(product, offers)
|
|
97
|
+
item = (product["data"] || []).first || {}
|
|
98
|
+
rows = offers.map do |o|
|
|
99
|
+
%(<tr><td>#{o["retailer"]}</td><td>$#{o["price"]}</td><td>#{o["condition"] || "new"}</td><td>#{o["availability"] ? "Yes" : "—"}</td></tr>)
|
|
100
|
+
end.join
|
|
101
|
+
<<~HTML
|
|
102
|
+
<div class="shopsavvy-table-wrap">
|
|
103
|
+
<p class="shopsavvy-table__title">#{item["name"]}</p>
|
|
104
|
+
<table class="shopsavvy-table">
|
|
105
|
+
<thead><tr><th>Retailer</th><th>Price</th><th>Condition</th><th>In stock</th></tr></thead>
|
|
106
|
+
<tbody>#{rows}</tbody>
|
|
107
|
+
</table>
|
|
108
|
+
</div>
|
|
109
|
+
HTML
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# {% shopsavvy_deals [category="..."] [limit=10] [sort="trending"] [grade="A"] %}
|
|
114
|
+
class DealsTag < Liquid::Tag
|
|
115
|
+
def initialize(tag_name, markup, tokens)
|
|
116
|
+
super
|
|
117
|
+
_positional, named = ArgsParser.parse(markup)
|
|
118
|
+
@category = named[:category]
|
|
119
|
+
@limit = (named[:limit] || 10).to_i
|
|
120
|
+
@sort = named[:sort] || "trending"
|
|
121
|
+
@grade = named[:grade]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def render(context)
|
|
125
|
+
client = SiteHelpers.client_for(context)
|
|
126
|
+
return "" unless client
|
|
127
|
+
deals = client.deals(category: @category, limit: @limit, sort: @sort, grade: @grade)
|
|
128
|
+
items = deals["data"] || []
|
|
129
|
+
body = items.map do |d|
|
|
130
|
+
%(<a class="shopsavvy-deal" href="#{d["url"] || "https://shopsavvy.com"}" rel="noopener">) +
|
|
131
|
+
(d["image"] ? %(<img src="#{d["image"]}" alt="#{d["name"]}" loading="lazy" />) : "") +
|
|
132
|
+
%(<div class="shopsavvy-deal__body"><p class="shopsavvy-deal__name">#{d["name"]}</p>) +
|
|
133
|
+
%(<p class="shopsavvy-deal__price"><span class="shopsavvy-deal__sale">$#{d["price"]}</span>) +
|
|
134
|
+
(d["strikethrough"] ? %(<span class="shopsavvy-deal__msrp">$#{d["strikethrough"]}</span>) : "") +
|
|
135
|
+
%(</p><p class="shopsavvy-deal__retailer">#{d["retailer"]}</p></div></a>)
|
|
136
|
+
end.join
|
|
137
|
+
%(<div class="shopsavvy-deals-grid">#{body}</div>)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# {% shopsavvy_price <identifier> %} — emits a plain price string.
|
|
142
|
+
class PriceTag < Liquid::Tag
|
|
143
|
+
def initialize(tag_name, markup, tokens)
|
|
144
|
+
super
|
|
145
|
+
positional, _named = ArgsParser.parse(markup)
|
|
146
|
+
@identifier = positional.first
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def render(context)
|
|
150
|
+
client = SiteHelpers.client_for(context)
|
|
151
|
+
return "" unless client && @identifier
|
|
152
|
+
identifier = Liquid::Template.parse(@identifier).render(context)
|
|
153
|
+
offers = client.current_offers(identifier)
|
|
154
|
+
cheapest = (offers["data"] || []).min_by { |o| o["price"].to_f }
|
|
155
|
+
cheapest ? "$#{cheapest["price"]}" : ""
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# {{ "012345678905" | shopsavvy_lookup }}
|
|
162
|
+
module Jekyll
|
|
163
|
+
module Shopsavvy
|
|
164
|
+
module Filters
|
|
165
|
+
def shopsavvy_lookup(identifier)
|
|
166
|
+
site = @context.registers[:site]
|
|
167
|
+
api_key = ENV["SHOPSAVVY_API_KEY"] || site.config.dig("shopsavvy", "api_key")
|
|
168
|
+
return {} if api_key.nil?
|
|
169
|
+
client = Jekyll::Shopsavvy::Client.new(api_key: api_key, cache: (site.config["__shopsavvy_cache"] ||= {}))
|
|
170
|
+
client.product_details(identifier.to_s)
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
Jekyll.logger.warn("ShopSavvy:", "lookup failed: #{e.message}")
|
|
173
|
+
{}
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require "jekyll"
|
|
2
|
+
require_relative "jekyll/shopsavvy/version"
|
|
3
|
+
require_relative "jekyll/shopsavvy/client"
|
|
4
|
+
require_relative "jekyll/shopsavvy/tags"
|
|
5
|
+
|
|
6
|
+
Liquid::Template.register_tag("shopsavvy_product", Jekyll::Shopsavvy::ProductTag)
|
|
7
|
+
Liquid::Template.register_tag("shopsavvy_deals", Jekyll::Shopsavvy::DealsTag)
|
|
8
|
+
Liquid::Template.register_tag("shopsavvy_price", Jekyll::Shopsavvy::PriceTag)
|
|
9
|
+
Liquid::Template.register_filter(Jekyll::Shopsavvy::Filters)
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jekyll-shopsavvy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- ShopSavvy by Monolith Technologies, Inc.
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-05-09 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: jekyll
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.7'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '5.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '3.7'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '5.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rspec
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '3.12'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '3.12'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: bundler
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '2.0'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '2.0'
|
|
60
|
+
description: Adds {% shopsavvy_product %}, {% shopsavvy_deals %}, {% shopsavvy_price
|
|
61
|
+
%} Liquid tags and a shopsavvy_lookup filter for embedding live product cards, deal
|
|
62
|
+
feeds, and price lookups in Jekyll sites at build time.
|
|
63
|
+
email:
|
|
64
|
+
- business@shopsavvy.com
|
|
65
|
+
executables: []
|
|
66
|
+
extensions: []
|
|
67
|
+
extra_rdoc_files: []
|
|
68
|
+
files:
|
|
69
|
+
- LICENSE
|
|
70
|
+
- README.md
|
|
71
|
+
- lib/jekyll-shopsavvy.rb
|
|
72
|
+
- lib/jekyll/shopsavvy/client.rb
|
|
73
|
+
- lib/jekyll/shopsavvy/tags.rb
|
|
74
|
+
- lib/jekyll/shopsavvy/version.rb
|
|
75
|
+
homepage: https://shopsavvy.com/integrations/jekyll
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata:
|
|
79
|
+
homepage_uri: https://shopsavvy.com/integrations/jekyll
|
|
80
|
+
source_code_uri: https://github.com/shopsavvy/jekyll-shopsavvy
|
|
81
|
+
documentation_uri: https://shopsavvy.com/integrations/jekyll
|
|
82
|
+
changelog_uri: https://github.com/shopsavvy/jekyll-shopsavvy/blob/main/CHANGELOG.md
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 2.7.0
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.2
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: Jekyll Liquid tags and filters powered by the ShopSavvy Data API
|
|
100
|
+
test_files: []
|