jekyll-twitter-plugin 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a8e002d1ba46fca2b0d5afc36bb1b8cedc61573
4
- data.tar.gz: eeb08ca5c7b025a7fc3c201fa8d583e0cb4c4e33
3
+ metadata.gz: eb81bd5f03311d7ea54536c4ae9b66a4958a0581
4
+ data.tar.gz: 8718758139ec729d224592a725e406e3fd18e795
5
5
  SHA512:
6
- metadata.gz: d503778f0ae4b8751cd4931c1d02e0b15e9d297edcf99e36fba66e6f21ff523287e71a109bbe0f0e3961e9889e94d075d21e2012d566c79d2ff0b856f6d202f7
7
- data.tar.gz: 341256c1974e2ac78ba5cd78d39dd3aee63c88695d9314dc84ded6ff77c6724541ee8993d61c88af2118a57e140310d5d35fad427296d9c09e19e194c7de7461
6
+ metadata.gz: 32e14f43bd34316445f6ac38eeb685b4fc5fa554a765022df00febfd45c8d153f4864144e95163e6f03e0fd6c4c46abf99a4bbd999715df4bf04c1c90fc683c0
7
+ data.tar.gz: a7a19a137770446e06eda37ee35ce540f880962d6e1f380b71ee8405d2602fdb2b49b7f803aed58e657eea5cda83acca6fa09bd64fd21f428dd1fe527243a708
data/.gitignore CHANGED
@@ -8,6 +8,7 @@
8
8
  /test/tmp/
9
9
  /test/version_tmp/
10
10
  /tmp/
11
+ .byebug_history
11
12
 
12
13
  ## Specific to RubyMotion:
13
14
  .dat*
@@ -32,3 +33,8 @@ Gemfile.lock
32
33
 
33
34
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
35
  .rvmrc
36
+
37
+ # App specific
38
+ .env
39
+ output_test.html
40
+ .tweet-cache
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,16 @@
1
+ LineLength:
2
+ Enabled: false
3
+
4
+ StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+ Enabled: true
7
+
8
+ Documentation:
9
+ Enabled: false
10
+
11
+ Style/FileName:
12
+ Exclude:
13
+ - 'lib/jekyll-twitter-plugin.rb'
14
+
15
+ Lint/AssignmentInCondition:
16
+ Enabled: false
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ cache: bundler
3
+ sudo: false
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - 2.2.0
9
+ - 2.3.0
10
+ - ruby-head
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
14
+ script: 'bundle exec rspec'
15
+ notifications:
16
+ email:
17
+ - false
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
2
3
 
3
4
  # Specify your gem's dependencies in jekyll-twitter-plugin.gemspec
4
5
  gemspec
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  A Liquid tag plugin for Jekyll that renders Tweets from Twitter API.
4
4
 
