onebox 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5b5e72349ed5dcca5148875d8e669b3850de0311
4
- data.tar.gz: b1d032a771038719f1c51064fee5942c50f94353
3
+ metadata.gz: 2d45ecfeb440c52d2c6d45aaec72669ad24f170b
4
+ data.tar.gz: 84c5f37f871a0e5a792f179d6d4983bc9c588e52
5
5
  SHA512:
6
- metadata.gz: cd575f2a3a215a63a5c9359e788cb5c3740b8b7b1d53ad59cbdf9db2e120fc63c95e6c96432f11b38f5dbd8d9a7fff41ce722fe51c70e3843bbad2b5362be229
7
- data.tar.gz: 6cb0cc9aef0081ba65b35221261492b44cf6ca161161d50044c1c108d7732a4f9c840867907d8ca1d9ea308430378cfe5f45909a82a31461fd0939ac85399d96
6
+ metadata.gz: 9cc1a432d53eaa94625f3dcea777e8cfb3247770334ced74b91295832bffed4fdc584c33f4c786a954ced16ba1f7010d6799c5cc29232748f29c1a69e6972063
7
+ data.tar.gz: 2ce5bae6fa88b77fa5f9e6a2dd1f0c6395836e51bea065dff2c62b4e79ea1329c8b4e7319a4e436a63427c4d793d8b8c1b034f52f085d9ad33f36892113c1047
data/lib/onebox/engine.rb CHANGED
@@ -13,8 +13,29 @@ module Onebox
13
13
  attr_reader :url
14
14
  attr_reader :cache
15
15
  attr_reader :timeout
16
+
17
+ DEFUALT = {}
18
+ def options
19
+ @options
20
+ end
21
+
22
+ def options=(opt)
23
+ return @options if opt.nil? #make sure options provided
24
+ if opt.instance_of? OpenStruct
25
+ @options = @options.merge(opt.to_h)
26
+ else
27
+ @options = @options.merge(opt)
28
+ end
29
+ @options
30
+ end
31
+
16
32
 
17
33
  def initialize(link, cache = nil, timeout = nil)
34
+
35
+ @options = DEFUALT
36
+ class_name = self.class.name.split("::").last.to_s
37
+ self.options = Onebox.options[class_name] || {} #Set the engine options extracted from global options.
38
+
18
39
  @url = link
19
40
  @cache = cache || Onebox.options.cache
20
41
  @timeout = timeout || Onebox.options.timeout
@@ -41,8 +62,9 @@ module Onebox
41
62
  private
42
63
 
43
64
  def record
44
- result = cache.fetch(url) { data }
45
- cache[url] = result if cache.respond_to?(:key?)
65
+ url_result = url
66
+ result = cache.fetch(url_result) { data }
67
+ cache[url_result] = result if cache.respond_to?(:key?)
46
68
  result
47
69
  end
48
70
 
@@ -101,6 +123,7 @@ require_relative "engine/html"
101
123
  require_relative "engine/json"
102
124
  require_relative "engine/amazon_onebox"
103
125
  require_relative "engine/classic_google_maps_onebox"
126
+ require_relative "engine/github_issue_onebox"
104
127
  require_relative "engine/github_blob_onebox"
105
128
  require_relative "engine/github_commit_onebox"
106
129
  # broken
@@ -4,40 +4,189 @@ module Onebox
4
4
  include Engine
5
5
  include LayoutSupport
6
6
 
