Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Octokit to fetch Gist content when passed a GItHub token #28

Merged
merged 13 commits into from
Dec 1, 2015
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ You may optionally specify a `filename` after the `gist_id`:

This will produce the correct URL to show just the specified file in your post rather than the entire Gist.

**Pro-tip**: If you provide a personal access token with Gist scope, as the environmental variable `JEKYLL_GITHUB_TOKEN`, Jekyll Gist will use the Gist API to speed up site generation.

## Disabling `noscript` support

By default, Jekyll Gist will make an HTTP call per Gist to retrieve the raw content of the Gist. This information is used to propagate `noscript` tags for search engines and browsers without Javascript support. If you'd like to disable this feature, for example, to speed up builds locally, simply add the following to your site's `_config.yml`:
Expand Down
1 change: 1 addition & 0 deletions jekyll-gist.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_dependency "octokit", "~> 4.2"
spec.add_development_dependency "bundler", "~> 1.6"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"
Expand Down
54 changes: 42 additions & 12 deletions lib/jekyll-gist/gist_tag.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'cgi'
require 'net/http'
require 'octokit'

Net::OpenTimeout = Class.new(RuntimeError) unless Net.const_defined?(:OpenTimeout)
Net::ReadTimeout = Class.new(RuntimeError) unless Net.const_defined?(:ReadTimeout)
Expand Down Expand Up @@ -55,7 +56,13 @@ def gist_noscript_tag(gist_id, filename = nil)
code = fetch_raw_code(gist_id, filename)
if !code.nil?
code = code.force_encoding(@encoding)
"<noscript><pre>#{CGI.escapeHTML(code)}</pre></noscript>"
code = CGI.escapeHTML(code)

# CGI.escapeHTML behavior differs in Ruby < 2.0
# See https://github.com/jekyll/jekyll-gist/pull/28
code = code.gsub("'", "&#39;") if RUBY_VERSION < "2.0"

"<noscript><pre>#{code}</pre></noscript>"
else
Jekyll.logger.warn "Warning:", "The <noscript> tag for your gist #{gist_id} could not"
Jekyll.logger.warn "", "be generated. This will affect users who do not have"
Expand All @@ -64,20 +71,43 @@ def gist_noscript_tag(gist_id, filename = nil)
end

def fetch_raw_code(gist_id, filename = nil)
return code_from_api(gist_id, filename) if ENV["JEKYLL_GITHUB_TOKEN"]

url = "https://gist.githubusercontent.com/#{gist_id}/raw"
url = "#{url}/#{filename}" unless filename.empty?
begin
uri = URI(url)
Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == 'https',
read_timeout: 3, open_timeout: 3) do |http|
request = Net::HTTP::Get.new uri.to_s
response = http.request(request)
response.body
end
rescue SocketError, Net::HTTPError, Net::OpenTimeout, Net::ReadTimeout, TimeoutError
nil
uri = URI(url)
Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == 'https',
read_timeout: 3, open_timeout: 3) do |http|
request = Net::HTTP::Get.new uri.to_s
response = http.request(request)
response.body
end
rescue SocketError, Net::HTTPError, Net::OpenTimeout, Net::ReadTimeout, TimeoutError
nil
end

private

def code_from_api(gist_id, filename = nil)
gist = client.gist gist_id

file = if filename.to_s.empty?
# No file specified, return the value of the first key/value pair
gist.files.first[1]
else
# .files is a hash of :"filename.extension" => data pairs
# Rather than using to_sym on arbitrary user input,
# Find our file by calling to_s on the keys
match = gist.files.find { |name, data| name.to_s == filename }
match[1] if match
end

file[:content] if file
end