5
+ [![Build Status](https://travis-ci.org/rob-murray/jekyll-twitter-plugin.svg?branch=master)](https://travis-ci.org/rob-murray/jekyll-twitter-plugin)
5
6
  [![Gem Version](https://badge.fury.io/rb/jekyll-twitter-plugin.svg)](http://badge.fury.io/rb/jekyll-twitter-plugin)
6
7
 
7
8
 
@@ -9,16 +10,17 @@ A Liquid tag plugin for Jekyll that renders Tweets from Twitter API.
9
10
 
10
11
  A Liquid tag plugin for [Jekyll](http://jekyllrb.com/) that enables Twitter content to be used in any content served by Jekyll, content is fetched from the [Twitter API](https://dev.twitter.com/home).
11
12
 
12
- It is based on the original Jekyll Tweet Tag from [scottwb](https://github.com/scottwb/jekyll-tweet-tag) which has not been updated since Twitter updated their API to require certain preconditions. This version uses the excellent [Twitter gem](https://github.com/sferik/twitter) to make requests and handle authentication.
13
+ It is based on the original [Jekyll Tweet Tag](https://github.com/scottwb/jekyll-tweet-tag) from [scottwb](https://github.com/scottwb/) which has not been updated since Twitter changed their API to require certain preconditions. This version uses the excellent [Twitter gem](https://github.com/sferik/twitter) to make requests and handle authentication.
13
14
 
14
- This plugin replaces the broken plugin mentioned above and uses a different tag name and API - this is by design so that the two plugins can be used, if the original gets fixed.
15
+ This plugin replaces the broken [Jekyll Tweet Tag](https://github.com/scottwb/jekyll-tweet-tag) plugin mentioned above and uses a different tag name and API - this is by design so that the two plugins can be separated and you can be certain which plugin is being used. You can also install this plugin via Rubygems and require it in your Jekyll `_config.yml` file. You can use *ENV* variables or `_config.yml` file for your Twitter API secret keys.
15
16
 
16
17
 
17
18
  ### Features
18
19
 
19
20
  The plugin supports the following features:
20
21
 
21
- * Installed via Rubygems
22
+ * Installed via Rubygems.
23
+ * Twitter API secrets read from *ENV* vars or `_config.yml` file.
22
24
  * [Oembed](#oembed) - Embed a Tweet in familiar Twitter styling.
23
25
  * [Caching](#caching) - Twitter API responses can be cached to avoid hitting request limits.
24
26
 
@@ -27,10 +29,12 @@ The plugin supports the following features:
27
29
 
28
30
  * Twitter oauth credentials - Most Twitter api functions now require authentication. Set up your [application](https://dev.twitter.com/apps/new) and get the credentials.
29
31
 
32
+
30
33
  ### Usage
31
34
 
32
35
  As mentioned by [Jekyll's documentation](http://jekyllrb.com/docs/plugins/#installing-a-plugin) you have two options; manually import the source file or require the plugin as a `gem`.
33
36
 
37
+
34
38
  #### Require gem
35
39
 
36
40
  Install the gem, add it to your Gemfile;
@@ -45,8 +49,11 @@ Add the `jekyll-twitter-plugin` to your site `_config.yml` file for Jekyll to im
45
49
  gems: ['jekyll-twitter-plugin']
46
50
  ```
47
51
 
52
+
48
53
  #### Manual import
49
54
 
55
+ > Note: this is deprecated and support will be removed in a later version.
56
+
50
57
  Just download the source file into your `_plugins` directory, e.g.
51
58
 
52
59
  ```bash
@@ -55,6 +62,7 @@ $ mkdir -p _plugins && cd _plugins
55
62
  $ wget https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/lib/jekyll-twitter-plugin.rb
56
63
  ```
57
64
 
65
+
58
66
  #### Credentials
59
67
 
60
68
  Your Twitter application authentication credentials are private - do not distribute these!
@@ -80,8 +88,11 @@ environment variables.
80
88
 
81
89
  ```bash
82
90
  $ export TWITTER_CONSUMER_KEY=foo etc.
91
+ # Or given a file .env with the keys and secrets
92
+ $ export $(cat .env | xargs)
83
93
  ```
84
94
 
95
+
85
96
  #### Plugin tag usage
86
97
 
87
98
  To use the plugin, in your source content use the tag `twitter` and then pass additional parameters to the plugin.
@@ -90,27 +101,41 @@ To use the plugin, in your source content use the tag `twitter` and then pass ad
90
101
  {% plugin_type api_type *params %}
91
102
  ```
92
103
 
93
- * `plugin_type` - Either `twitter` or `twitternocache`.
94
- * `api_type` - The Twitter API to use, check below for supported APIs.
95
- * `*params` - Parameters for the API separated by spaces. Refer below and to respective Twitter API documentation for available parameters.
104
+ | Argument | Required? | Description |
105
+ |---|---|---|
106
+ | `plugin_type` | Yes | Either `twitter` or `twitternocache` (same as `twitter` but does not cache api responses) |
107
+ | `api_type` | No - defaults to **oembed** | The Twitter API to use, check below for supported APIs. |
108
+ | `*params` | Yes* | Parameters for the API separated by spaces. Refer below and to respective Twitter API documentation for available parameters. |
109
+
110
+ * Required arguments depend on the API used.
111
+
96
112
 
97
113
  ### Supported Twitter APIs
98
114
 
99
115
  The following Twitter APIs are supported.
100
116
 
101
- #### Oembed
117
+
118
+ #### Oembed - Default
102
119
 
103
120
  The [oembed](https://dev.twitter.com/rest/reference/get/statuses/oembed) API returns html snippet to embed in your app, this will be rendered in the familiar Twitter style.
104
121
 
122
+ This API requires the `status_url` parameter, all others are optional.
123
+
105
124
  ```liquid
125
+ # With api_type specified
106
126
  {% twitter oembed status_url *options %}
127
+ # 'oembed' autoselected by default
128
+ {% twitter status_url *options %}
107
129
 
108
- # Example
130
+ # Basic example
109
131
  {% twitter oembed https://twitter.com/rubygems/status/518821243320287232 %}
132
+ # Oembed default example
133
+ {% twitter https://twitter.com/rubygems/status/518821243320287232 %}
110
134
  # With options
111
135
  {% twitter oembed https://twitter.com/rubygems/status/518821243320287232 align='right' width='350' %}
112
136
  ```
113
137
 
138
+
114
139
  ### Output
115
140
 
116
141
  As with the original plugin, all content will be rendered inside a div with the classes 'embed' and 'twitter'
@@ -129,6 +154,7 @@ If the Twitter client receives one of `Twitter::Error::NotFound, Twitter::Error:
129
154
 
130
155
  > There was a '{error name}' error fetching Tweet '{Tweet status url}'
131
156
 
157
+
132
158
  ### Caching
133
159
 
134
160
  Twitter API responses can be cached to speed up Jekyll site builds and avoid going over API limits. The reponses will be cached in a directory within your Jekyll project called `.tweet-cache`, ensure that this is not commit to source control.
@@ -145,12 +171,20 @@ It is possible to disable caching by using the specific `twitternocache` tag.
145
171
 
146
172
  ```
147
173
 
174
+
148
175
  ### Contributions
149
176
 
150
- I've tried hard to keep all classes and code in the one `lib/jekyll-twitter-plugin.rb` file so that people can just grab this file and include in their Jekyll `_plugins` directory if they do not want to use the Gem. This may have to be dropped if the one file gets too overwhelming.
177
+ I've tried hard to keep all classes and code in the one `lib/jekyll-twitter-plugin.rb` file so that people can just grab this file and include in their Jekyll `_plugins` directory if they do not want to use the Gem. This will be dropped in a later version.
151
178
 
152
179
  Please use the GitHub pull-request mechanism to submit contributions.
153
180
 
181
+ There is a quick integration test in `spec/integration_tests.rb` that needs API keys and will use the public api and output a file `output_test.html`. Run this with the following command:
182
+
183
+ ```ruby
184
+ $ ruby spec/integration_tests.rb
185
+ ```
186
+
187
+
154
188
  ### License
155
189
 
156
190
  This project is available for use under the MIT software license.
data/Rakefile CHANGED
@@ -1 +1,2 @@
1
- require 'bundler/gem_tasks'
1
+ # frozen_string_literal: true
2
+ require "bundler/gem_tasks"
@@ -1,23 +1,26 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path("../lib", __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
 
5
6
  Gem::Specification.new do |spec|
6
- spec.name = 'jekyll-twitter-plugin'
7
- spec.version = '1.3.1'
8
- spec.authors = ['Rob Murray']
9
- spec.email = ['robmurray17@gmail.com']
10
- spec.summary = 'A Liquid tag plugin for Jekyll that renders Tweets from Twitter API'
11
- spec.homepage = 'https://github.com/rob-murray/jekyll-twitter-plugin'
12
- spec.license = 'MIT'
7
+ spec.name = "jekyll-twitter-plugin"
8
+ spec.version = "1.4.0"
9
+ spec.authors = ["Rob Murray"]
10
+ spec.email = ["robmurray17@gmail.com"]
11
+ spec.summary = "A Liquid tag plugin for Jekyll that renders Tweets from Twitter API"
12
+ spec.homepage = "https://github.com/rob-murray/jekyll-twitter-plugin"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 1.9.3"
13
15
 
14
16
  spec.files = `git ls-files -z`.split("\x0")
15
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
- spec.require_paths = ['lib']
18
+ spec.require_paths = ["lib"]
17
19
 
18
- spec.add_development_dependency 'bundler', '~> 1.7'
19
- spec.add_development_dependency 'rake'
20
- spec.add_development_dependency 'pry-byebug'
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ spec.add_development_dependency "byebug" if RUBY_VERSION >= "2.0"
21
24
 
22
- spec.add_dependency 'twitter', '~> 5.11'
25
+ spec.add_dependency "twitter", "~> 5.16"
23
26
  end
@@ -1,14 +1,26 @@
1
- require 'twitter'
1
+ # frozen_string_literal: true
2
+ require "fileutils"
3
+ require "twitter"
2
4
 
3
5
  ##
4
6
  # A Liquid tag plugin for Jekyll that renders Tweets from Twitter API.
5
7
  # https://github.com/rob-murray/jekyll-twitter-plugin
6
8
  #
7
-
8
9
  module TwitterJekyll
10
+ MissingApiKeyError = Class.new(StandardError)
11
+ TwitterSecrets = Struct.new(:consumer_key, :consumer_secret, :access_token, :access_token_secret) do
12
+ def self.build(source, keys)
13
+ new(*source.values_at(*keys))
14
+ end
15
+ end
16
+ CONTEXT_API_KEYS = %w(consumer_key consumer_secret access_token access_token_secret).freeze
17
+ ENV_API_KEYS = %w(TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET TWITTER_ACCESS_TOKEN TWITTER_ACCESS_TOKEN_SECRET).freeze
18
+ TWITTER_STATUS_URL = %r{\Ahttps?://twitter\.com/(:#!\/)?\w+/status(es)?/\d+}i
19
+ REFER_TO_README = "Please see 'https://github.com/rob-murray/jekyll-twitter-plugin' for usage."
20
+
9
21
  class FileCache
10
22
  def initialize(path)
11
- @cache_folder = File.expand_path path
23
+ @cache_folder = File.expand_path path
12
24
  FileUtils.mkdir_p @cache_folder
13
25
  end
14
26
 
@@ -21,7 +33,7 @@ module TwitterJekyll
21
33
  file_to_write = cache_file(cache_filename(key))
22
34
  data_to_write = JSON.generate data.to_h
23
35
 
24
- File.open(file_to_write, 'w') do |f|
36
+ File.open(file_to_write, "w") do |f|
25
37
  f.write(data_to_write)
26
38
  end
27
39
  end
@@ -38,6 +50,8 @@ module TwitterJekyll
38
50
  end
39
51
 
40
52
  class NullCache
53
+ def initialize(*_args); end
54
+
41
55
  def read(_key); end
42
56
 
43
57
  def write(_key, _data); end
@@ -52,7 +66,7 @@ module TwitterJekyll
52
66
  end
53
67
 
54
68
  class TwitterApi
55
- ERRORS_TO_IGNORE = [Twitter::Error::NotFound, Twitter::Error::Forbidden]
69
+ ERRORS_TO_IGNORE = [Twitter::Error::NotFound, Twitter::Error::Forbidden].freeze
56
70
 
57
71
  attr_reader :error
58
72
 
@@ -62,12 +76,12 @@ module TwitterJekyll
62
76
  parse_args(params)
63
77
  end
64
78
 
79
+ def fetch; end
80
+
65
81
  private
66
82
 
67
83
  def id_from_status_url(url)
68
- if url.to_s =~ /([^\/]+$)/
69
- Regexp.last_match[1]
70
- end
84
+ Regexp.last_match[1] if url.to_s =~ %r{([^\/]+$)}
71
85
  end
72
86
 
73
87
  def find_tweet(id)
@@ -81,17 +95,13 @@ module TwitterJekyll
81
95
 
82
96
  def parse_args(args)
83
97
  @params ||= begin
84
- params = {}
85
- args.each do |arg|
86
- k, v = arg.split('=').map(&:strip)
98
+ args.each_with_object({}) do |arg, params|
99
+ k, v = arg.split("=").map(&:strip)
87
100
  if k && v
88
- if v =~ /^'(.*)'$/
89
- v = Regexp.last_match[1]
90
- end
101
+ v = Regexp.last_match[1] if v =~ /^'(.*)'$/
91
102
  params[k] = v
92
103
  end
93
104
  end
94
- params
95
105
  end
96
106
  end
97
107
 
@@ -118,17 +128,13 @@ module TwitterJekyll
118
128
  private
119
129
 
120
130
  def key
121
- '%s-%s' % [@status_url, @params.to_s]
131
+ format("%s-%s", @status_url, @params.to_s)
122
132
  end
123
133
  end
124
134
 
125
- class UnknownTypeClient
126
- include TwitterJekyll::Cacheable
127
-
128
- def fetch; end
129
- end
130
-
131
135
  class ErrorResponse
136
+ attr_reader :error
137
+
132
138
  def initialize(error)
133
139
  @error = error
134
140
  end
@@ -143,18 +149,22 @@ module TwitterJekyll
143
149
  end
144
150
 
145
151
  class TwitterTag < Liquid::Tag
146
- ERROR_BODY_TEXT = '<p>Tweet could not be processed</p>'
152
+ ERROR_BODY_TEXT = "<p>Tweet could not be processed</p>"
153
+ DEFAULT_API_TYPE = "oembed"
154
+
155
+ attr_writer :cache # for testing
147
156
 
148
157
  def initialize(_name, params, _tokens)
149
158
  super
150
- @cache = FileCache.new('./.tweet-cache')
151
- args = params.split(/\s+/).map(&:strip)
152
- @api_type = args.shift
153
- @params = args
159
+ @api_type, @params = parse_params(params)
160
+ end
161
+
162
+ def self.cache_klass
163
+ FileCache
154
164
  end
155
165
 
156
166
  def render(context)
157
- secrets = extract_twitter_secrets_from_context(context) || extract_twitter_secrets_from_env
167
+ secrets = find_secrets!(context)
158
168
  create_twitter_rest_client(secrets)
159
169
  api_client = create_api_client(@api_type, @params)
160
170
  response = cached_response(api_client) || live_response(api_client)
@@ -163,38 +173,48 @@ module TwitterJekyll
163
173
 
164
174
  private
165
175
 
166
- def html_output_for(response)
167
- body = ERROR_BODY_TEXT
176
+ def cache
177
+ @cache ||= self.class.cache_klass.new("./.tweet-cache")
178
+ end
168
179
 
169
- if response
170
- body = response.html || body
171
- end
180
+ def html_output_for(response)
181
+ body = (response.html if response) || ERROR_BODY_TEXT
172
182
 
173
183
  "<div class='embed twitter'>#{body}</div>"
174
184
  end
175
185
 
176
186
  def live_response(api_client)
177
187
  if response = api_client.fetch
178
- @cache.write(api_client.cache_key, response)
188
+ cache.write(api_client.cache_key, response)
179
189
  response
180
190
  end
181
191
  end
182
192
 
183
193
  def cached_response(api_client)
184
- response = @cache.read(api_client.cache_key)
194
+ response = cache.read(api_client.cache_key)
185
195
  OpenStruct.new(response) unless response.nil?
186
196
  end
187
197
 
188
- def create_api_client(api_type, params)
189
- klass_name = api_type.capitalize
190
- if TwitterJekyll.const_defined?(klass_name)
191
- api_client_klass = TwitterJekyll.const_get(klass_name)
192
- api_client_klass.new(@twitter_client, params)
198
+ def parse_params(params)
199
+ args = params.split(/\s+/).map(&:strip)
200
+
201
+ case args[0]
202
+ when DEFAULT_API_TYPE
203
+ api_type, *api_args = args
204
+ [api_type, api_args]
205
+ when TWITTER_STATUS_URL
206
+ [DEFAULT_API_TYPE, args]
193
207
  else
194
- UnknownTypeClient.new
208
+ invalid_args!(args)
195
209
  end
196
210
  end
197
211
 
212
+ def create_api_client(api_type, params)
213
+ klass_name = api_type.capitalize
214
+ api_client_klass = TwitterJekyll.const_get(klass_name)
215
+ api_client_klass.new(@twitter_client, params)
216
+ end
217
+
198
218
  def create_twitter_rest_client(secrets)
199
219
  @twitter_client = Twitter::REST::Client.new do |config|
200
220
  config.consumer_key = secrets.consumer_key
@@ -204,29 +224,43 @@ module TwitterJekyll
204
224
  end
205
225
  end
206
226
 
207
- TwitterSecrets = Struct.new(:consumer_key, :consumer_secret, :access_token, :access_token_secret)
227
+ def find_secrets!(context)
228
+ extract_twitter_secrets_from_context(context) || extract_twitter_secrets_from_env || missing_keys!
229
+ end
208
230
 
209
231
  def extract_twitter_secrets_from_context(context)
210
- TwitterSecrets.new(context.registers[:site].config['twitter']['consumer_key'], context.registers[:site].config['twitter']['consumer_secret'], context.registers[:site].config['twitter']['access_token'], context.registers[:site].config['twitter']['access_token_secret']) if context_has_twitter_secrets?(context)
211
- end
232
+ twitter_secrets = context.registers[:site].config.fetch("twitter", {})
233
+ return unless store_has_keys?(twitter_secrets, CONTEXT_API_KEYS)
212
234
 
213
- def context_has_twitter_secrets?(context)
214
- twitter_secrets = context.registers[:site].config['twitter'] || {}
215
- ['consumer_key', 'consumer_secret', 'access_token', 'access_token_secret'].all? {|s| twitter_secrets.key?(s)}
235
+ TwitterSecrets.build(twitter_secrets, CONTEXT_API_KEYS)
216
236
  end
217
237
 
218
238
  def extract_twitter_secrets_from_env
219
- TwitterSecrets.new(ENV.fetch('TWITTER_CONSUMER_KEY'), ENV.fetch('TWITTER_CONSUMER_SECRET'), ENV.fetch('TWITTER_ACCESS_TOKEN'), ENV.fetch('TWITTER_ACCESS_TOKEN_SECRET'))
239
+ return unless store_has_keys?(ENV, ENV_API_KEYS)
240
+
241
+ TwitterSecrets.build(ENV, ENV_API_KEYS)
242
+ end
243
+
244
+ def store_has_keys?(store, keys)
245
+ keys.all? { |required_key| store.key?(required_key) }
246
+ end
247
+
248
+ def missing_keys!
249
+ raise MissingApiKeyError, "Twitter API keys not found. You can specify these in Jekyll config or ENV. #{REFER_TO_README}"
250
+ end
251
+
252
+ def invalid_args!(arguments)
253
+ formatted_args = Array(arguments).join(" ")
254
+ raise ArgumentError, "Invalid arguments '#{formatted_args}' passed to 'jekyll-twitter-plugin'. #{REFER_TO_README}"
220
255
  end
221
256
  end
222
257
 
223
258
  class TwitterTagNoCache < TwitterTag
224
- def initialize(_tag_name, _text, _token)
225
- super
226
- @cache = NullCache.new
259
+ def self.cache_klass
260
+ NullCache
227
261
  end
228
262
  end
229
263
  end
230
264
 
231
- Liquid::Template.register_tag('twitter', TwitterJekyll::TwitterTag)
232
- Liquid::Template.register_tag('twitternocache', TwitterJekyll::TwitterTagNoCache)
265
+ Liquid::Template.register_tag("twitter", TwitterJekyll::TwitterTag)
266
+ Liquid::Template.register_tag("twitternocache", TwitterJekyll::TwitterTagNoCache)
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # Basic integration example - run code to produce html output
3
+ #
4
+ # * Requires .env populated with valid Twitter API creds.
5
+ #
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
7
+ require_relative "./support/jekyll_template"
8
+ require "jekyll-twitter-plugin"
9
+ require "erb"
10
+
11
+ OUTPUT_FILENAME = "output_test.html"
12
+ OPTIONS = [
13
+ "oembed https://twitter.com/rubygems/status/518821243320287232",
14
+ "oembed https://twitter.com/rubygems/status/518821243320287232 align='right' width='350'",
15
+ "https://twitter.com/rubygems/status/518821243320287232",
16
+ "oembed https://twitter.com/rubygems/status/missing"
17
+ ].freeze
18
+
19
+ COLOUR_MAP = {
20
+ red: 31,
21
+ green: 32,
22
+ yellow: 33,
23
+ blue: 34
24
+ }.freeze
25
+
26
+ def say_with_colour(text, colour_name)
27
+ colour_code = COLOUR_MAP.fetch(colour_name)
28
+ puts "\e[#{colour_code}m#{text}\e[0m"
29
+ end
30
+
31
+ class TwitterRenderer
32
+ Context = Struct.new(:registers)
33
+ Site = Struct.new(:config)
34
+
35
+ def initialize(options)
36
+ @options = options
37
+ @jekyll_context = Context.new(site: Site.new({}))
38
+ end
39
+
40
+ def render
41
+ ERB.new(template)
42
+ .result(binding)
43
+ .gsub!("src=\"//", "src=\"https://")
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :options, :jekyll_context
49
+
50
+ def render_twitter_tag(params)
51
+ say_with_colour "Fetching with params: #{params}", :yellow
52
+ TwitterJekyll::TwitterTag.new(nil, params, nil).render(jekyll_context)
53
+ end
54
+
55
+ def template
56
+ <<~HTML
57
+ <html>
58
+ <body>
59
+ <h1>jekyll-twitter-plugin output tests</h1>
60
+ <% options.each do |option| %>
61
+ <h3><%= option %></h3>
62
+ <%= render_twitter_tag(option) %>
63
+ <hr>
64
+ <% end %>
65
+ </body>
66
+ </html>
67
+ HTML
68
+ end
69
+ end
70
+
71
+ def main
72
+ rederer = TwitterRenderer.new(OPTIONS)
73
+ File.open(OUTPUT_FILENAME, "w") do |f|
74
+ f.write rederer.render
75
+ end
76
+ end
77
+
78
+ if __FILE__ == $PROGRAM_NAME
79
+ say_with_colour "Running integration tests...", :red
80
+ main
81
+ say_with_colour "Created file: #{OUTPUT_FILENAME}", :green
82
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ RSpec.describe TwitterJekyll::Oembed do
3
+ let(:api_client) { double("Twitter::REST::Client") }
4
+ let(:status_response) { double }
5
+ subject { described_class.new(api_client, params) }
6
+
7
+ describe "parsing status id" do
8
+ let(:params) { ["https://twitter.com/twitter_user/status/12345"] }
9
+
10
+ it "parses the status id from the tweet url correctly" do
11
+ status_response = double
12
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
13
+
14
+ expect(api_client).to receive(:status).with(12_345).and_return(status_response)
15
+ expect(api_client).to receive(:oembed).with(status_response, {}).and_return("")
16
+ subject.fetch
17
+ end
18
+ end
19
+
20
+ describe "parsing options" do
21
+ context "with extra options" do
22
+ let(:params) { ["https://twitter.com/twitter_user/status/12345", "align='right'", "width='350'"] }
23
+
24
+ it "passes to api" do
25
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
26
+ allow(api_client).to receive(:status).with(12_345).and_return(status_response)
27
+
28
+ expect(api_client).to receive(:oembed).with(status_response, "align" => "right", "width" => "350").and_return("")
29
+ subject.fetch
30
+ end
31
+ end
32
+
33
+ context "with an invalid option" do
34
+ let(:params) { ["https://twitter.com/twitter_user/status/12345", "align=", "width='350'"] }
35
+
36
+ it "ignores param" do
37
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
38
+ allow(api_client).to receive(:status).with(12_345).and_return(status_response)
39
+
40
+ expect(api_client).to receive(:oembed).with(status_response, "width" => "350").and_return("")
41
+ subject.fetch
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#fetch" do
47
+ let(:params) { ["https://twitter.com/twitter_user/status/12345"] }
48
+
49
+ it "returns response from api" do
50
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
51
+ allow(api_client).to receive(:status).with(12_345).and_return(status_response)
52
+
53
+ expect(api_client).to receive(:oembed).with(status_response, {}).and_return("api response")
54
+ expect(subject.fetch).to eq "api response"
55
+ end
56
+
57
+ context "when status is not found" do
58
+ it "returns error" do
59
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
60
+ allow(api_client).to receive(:status).with(12_345).and_raise(Twitter::Error::NotFound)
61
+
62
+ expect(api_client).not_to receive(:oembed)
63
+ result = subject.fetch
64
+ expect(result).to be_a(TwitterJekyll::ErrorResponse)
65
+ expect(result.error).to eq "There was a 'Twitter::Error::NotFound' error fetching Tweet 'https://twitter.com/twitter_user/status/12345'"
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "#cache_key" do
71
+ context "with no params" do
72
+ let(:params) { ["https://twitter.com/twitter_user/status/12345"] }
73
+
74
+ it "matches on status url" do
75
+ oembed_1 = TwitterJekyll::Oembed.new(api_client, params.dup)
76
+ oembed_2 = TwitterJekyll::Oembed.new(api_client, params.dup)
77
+
78
+ expect(
79
+ oembed_1.cache_key == oembed_2.cache_key
80
+ ).to be true
81
+ end
82
+ end
83
+
84
+ context "with params" do
85
+ let(:params_1) { ["https://twitter.com/twitter_user/status/12345", "align='right'"] }
86
+ let(:params_2) { ["https://twitter.com/twitter_user/status/12345", "align='left'"] }
87
+
88
+ it "matches on keys and values" do
89
+ oembed_1 = TwitterJekyll::Oembed.new(api_client, params_1.dup)
90
+ oembed_2 = TwitterJekyll::Oembed.new(api_client, params_2.dup)
91
+ oembed_3 = TwitterJekyll::Oembed.new(api_client, params_1.dup)
92
+
93
+ expect(
94
+ oembed_1.cache_key == oembed_2.cache_key
95
+ ).to be false
96
+
97
+ expect(
98
+ oembed_1.cache_key == oembed_3.cache_key
99
+ ).to be true
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
3
+ require "support/jekyll_template"
4
+ require "support/shared_contexts"
5
+ require "jekyll-twitter-plugin"
6
+ require "byebug" if RUBY_VERSION >= "2.0"
7
+
8
+ #
9
+ # The `.rspec` file also contains a few flags that are not defaults but that
10
+ # users commonly want.
11
+ #
12
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
13
+ RSpec.configure do |config|
14
+ config.expect_with :rspec do |expectations|
15
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
16
+ end
17
+
18
+ config.mock_with :rspec do |mocks|
19
+ mocks.verify_partial_doubles = true
20
+ end
21
+
22
+ config.shared_context_metadata_behavior = :apply_to_host_groups
23
+ config.filter_run_when_matching :focus
24
+ config.disable_monkey_patching!
25
+ config.warnings = true
26
+ config.default_formatter = "doc" if config.files_to_run.one?
27
+
28
+ config.order = :random
29
+ Kernel.srand config.seed
30
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ # Hack ...or stub Liquid classes and methods used in plugin
3
+ module Liquid
4
+ class Tag
5
+ def initialize(*_); end
6
+ end
7
+
8
+ class Template
9
+ def self.register_tag(*_); end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ RSpec.shared_context "without cached response" do
3
+ let(:cache) { null_cache }
4
+
5
+ before do
6
+ subject.cache = cache
7
+ end
8
+
9
+ def null_cache
10
+ double("TwitterJekyll::NullCache", read: nil, write: nil)
11
+ end
12
+ end
13
+
14
+ RSpec.shared_context "with any oembed request and response" do
15
+ let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
16
+ let(:response) { OpenStruct.new(html: "<p>tweet html</p>") }
17
+
18
+ before do
19
+ allow(api_client).to receive(:oembed).and_return(response)
20
+ end
21
+ end
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+ RSpec.describe TwitterJekyll::TwitterTag do
3
+ let(:context) { double.as_null_object }
4
+ let(:options) { "" }
5
+ subject { described_class.new(nil, options, nil) }
6
+
7
+ describe "output from oembed request" do
8
+ let(:api_response_hash) do
9
+ {
10
+ "url" => "https://twitter.com/twitter_user/status/12345",
11
+ "author_name" => "twitter user",
12
+ "author_url" => "https://twitter.com/twitter_user",
13
+ "html" => "<p>tweet html</p>",
14
+ "width" => 550,
15
+ "height" => nil,
16
+ "type" => "rich",
17
+ "cache_age" => "3153600000",
18
+ "provider_name" => "Twitter",
19
+ "provider_url" => "https://twitter.com",
20
+ "version" => "1.0"
21
+ }
22
+ end
23
+ let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
24
+
25
+ context "with cached response" do
26
+ let(:cache) { double("TwitterJekyll::FileCache") }
27
+ before do
28
+ subject.cache = cache
29
+ end
30
+
31
+ it "renders response from cache" do
32
+ allow(Twitter::REST::Client).to receive(:new).and_return(double.as_null_object)
33
+ expect(cache).to receive(:read).with(an_instance_of(String)).and_return(api_response_hash)
34
+
35
+ output = subject.render(context)
36
+ expect_output_to_match_tag_content(output, api_response_hash.fetch("html"))
37
+ end
38
+ end
39
+
40
+ context "without cached response" do
41
+ let(:cache) { double("TwitterJekyll::FileCache") }
42
+ let(:response) { build_response_object(api_response_hash) }
43
+ before do
44
+ subject.cache = cache
45
+ allow(cache).to receive(:read).with(an_instance_of(String)).and_return(nil)
46
+ end
47
+
48
+ context "with successful api request" do
49
+ it "renders response from api and writes to cache" do
50
+ api_client = double("Twitter::REST::Client", status: double)
51
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
52
+
53
+ expect(api_client).to receive(:oembed).and_return(response)
54
+ expect(cache).to receive(:write).with(an_instance_of(String), response)
55
+
56
+ output = subject.render(context)
57
+ expect_output_to_match_tag_content(output, api_response_hash.fetch("html"))
58
+ end
59
+ end
60
+
61
+ context "with a status not found api request" do
62
+ it "renders response from api and does not write to cache" do
63
+ api_client = double("Twitter::REST::Client", status: double)
64
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
65
+
66
+ expect(api_client).to receive(:status).and_raise(Twitter::Error::NotFound)
67
+ expect(cache).not_to receive(:write).with(an_instance_of(String), response)
68
+
69
+ output = subject.render(context)
70
+ expect_output_to_have_error(output, Twitter::Error::NotFound)
71
+ end
72
+ end
73
+
74
+ context "with a status request not permitted api request" do
75
+ it "renders response from api and does not write to cache" do
76
+ api_client = double("Twitter::REST::Client", status: double)
77
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
78
+
79
+ expect(api_client).to receive(:status).and_raise(Twitter::Error::Forbidden)
80
+ expect(cache).not_to receive(:write).with(an_instance_of(String), response)
81
+
82
+ output = subject.render(context)
83
+ expect_output_to_have_error(output, Twitter::Error::Forbidden)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "With an invalid request type" do
90
+ context "without any arguments" do
91
+ let(:options) { "" }
92
+
93
+ it "raises an exception" do
94
+ expect_to_raise_invalid_args_error(options) do
95
+ tag = described_class.new(nil, options, nil)
96
+ tag.render(context)
97
+ end
98
+ end
99
+ end
100
+
101
+ context "with an api request type not supported" do
102
+ let(:options) { "unsupported https://twitter.com/twitter_user/status/12345" }
103
+
104
+ it "raises an exception" do
105
+ expect_to_raise_invalid_args_error(options) do
106
+ tag = described_class.new(nil, options, nil)
107
+ tag.render(context)
108
+ end
109
+ end
110
+ end
111
+
112
+ context "without an api request type and no valid status url" do
113
+ let(:options) { "https://anything.com/twitter_user/status/12345" }
114
+
115
+ it "raises an exception" do
116
+ expect_to_raise_invalid_args_error(options) do
117
+ tag = described_class.new(nil, options, nil)
118
+ tag.render(context)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "parsing api request type" do
125
+ include_context "without cached response"
126
+ let(:response) { OpenStruct.new(html: "anything") }
127
+ let(:status) { double }
128
+
129
+ context "with oembed requested" do
130
+ let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
131
+
132
+ it "uses the oembed api" do
133
+ api_client = double("Twitter::REST::Client", status: status)
134
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
135
+
136
+ expect(api_client).to receive(:oembed).with(status, {}).and_return(response)
137
+ subject.render(context)
138
+ end
139
+ end
140
+
141
+ context "without an api request type" do
142
+ let(:options) { "https://twitter.com/twitter_user/status/12345" }
143
+
144
+ it "uses the default oembed api type" do
145
+ api_client = double("Twitter::REST::Client", status: status)
146
+ allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
147
+
148
+ expect(api_client).to receive(:oembed).with(status, {}).and_return(response)
149
+ subject.render(context)
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "parsing api secrets" do
155
+ include_context "without cached response"
156
+ include_context "with any oembed request and response"
157
+ let(:api_client) { double("Twitter::REST::Client", status: double) }
158
+ let(:config_builder) { double }
159
+
160
+ before do
161
+ allow(Twitter::REST::Client).to receive(:new).and_yield(config_builder).and_return(api_client)
162
+ end
163
+
164
+ context "with api secrets provided by ENV" do
165
+ let(:context) { double("context", registers: { site: double(config: {}) }) }
166
+ before do
167
+ stub_const("ENV", "TWITTER_CONSUMER_KEY" => "consumer_key",
168
+ "TWITTER_CONSUMER_SECRET" => "consumer_secret",
169
+ "TWITTER_ACCESS_TOKEN" => "access_token",
170
+ "TWITTER_ACCESS_TOKEN_SECRET" => "access_token_secret")
171
+ end
172
+
173
+ it "creates api client correctly" do
174
+ expect(config_builder).to receive(:consumer_key=).with("consumer_key")
175
+ expect(config_builder).to receive(:consumer_secret=).with("consumer_secret")
176
+ expect(config_builder).to receive(:access_token=).with("access_token")
177
+ expect(config_builder).to receive(:access_token_secret=).with("access_token_secret")
178
+
179
+ subject.render(context)
180
+ end
181
+ end
182
+
183
+ context "with api secrets provided by Jekyll config" do
184
+ let(:context) do
185
+ api_secrets = %w(consumer_key consumer_secret access_token access_token_secret)
186
+ .each_with_object({}) { |secret, h| h[secret] = secret }
187
+ double("context", registers:
188
+ { site: double(config: { "twitter" => api_secrets }) })
189
+ end
190
+ before do
191
+ stub_const("ENV", {})
192
+ end
193
+
194
+ it "creates api client correctly" do
195
+ expect(config_builder).to receive(:consumer_key=).with("consumer_key")
196
+ expect(config_builder).to receive(:consumer_secret=).with("consumer_secret")
197
+ expect(config_builder).to receive(:access_token=).with("access_token")
198
+ expect(config_builder).to receive(:access_token_secret=).with("access_token_secret")
199
+
200
+ subject.render(context)
201
+ end
202
+ end
203
+
204
+ context "with no api secrets provided" do
205
+ let(:context) { double("context", registers: { site: double(config: {}) }) }
206
+ before do
207
+ stub_const("ENV", {})
208
+ end
209
+
210
+ it "raises an exception" do
211
+ expect do
212
+ subject.render(context)
213
+ end.to raise_error(TwitterJekyll::MissingApiKeyError)
214
+ end
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def expect_output_to_match_tag_content(actual, content)
221
+ expect(actual).to eq(
222
+ "<div class='embed twitter'>#{content}</div>"
223
+ )
224
+ end
225
+
226
+ def expect_output_to_have_error(actual, error, tweet_url = "https://twitter.com/twitter_user/status/12345")
227
+ expect_output_to_match_tag_content(actual, "<p>There was a '#{error}' error fetching Tweet '#{tweet_url}'</p>")
228
+ end
229
+
230
+ def expect_to_raise_invalid_args_error(options)
231
+ raise unless block_given?
232
+
233
+ message = "Invalid arguments '#{options}' passed to 'jekyll-twitter-plugin'. Please see 'https://github.com/rob-murray/jekyll-twitter-plugin' for usage."
234
+ expect do
235
+ yield
236
+ end.to raise_error(ArgumentError, message)
237
+ end
238
+
239
+ # The twitter gem responds with a struct like object so we do too.
240
+ def build_response_object(response)
241
+ OpenStruct.new(response)
242
+ end
243
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-twitter-plugin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Murray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-05 00:00:00.000000000 Z
11
+ date: 2016-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,21 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: pry-byebug
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
@@ -58,14 +72,14 @@ dependencies:
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '5.11'
75
+ version: '5.16'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '5.11'
82
+ version: '5.16'
69
83
  description:
70
84
  email:
71
85
  - robmurray17@gmail.com
@@ -74,12 +88,21 @@ extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
76
90
  - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - ".travis.yml"
77
94
  - Gemfile
78
95
  - LICENSE
79
96
  - README.md
80
97
  - Rakefile
81
98
  - jekyll-twitter-plugin.gemspec
82
99
  - lib/jekyll-twitter-plugin.rb
100
+ - spec/integration_tests.rb
101
+ - spec/oembed_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/jekyll_template.rb
104
+ - spec/support/shared_contexts.rb
105
+ - spec/twitter_tag_spec.rb
83
106
  homepage: https://github.com/rob-murray/jekyll-twitter-plugin
84
107
  licenses:
85
108
  - MIT
@@ -92,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
115
  requirements:
93
116
  - - ">="
94
117
  - !ruby/object:Gem::Version
95
- version: '0'
118
+ version: 1.9.3
96
119
  required_rubygems_version: !ruby/object:Gem::Requirement
97
120
  requirements:
98
121
  - - ">="
@@ -100,8 +123,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
123
  version: '0'
101
124
  requirements: []
102
125
  rubyforge_project:
103
- rubygems_version: 2.4.6
126
+ rubygems_version: 2.5.1
104
127
  signing_key:
105
128
  specification_version: 4
106
129
  summary: A Liquid tag plugin for Jekyll that renders Tweets from Twitter API
107
- test_files: []
130
+ test_files:
131
+ - spec/integration_tests.rb
132
+ - spec/oembed_spec.rb
133
+ - spec/spec_helper.rb
134
+ - spec/support/jekyll_template.rb
135
+ - spec/support/shared_contexts.rb
136
+ - spec/twitter_tag_spec.rb