7
- MAX_LINES = 20
8
- MAX_CHARS = 5000
9
-
7
+ EXPAND_AFTER = 0b001
8
+ EXPAND_BEFORE = 0b010
9
+ EXPAND_NONE = 0b0
10
+
11
+ DEFAULTS = {
12
+ :EXPAND_ONE_LINER => EXPAND_AFTER|EXPAND_BEFORE, #set how to expand a one liner. user EXPAND_NONE to disable expand
13
+ :LINES_BEFORE => 10,
14
+ :LINES_AFTER => 10,
15
+ :SHOW_LINE_NUMBER => true,
16
+ :MAX_LINES => 20,
17
+ :MAX_CHARS => 5000
18
+ }
19
+
10
20
  matches_regexp(/^https?:\/\/(www\.)?github\.com.*\/blob\//)
11
21
 
22
+
23
+
24
+ def initialize(link, cache = nil, timeout = nil)
25
+ super link, cache , timeout
26
+ #merge engine options from global Onebox.options interface
27
+ # self.options = Onebox.options["GithubBlobOnebox"] # self.class.name.split("::").last.to_s
28
+ # self.options = Onebox.options[self.class.name.split("::").last.to_s] #We can use this a more generic approach. extract the engine class name automatically
29
+
30
+ self.options = DEFAULTS
31
+
32
+ # Define constant after merging options set in Onebox.options
33
+ # We can define constant automatically.
34
+ options.each_pair {|constant_name,value|
35
+ constant_name_u = constant_name.to_s.upcase
36
+ if constant_name_u == constant_name.to_s
37
+ #define a constant if not already defined
38
+ self.class.const_set constant_name_u.to_sym , options[constant_name_u.to_sym] unless self.class.const_defined? constant_name_u.to_sym
39
+ end
40
+ }
41
+ end
42
+
12
43
  private
44
+ @selected_lines_array = nil
45
+ @selected_one_liner = 0
46
+ def calc_range(m,contents_lines_size)
47
+ #author Lidlanca 09/15/2014
48
+ truncated = false
49
+ from = /\d+/.match(m[:from]) #get numeric should only match a positive interger
50
+ to = /\d+/.match(m[:to]) #get numeric should only match a positive interger
51
+ range_provided = !(from.nil? && to.nil?) #true if "from" or "to" provided in URL
52
+ from = from.nil? ? 1 : from[0].to_i #if from not provided default to 1st line
53
+ to = to.nil? ? -1 : to[0].to_i #if to not provided default to undefiend to be handled later in the logic
54
+
55
+ if to === -1 && range_provided #case "from" exists but no valid "to". aka ONE_LINER
56
+ one_liner = true
57
+ to = from
58
+ else
59
+ one_liner = false
60
+ end
61
+
62
+ unless range_provided #case no range provided default to 1..MAX_LINES
63
+ from = 1
64
+ to = MAX_LINES
65
+ truncated = true if contents_lines_size > MAX_LINES
66
+ #we can technically return here
67
+ end
68
+
69
+ from, to = [from,to].sort #enforce valid range. [from < to]
70
+ from = 1 if from > contents_lines_size #if "from" out of TOP bound set to 1st line
71
+ to = contents_lines_size if to > contents_lines_size #if "to" is out of TOP bound set to last line.
72
+
73
+ if one_liner
74
+ @selected_one_liner = from
75
+ if EXPAND_ONE_LINER != EXPAND_NONE
76
+ if (EXPAND_ONE_LINER & EXPAND_BEFORE != 0) # check if EXPAND_BEFORE flag is on
77
+ from = [1, from - LINES_BEFORE].max # make sure expand before does not go out of bound
78
+ end
79
+
80
+ if (EXPAND_ONE_LINER & EXPAND_AFTER != 0) # check if EXPAND_FLAG flag is on
81
+ to = [to + LINES_AFTER, contents_lines_size].min # make sure expand after does not go out of bound
82
+ end
83
+
84
+ from = contents_lines_size if from > contents_lines_size #if "from" is out of the content top bound
85
+ # to = contents_lines_size if to > contents_lines_size #if "to" is out of the content top bound
86
+ else
87
+ #no expand show the one liner solely
88
+ end
89
+ end
90
+
91
+ if to-from > MAX_LINES && !one_liner #if exceed the MAX_LINES limit correct unless range was produced by one_liner which it expand setting will allow exceeding the line limit
92
+ truncated = true
93
+ to = from + MAX_LINES-1
94
+ end
95
+
96
+ {:from => from, #calculated from
97
+ :from_minus_one => from-1, #used for getting currect ol>li numbering with css used in template
98
+ :to => to, #calculated to
99
+ :one_liner => one_liner, #boolean if a one-liner
100
+ :selected_one_liner => @selected_one_liner, #if a one liner is provided we create a reference for it.
101
+ :range_provided => range_provided, #boolean if range provided
102
+ :truncated => truncated}
103
+ end
104
+
105
+ #minimize/compact leading indentation while preserving overall indentation
106
+ def removeLeadingIndentation str
107
+ #author Lidlanca 2014
108
+ min_space=100
109
+ a_lines = str.lines
110
+ a_lines.each {|l|
111
+ l = l.chomp("\n") # remove new line
112
+ m = l.match /^[ ]*/ # find leading spaces 0 or more
113
+ unless m.nil? || l.size==m[0].size || m[0].size==0 # no match | only spaces in line | empty line
114
+ m_str_length = m[0].size
115
+ if m_str_length <= 1 # minimum space is 1 or nothing we can break we found our minimum
116
+ min_space = m_str_length
117
+ break #stop iteration
118
+ end
119
+ if m_str_length < min_space
120
+ min_space = m_str_length
121
+ end
122
+ else
123
+ next # SKIP no match or line is only spaces
124
+ end
125
+ }
126
+ a_lines.each {|l|
127
+ re = Regexp.new "^[ ]{#{min_space}}" #match the minimum spaces of the line
128
+ l.gsub!(re, "")
129
+ }
130
+ a_lines.join
131
+ end
132
+
133
+ def line_number_helper(lines,start,selected)
134
+ #author Lidlanca 09/15/2014
135
+ lines = removeLeadingIndentation(lines.join).lines # A little ineffeicent we could modify removeLeadingIndentation to accept array and return array, but for now it is only working with a string
136
+ hash_builder =[]
137
+ output_builder = []
138
+ lines.map.with_index { |line,i|
139
+ lnum = (i.to_i+start)
140
+ hash_builder.push({:line_number => lnum, :data=> line.gsub("\n",""), :selected=> (selected==lnum)? true: false} )
141
+ output_builder.push "#{lnum}: #{line}"
142
+ }
143
+ {:output=>output_builder.join(), :array=>hash_builder}
144
+ end
145
+
13
146
 
14
147
  def raw
148
+ options_id = self.class.name.split("::").last.to_s #get class name without module namespace
149
+
15
150
  return @raw if @raw
16
151
  m = @url.match(/github\.com\/(?<user>[^\/]+)\/(?<repo>[^\/]+)\/blob\/(?<sha1>[^\/]+)\/(?<file>[^#]+)(#(L(?<from>[^-]*)(-L(?<to>.*))?))?/mi)
152
+
17
153
  if m
18
- from = (m[:from] || -1).to_i
19
- to = (m[:to] || -1).to_i
154
+ from = /\d+/.match(m[:from]) #get numeric should only match a positive interger
155
+ to = /\d+/.match(m[:to]) #get numeric should only match a positive interger
156
+
20
157
  @file = m[:file]
21
158
  contents = open("https://raw.github.com/#{m[:user]}/#{m[:repo]}/#{m[:sha1]}/#{m[:file]}", read_timeout: timeout).read
22
- if from > 0
23
- if to < 0
24
- from = from - 10
25
- to = from + 20
26
- end
27
- if to > from
28
- contents = contents.split("\n")[from..to].join("\n")
159
+
160
+ contents_lines = contents.lines #get contents lines
161
+ contents_lines_size = contents_lines.size #get number of lines
162
+
163
+ cr = calc_range(m,contents_lines_size) #calculate the range of lines for output
164
+ selected_one_liner = cr[:selected_one_liner] #if url is a one-liner calc_range will return it
165
+ # puts "SELECTED LINE" + cr[:selected_one_liner].to_s
166
+ from = cr[:from]
167
+ to = cr[:to]
168
+ @truncated = cr[:truncated]
169
+ range_provided = cr[:range_provided]
170
+ one_liner = cr[:one_liner]
171
+ @cr_results = cr
172
+ if range_provided #if a range provided (single line or more)
173
+ if SHOW_LINE_NUMBER
174
+ lines_result = line_number_helper(contents_lines[from-1..to-1], from, selected_one_liner) #print code with prefix line numbers in case range provided
175
+ contents = lines_result[:output]
176
+ @selected_lines_array = lines_result[:array]
177
+ else
178
+ contents = contents_lines[from-1..to-1].join()
29
179
  end
180
+
181
+ else
182
+ contents = contents_lines[from-1..to-1].join()
30
183
  end
31
- if contents.length > MAX_CHARS
184
+
185
+ if contents.length > MAX_CHARS #truncate content chars to limits
32
186
  contents = contents[0..MAX_CHARS]
33
187
  @truncated = true
34
188
  end
35
189
 
36
- split = contents.split("\n")
37
- if split.length > MAX_LINES
38
- contents = split[0..MAX_LINES].join("\n")
39
- @truncated = true
40
- end
41
190
  @raw = contents
42
191
  end
43
192
  end
@@ -46,8 +195,13 @@ module Onebox
46
195
  @data ||= {title: link.sub(/^https?\:\/\/github\.com\//, ''),
47
196
  link: link,
48
197
  content: raw,
198
+ lines: @selected_lines_array ,
199
+ has_lines: !@selected_lines_array.nil?,
200
+ selected_one_liner: @selected_one_liner,
201
+ cr_results:@cr_results,
49
202
  truncated: @truncated}
50
203
  end
204
+
51
205
  end
52
206
  end
53
207
  end
@@ -0,0 +1,49 @@
1
+ module Onebox
2
+ module Engine
3
+ class GithubIssueOnebox
4
+ #Author Lidlanca 2014
5
+ include Engine
6
+ include LayoutSupport
7
+ include JSON
8
+ matches_regexp Regexp.new("^http(?:s)?:\/\/(?:www\.)?(?:(?:\w)+\.)?github\.com\/(?<org>.+)\/(?<repo>.+)\/issues\/([[:digit:]]+)")
9
+
10
+ def url
11
+ m = match
12
+ "https://api.github.com/repos/#{m["org"]}/#{m["repo"]}/issues/#{m["item_id"]}"
13
+ end
14
+
15
+ private
16
+
17
+ def match
18
+ @match ||= @url.match(/^http(?:s)?:\/\/(?:www\.)?(?:(?:\w)+\.)?github\.com\/(?<org>.+)\/(?<repo>.+)\/(?<type>issues)\/(?<item_id>[\d]+)/)
19
+ end
20
+
21
+ def data
22
+
23
+ @raw ||= ::MultiJson.load(open(url,"Accept"=>"application/vnd.github.v3.text+json",:read_timeout=>timeout )) #custom Accept header so we can get body as text.
24
+ body_text= @raw["body_text"]
25
+
26
+
27
+ content_words = body_text.gsub("\n\n","\n").gsub("\n","<br>").split(" ") #one pass of removing double newline, then we change \n to <br> and later on we revert it back to \n this is a workaround to avoid losing newlines after we join it back.
28
+ max_words = 20
29
+ short_content = content_words[0..max_words].join(" ")
30
+ short_content << "..." if content_words.length > max_words
31
+
32
+ status_color = {"open"=>"#6cc644","closed"=>"#bd2c00","merged"=>"#6e5494"}
33
+ result = { link: @url,
34
+ title: "Issue: " + @raw["title"],
35
+ content:short_content.gsub("<br>","\n"),
36
+ labels: @raw["labels"],
37
+ user: @raw['user'],
38
+ status: @raw['state'],
39
+ status_color: status_color[@raw['state']] || "#cfcfcf",
40
+ created_at: @raw['created_at'].split("T")[0], #get only date for now
41
+ closed_at: (@raw['closed_at'].nil? ? "" : @raw['closed_at'].split("T")[0] ) ,
42
+ closed_by: @raw['closed_by'],
43
+ # avatar: "https://www.gravatar.com/avatar/#{@raw['user']['gravatar_id']}?s=128",
44
+ avatar:"https://avatars1.githubusercontent.com/u/#{@raw['user']['id']}?v=2&s=96"
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -13,12 +13,57 @@ module Onebox
13
13
 
14
14
  private
15
15
 
16
+ #Make an api JSON request, will attempt to authenticate if provided in the engine options
17
+ # Author: Lidlanca
18
+ #: self.options[:github_auth_method] = :basic | :oauth | nil
19
+ # :oauth is the recommend way for authentication. when generating token you can control privileges, and you do not expose your password
20
+ # :basic require username and password provided in options[:github_auth_user , :github_auth_pass]
21
+ # nil or false will make a request without any authentication. request rate limit are lower.
22
+
23
+ def api_json_request url
24
+ box_options = self.options
25
+ case box_options[:github_auth_method]
26
+ when :basic
27
+ auth = [box_options[:github_auth_user] , box_options[:github_auth_pass]] # user name and password
28
+ when :oauth
29
+ auth = [box_options[:github_auth_token] , "x-oauth-basic"] #oauth does not need password with token
30
+ else
31
+ #request without auth
32
+ return ::MultiJson.load(open(url,"Accept"=>"application/vnd.github.v3.text+json",read_timeout: timeout))
33
+
34
+ end
35
+ #Request with auth
36
+ return ::MultiJson.load(open(url,"Accept"=>"application/vnd.github.v3.text+json",http_basic_authentication:auth, read_timeout: timeout))
37
+ end
38
+
39
+ def raw
40
+ @raw ||= api_json_request url
41
+ end
16
42
  def match
17
43
  @match ||= @url.match(%r{github\.com/(?<owner>[^/]+)/(?<repository>[^/]+)/pull/(?<number>[^/]+)})
18
44
  end
19
45
 
20
46
  def data
47
+ box_options = self.options
21
48
  result = raw.clone
49
+
50
+ pull_status = "Pull Status:" << {:closed=>"closed",:open=>"open"}[raw["state"].to_sym] << (raw["state"] == "closed" ? (raw["merged"] ? " & accepted" : " & declined") : "") #closed , open
51
+ result['pull_status_str'] = pull_status
52
+ if box_options[:get_build_status]
53
+ url2 = raw["statuses_url"]
54
+ raw2 = api_json_request url2 #2nd api request to get build status
55
+
56
+ result['pull_status'] = raw["state"]
57
+ result['pull_status_str'] = pull_status
58
+ result['pull_status_str_open'] = raw["state"]=="open"
59
+ result['pull_status_closed_accepted'] = raw["state"]=="closed" && raw["merged"]
60
+ result['pull_status_closed_declined'] = raw["state"]=="closed" && !raw["merged"]
61
+ unless raw2.empty?
62
+ result['build_status'] = "Build status: " + raw2[0]["state"].to_s.capitalize + " | " + raw2[0]["description"].to_s
63
+ end
64
+ end
65
+
66
+
22
67
  result['link'] = link
23
68
  result['created_at'] = Time.parse(result['created_at']).strftime("%I:%M%p - %d %b %y")
24
69
  result
@@ -26,3 +71,5 @@ module Onebox
26
71
  end
27
72
  end
28
73
  end
74
+
75
+
@@ -1,3 +1,3 @@
1
1
  module Onebox
2
- VERSION = "1.5.0"
2
+ VERSION = "1.5.1"
3
3
  end
@@ -1,7 +1,37 @@
1
1
  <h4><a href="{{link}}" target="_blank">{{title}}</a></h4>
2
+ {{^has_lines}}
2
3
  <pre><code class='{{lang}}'>{{content}}</code></pre>
4
+ {{/has_lines}}
5
+ {{#has_lines}}
6
+ {{! This is a template comment | Sample rules for this box
7
+ <style>
8
+ pre.onebox code ol{
9
+ margin-left:0px;
10
+ }
11
+ pre.onebox code ol .lines{
12
+ margin-left:30px;
13
+ }
14
+ pre.onebox code ol.lines li {
15
+ list-style-type: decimal;
16
+ margin-left:45px;
17
+ }
18
+ pre.onebox code li{
19
+ list-style-type: none;
20
+ background-color:#fff;
21
+ border-bottom:1px solid #F0F0F0;
22
+ padding-left:5px;
23
+
24
+ }
25
+ pre.onebox code li.selected{
26
+ background-color:#cfc
27
+ }
28
+ </style>
29
+ }}
30
+ <pre class='onebox' ><code class='{{lang}}'><ol class='start lines' start="{{cr_results.from}}" style='counter-reset: li-counter {{cr_results.from_minus_one}} ;'>
31
+ {{#lines}}<li{{#selected}} class="selected"{{/selected}}>{{data}}</li>{{/lines}}
32
+ </ol></code></pre>
33
+ {{/has_lines}}
3
34
 
4
35
  {{#truncated}}
5
36
  This file has been truncated. <a href="{{link}}" target="_blank">show original</a>
6
- {{/truncated}}
7
-
37
+ {{/truncated}}
@@ -0,0 +1,29 @@
1
+ <a href="{{user.html_url}}">
2
+ <img src="{{avatar}}" class="thumbnail onebox-avatar">
3
+ </a>
4
+
5
+ <h4><a href="{{link}}" target="_blank">{{title}}</a></h4>
6
+
7
+ <div class="date" style="margin-top:10px;">
8
+ <div class="status">
9
+ <span style="background-color: {{status_color}};padding: 2px;border-radius: 4px;color: #fff;">{{status}}</span>
10
+ </div>
11
+ <div class="user" style="margin-top:10px;">
12
+ opened by <a href="{{user.html_url}}" target="_blank">{{user.login}}</a>
13
+ on <a href="{{link}}" target="_blank">{{created_at}}</a>
14
+ </div>
15
+ <div class="user">
16
+ {{#closed_by}}
17
+ closed by <a href="{{html_url}}" target="_blank">{{login}}</a>
18
+ on <a href="{{link}}" target="_blank">{{closed_at}}</a>
19
+ {{/closed_by}}
20
+ </div>
21
+ </div>
22
+
23
+ <pre class='content' style="white-space: pre-wrap;">{{content}}</pre>
24
+
25
+ <div class='lables'>
26
+ {{#labels}}
27
+ <span style="display:inline-block;margin-top:2px;background-color: #B8B8B8;padding: 2px;border-radius: 4px;color: #fff;margin-left: 3px;">{{name}}</span>
28
+ {{/labels}}
29
+ </div>
@@ -8,6 +8,27 @@
8
8
  <a href="{{html_url}}" target="_blank">{{title}}</a>
9
9
  </h4>
10
10
 
11
+
12
+
13
+ <div class='github-commit-status' style='margin-bottom:3px;'>
14
+ {{#pull_status_str_open}}
15
+ <span style='background:#6cc644;color:#fff;display:inline-block;border-radius:3px;padding:2px;'>
16
+ {{/pull_status_str_open}}
17
+ {{#pull_status_closed_accepted}}
18
+ <span style='background:#6e5494;color:#fff;display:inline-block;border-radius:3px;padding:2px;'>
19
+ {{/pull_status_closed_accepted}}
20
+ {{#pull_status_closed_declined}}
21
+ <span style='background:#bd2c00;color:#fff;display:inline-block;border-radius:3px;padding:2px;'>
22
+ {{/pull_status_closed_declined}}
23
+
24
+ {{pull_status_str}} </span>
25
+ </br>
26
+
27
+ {{#build_status}}
28
+ <span style='padding: 2px;background: #cfcfcf;'>{{build_status}}</span>
29
+ {{/build_status}}
30
+ </div>
31
+
11
32
  <div class="date">
12
33
  by <a href="{{user.html_url}}" target="_blank">{{user.login}}</a>
13
34
  on <a href="{{html_url}}" target="_blank">{{created_at}}</a>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onebox
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joanna Zeta
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-09-09 00:00:00.000000000 Z
13
+ date: 2014-09-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: multi_json
@@ -249,6 +249,7 @@ files:
249
249
  - lib/onebox/engine/github_blob_onebox.rb
250
250
  - lib/onebox/engine/github_commit_onebox.rb
251
251
  - lib/onebox/engine/github_gist_onebox.rb
252
+ - lib/onebox/engine/github_issue_onebox.rb
252
253
  - lib/onebox/engine/github_pullrequest_onebox.rb
253
254
  - lib/onebox/engine/google_play_app_onebox.rb
254
255
  - lib/onebox/engine/html.rb
@@ -315,6 +316,7 @@ files:
315
316
  - templates/amazon.mustache
316
317
  - templates/githubblob.mustache
317
318
  - templates/githubcommit.mustache
319
+ - templates/githubissue.mustache
318
320
  - templates/githubpullrequest.mustache
319
321
  - templates/googleplayapp.mustache
320
322
  - templates/pubmed.mustache