def client
@client ||= Octokit::Client.new :access_token => ENV["JEKYLL_GITHUB_TOKEN"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, I'm awfully silly. Really sorry, Ben, I misspoke. When I asked about caching, my reasoning was that for each {% gist %}, a new GistTag instance is created and thus requires its own client. The Client won't change between various gist calls, so storing it so it's on the class rather than instance (or similar, store it anywhere you feel is appropriate) gives us a little speed boost and relieves some pressure on the garbage collector.

Does that make sense? Sorry I didn't communicate all that. Still figuring out the right balance between being concise (saves time) and communicating 100% of what I was thinking.

So this code would become:

def self.client
  @client ||= Octokit::Client.new #...
end

and would be accessed via GistTag.client (i.e. at the class level).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes total sense. Didn't think through that. Does it need to be @@client?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be @@client?

No, in the case of @client, it's an Instance variable on the Singleton. @@client is a class variable on GistTag, @client is an instance variable on the Singleton instance of GistTag. It's equivalent to:

class GistTag
  @client = Octokit::Client.new #...
end

Ruby is hella weird but there are fun nuances like this that keep things interesting. 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. 📚. Implemented via af85461.

end
end
end
Expand Down
111 changes: 111 additions & 0 deletions spec/fixtures/multiple-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"url": "https://api.github.com/gists/aa5a315d61ae9438b18d",
"forks_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/forks",
"commits_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/commits",
"id": "aa5a315d61ae9438b18d",
"description": "description of gist",
"public": true,
"owner": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"user": null,
"files": {
"ring.erl": {
"size": 932,
"raw_url": "https://gist.githubusercontent.com/raw/365370/8c4d2d43d178df44f4c03a7f2ac0ff512853564e/ring.erl",
"type": "text/plain",
"language": "Erlang",
"truncated": false,
"content": "contents of gist"
},
"hello-world.rb": {
"size": 932,
"raw_url": "https://gist.githubusercontent.com/raw/365370/8c4d2d43d178df44f4c03a7f2ac0ff512853564e/ring.erl",
"type": "text/plain",
"language": "Ruby",
"truncated": false,
"content": "puts 'hello world'"
}
},
"comments": 0,
"comments_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/comments/",
"html_url": "https://gist.github.com/aa5a315d61ae9438b18d",
"git_pull_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
"git_push_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
"created_at": "2010-04-14T02:15:15Z",
"updated_at": "2011-06-20T11:34:15Z",
"forks": [
{
"user": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"url": "https://api.github.com/gists/dee9c42e4998ce2ea439",
"id": "dee9c42e4998ce2ea439",
"created_at": "2011-04-14T16:00:49Z",
"updated_at": "2011-04-14T16:00:49Z"
}
],
"history": [
{
"url": "https://api.github.com/gists/aa5a315d61ae9438b18d/57a7f021a713b1c5a6a199b54cc514735d2d462f",
"version": "57a7f021a713b1c5a6a199b54cc514735d2d462f",
"user": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"change_status": {
"deletions": 0,
"additions": 180,
"total": 180
},
"committed_at": "2010-04-14T02:15:15Z"
}
]
}
103 changes: 103 additions & 0 deletions spec/fixtures/single-file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"url": "https://api.github.com/gists/aa5a315d61ae9438b18d",
"forks_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/forks",
"commits_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/commits",
"id": "aa5a315d61ae9438b18d",
"description": "description of gist",
"public": true,
"owner": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"user": null,
"files": {
"ring.erl": {
"size": 932,
"raw_url": "https://gist.githubusercontent.com/raw/365370/8c4d2d43d178df44f4c03a7f2ac0ff512853564e/ring.erl",
"type": "text/plain",
"language": "Erlang",
"truncated": false,
"content": "contents of gist"
}
},
"comments": 0,
"comments_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/comments/",
"html_url": "https://gist.github.com/aa5a315d61ae9438b18d",
"git_pull_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
"git_push_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
"created_at": "2010-04-14T02:15:15Z",
"updated_at": "2011-06-20T11:34:15Z",
"forks": [
{
"user": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"url": "https://api.github.com/gists/dee9c42e4998ce2ea439",
"id": "dee9c42e4998ce2ea439",
"created_at": "2011-04-14T16:00:49Z",
"updated_at": "2011-04-14T16:00:49Z"
}
],
"history": [
{
"url": "https://api.github.com/gists/aa5a315d61ae9438b18d/57a7f021a713b1c5a6a199b54cc514735d2d462f",
"version": "57a7f021a713b1c5a6a199b54cc514735d2d462f",
"user": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"change_status": {
"deletions": 0,
"additions": 180,
"total": 180
},
"committed_at": "2010-04-14T02:15:15Z"
}
]
}
32 changes: 32 additions & 0 deletions spec/gist_tag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
doc.output = Jekyll::Renderer.new(doc.site, doc).run
end

before(:each) { ENV["JEKYLL_GITHUB_TOKEN"] = nil }

context "valid gist" do
context "with user prefix" do
Expand Down Expand Up @@ -118,6 +119,37 @@

end

context "with token" do
before { ENV["JEKYLL_GITHUB_TOKEN"] = "1234" }
before {
stub_request(:get, "https://api.github.com/gists/1342013").
to_return(:status => 200, :body => fixture("single-file"), :headers => {"Content-Type" => "application/json"})
}
let(:gist_id) { "1342013" }
let(:gist) { "page.gist_id" }
let(:output) do
doc.data['gist_id'] = gist_id
doc.content = content
doc.output = Jekyll::Renderer.new(doc.site, doc).run
end

it "produces the noscript tag" do
expect(output).to match(/<noscript><pre>contents of gist<\/pre><\/noscript>/)
end

context "with a filename" do
before {
stub_request(:get, "https://api.github.com/gists/1342013").
to_return(:status => 200, :body => fixture("multiple-files"), :headers => {"Content-Type" => "application/json"})
}
let(:content) { "{% gist 1342013 hello-world.rb %}" }

it "produces the noscript tag" do
expect(output).to match(/<noscript><pre>puts &#39;hello world&#39;<\/pre><\/noscript>/)
end
end
end

context "with noscript disabled" do
let(:doc) { doc_with_content(content, { "gist" => { "noscript" => false } }) }
let(:output) do
Expand Down
6 changes: 5 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
require File.expand_path("../lib/jekyll-gist.rb", TEST_DIR)

Jekyll.logger.log_level = :error
STDERR.reopen(test(?e, '/dev/null') ? '/dev/null' : 'NUL:')

RSpec.configure do |config|
config.run_all_when_everything_filtered = true
Expand Down Expand Up @@ -42,4 +41,9 @@ def site(opts = {})
}))
Jekyll::Site.new(conf)
end

def fixture(name)
path = File.expand_path "./fixtures/#{name}.json", File.dirname(__FILE__)
File.open(path).read
end
end