jekyll-fdroid 0.1.1 → 0.2.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 +4 -4
- data/lib/fdroid/App.rb +248 -0
- data/lib/fdroid/IndexV1.rb +75 -0
- data/lib/fdroid/Package.rb +75 -0
- data/lib/fdroid/{FDroidIndex.rb → Permission.rb} +14 -8
- data/lib/fdroid/Repo.rb +45 -0
- data/lib/jekyll-fdroid.rb +2 -1
- data/lib/jekyll/FDroidPackageDetailGenerator.rb +3 -3
- data/lib/jekyll/FDroidPackageDetailPage.rb +25 -108
- data/lib/jekyll/FDroidRepoInfoTag.rb +39 -0
- data/lib/jekyll/FDroidSearchAutocompleteTag.rb +60 -13
- data/lib/lunr/LunrIndexer.rb +86 -93
- metadata +48 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5223ec6f44fa3c3131928d083cee93e06c28045b
|
4
|
+
data.tar.gz: 1acae1bbdda2af1529df4d8fc955e27d6e428514
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52dd3848f495b9cf4a2c973faf5f3324e90bdacacbd21545e63a93a455771c4d1522739aba09968727e8e48f4d7bc496320142138ec9425cc7ce420fcad98bf6
|
7
|
+
data.tar.gz: c6227570b89a1c4edad316e55678bca5bd1278f28511d3474f41f58d6eda57bcd625c760626bebde750f80a960b6dd8fb828a08c87ca82ca1bc62e10d6469e78
|
data/lib/fdroid/App.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
# F-Droid's Jekyll Plugin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2017 Nico Alt
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require_relative './Package'
|
19
|
+
|
20
|
+
module FDroid
|
21
|
+
class App
|
22
|
+
def initialize(app, packages, locale)
|
23
|
+
# Sort packages in reverse-chronological order
|
24
|
+
@packages = packages.map { |p| Package.new(p) }
|
25
|
+
@app = app
|
26
|
+
@locale = locale
|
27
|
+
@available_locales = app.key?('localized') ? App.available_locales(locale, app['localized']) : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def package_name
|
31
|
+
field 'packageName'
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
package_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def icon
|
39
|
+
localized = App.localized_graphic_path(@available_locales, @app['localized'], 'icon')
|
40
|
+
if localized
|
41
|
+
"#{package_name}/#{localized}"
|
42
|
+
else
|
43
|
+
"icons-640/#{field('icon')}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
App.localized(@available_locales, @app['localized'], 'name') || field('name')
|
49
|
+
end
|
50
|
+
|
51
|
+
def summary
|
52
|
+
App.localized(@available_locales, @app['localized'], 'summary') || field('summary')
|
53
|
+
end
|
54
|
+
|
55
|
+
def description
|
56
|
+
desc = App.localized(@available_locales, @app['localized'], 'description') || field('description')
|
57
|
+
|
58
|
+
if desc != nil
|
59
|
+
desc = App.process_app_description(desc)
|
60
|
+
end
|
61
|
+
|
62
|
+
return desc
|
63
|
+
end
|
64
|
+
|
65
|
+
def suggested_version_code
|
66
|
+
code = field('suggestedVersionCode')
|
67
|
+
if code != nil
|
68
|
+
code = Integer(code)
|
69
|
+
end
|
70
|
+
return code
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Generates a hash of dumb strings to be used in templates.
|
75
|
+
# If a specific value is not present, then it will have a nil value.
|
76
|
+
# If a value can be localized, then it will choose the most appropriate
|
77
|
+
# translation based on @available_locales and @locale.
|
78
|
+
# The 'packages' key is an array of Package.to_data hashes.
|
79
|
+
# @return [Hash]
|
80
|
+
def to_data
|
81
|
+
{
|
82
|
+
# These fields are taken as is from the metadata. If not present, they are
|
83
|
+
'package_name' => package_name,
|
84
|
+
'author_email' => field('authorEmail'),
|
85
|
+
'author_name' => field('authorName'),
|
86
|
+
'author_website' => field('authorWebSite'),
|
87
|
+
'bitcoin' => field('bitcoin'),
|
88
|
+
'donate' => field('donate'),
|
89
|
+
'flattr' => field('flattr'),
|
90
|
+
'categories' => field('categories'),
|
91
|
+
'anti_features' => field('anti_features'),
|
92
|
+
'suggested_version_code' => suggested_version_code,
|
93
|
+
'suggested_version_name' => @packages.detect { |p| p.version_code == suggested_version_code }&.version_name,
|
94
|
+
'issue_tracker' => field('issueTracker'),
|
95
|
+
'changelog' => field('changelog'),
|
96
|
+
'license' => field('license'),
|
97
|
+
'source_code' => field('sourceCode'),
|
98
|
+
'website' => field('webSite'),
|
99
|
+
'added' => field('added'),
|
100
|
+
'last_updated' => field('lastUpdated'),
|
101
|
+
'whats_new' => App.process_app_description(App.localized(@available_locales, @app['localized'], 'whatsNew')),
|
102
|
+
|
103
|
+
'icon' => icon,
|
104
|
+
'title' => name,
|
105
|
+
'summary' => summary,
|
106
|
+
|
107
|
+
'description' => description,
|
108
|
+
'feature_graphic' => App.localized_graphic_path(@available_locales, @app['localized'], 'featureGraphic'),
|
109
|
+
'phone_screenshots' => App.localized_graphic_list_paths(@available_locales, @app['localized'], 'phoneScreenshots'),
|
110
|
+
'seven_inch_screenshots' => App.localized_graphic_list_paths(@available_locales, @app['localized'], 'sevenInchScreenshots'),
|
111
|
+
|
112
|
+
'packages' => @packages.sort.reverse.map { |p| p.to_data },
|
113
|
+
|
114
|
+
'beautiful_url' => "/packages/#{package_name}"
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Any transformations which are required to turn the "description" into something which is
|
119
|
+
# displayable via HTML is done here (e.g. replacing "fdroid.app:" schemes, formatting new lines,
|
120
|
+
# etc.
|
121
|
+
def self.process_app_description(string)
|
122
|
+
if string == nil
|
123
|
+
return nil
|
124
|
+
end
|
125
|
+
|
126
|
+
string = self.replace_fdroid_app_links(string)
|
127
|
+
self.format_description_to_html(string)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Finds all "fdroid.app:" schemes in a particular string, and replaces with "/packages/".
|
131
|
+
# @param [string] string
|
132
|
+
# @return [string]
|
133
|
+
def self.replace_fdroid_app_links(string)
|
134
|
+
string.gsub /fdroid\.app:([\w._]*)/, '/packages/\1'
|
135
|
+
end
|
136
|
+
|
137
|
+
# Ensure double newlines "\n\n" are converted to "<br />" tags.
|
138
|
+
def self.format_description_to_html(string)
|
139
|
+
string
|
140
|
+
.gsub("\n\n", '<br />')
|
141
|
+
.gsub(/\r?\n/, ' ')
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [string] available_locales
|
145
|
+
# @param [string] localized
|
146
|
+
# @param [string] field
|
147
|
+
# @return [string]
|
148
|
+
def self.localized(available_locales, localized, field)
|
149
|
+
return nil unless available_locales != nil
|
150
|
+
|
151
|
+
available_locales.each do |l|
|
152
|
+
if localized[l].key?(field)
|
153
|
+
return localized[l][field]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
|
160
|
+
# Prefixes the result with "chosen_locale/" before returning.
|
161
|
+
# @see localized
|
162
|
+
def self.localized_graphic_path(available_locales, localized, field)
|
163
|
+
return nil unless available_locales != nil
|
164
|
+
|
165
|
+
available_locales.each do |l|
|
166
|
+
if localized[l].key?(field)
|
167
|
+
return "#{l}/#{localized[l][field]}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Similar to localized_graphic_path, but prefixes each item in the resulting array
|
175
|
+
# with "chosen_locale/field/".
|
176
|
+
# @see localized
|
177
|
+
# @see localized_graphic_path
|
178
|
+
def self.localized_graphic_list_paths(available_locales, localized, field)
|
179
|
+
return nil unless available_locales != nil
|
180
|
+
|
181
|
+
available_locales.each do |l|
|
182
|
+
if localized[l].key?(field)
|
183
|
+
return localized[l][field].map { |val| "#{l}/#{field}/#{val}" }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
|
190
|
+
# Given the desired_locale, searches through the list of localized_data entries
|
191
|
+
# and finds those with keys which match either:
|
192
|
+
# * The desired locale exactly
|
193
|
+
# * The same language as the desired locale (but different region)
|
194
|
+
# * Any English language (so if the desired language is not there it will suffice)
|
195
|
+
#
|
196
|
+
# These will be sorted in order of preference:
|
197
|
+
# * Exact matches (language and region)
|
198
|
+
# * Language portion matches but region is absent/doesn't match.
|
199
|
+
# * en-US
|
200
|
+
# * en
|
201
|
+
# * en-*
|
202
|
+
#
|
203
|
+
# It is intentionally liberal in searching for either "_" or "-" to separate language
|
204
|
+
# and region, because they both mean (in different context) to split langugae on the
|
205
|
+
# left, and region on the right, and it is cheap to do so.
|
206
|
+
#
|
207
|
+
# @param [string] desired_locale
|
208
|
+
# @param [Hash] localized_data
|
209
|
+
# @return [Array]
|
210
|
+
def self.available_locales(desired_locale, localized_data)
|
211
|
+
parts = desired_locale.split(/[_-]/)
|
212
|
+
desired_lang = parts[0]
|
213
|
+
|
214
|
+
locales = localized_data.keys.select do |available_locale|
|
215
|
+
parts = available_locale.split(/[_-]/)
|
216
|
+
available_lang = parts[0]
|
217
|
+
available_lang == desired_lang || available_lang == 'en'
|
218
|
+
end
|
219
|
+
|
220
|
+
measure_locale_goodness = lambda do |locale|
|
221
|
+
parts = locale.split(/[_-]/)
|
222
|
+
lang = parts[0]
|
223
|
+
region = parts.length > 1 ? parts[1] : nil
|
224
|
+
if locale == desired_locale
|
225
|
+
return 1
|
226
|
+
elsif lang == desired_lang
|
227
|
+
return 2
|
228
|
+
elsif locale == 'en-US'
|
229
|
+
return 3
|
230
|
+
elsif lang == 'en' && region.nil?
|
231
|
+
return 4
|
232
|
+
elsif lang == 'en'
|
233
|
+
return 5
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
locales.sort do |a, b|
|
238
|
+
measure_locale_goodness.call(a) <=> measure_locale_goodness.call(b)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def field(name)
|
245
|
+
@app.key?(name) ? @app[name] : nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# F-Droid's Jekyll Plugin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2017 Nico Alt
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'tmpdir'
|
19
|
+
require 'open-uri'
|
20
|
+
require 'net/http'
|
21
|
+
require 'json'
|
22
|
+
require 'zip'
|
23
|
+
require_relative './App'
|
24
|
+
require_relative './Repo'
|
25
|
+
|
26
|
+
module FDroid
|
27
|
+
class IndexV1
|
28
|
+
attr_reader :apps, :repo
|
29
|
+
|
30
|
+
@@downloaded_repos = {}
|
31
|
+
|
32
|
+
# Download and parse an index, returning a new instance of IndexV1.
|
33
|
+
# @param [string] repo
|
34
|
+
# @param [string] locale
|
35
|
+
# @return [FDroid::IndexV1]
|
36
|
+
def self.download(repo, locale)
|
37
|
+
repo = URI.parse "#{repo}/index-v1.jar"
|
38
|
+
index = download_index repo
|
39
|
+
IndexV1.new(JSON.parse(index), locale)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Make a network request, download the index-v1.jar file from the repo, unzip and get the contents
|
43
|
+
# of the index-v1.json file.
|
44
|
+
# @param [string] repo
|
45
|
+
# @return [Hash]
|
46
|
+
def self.download_index(repo)
|
47
|
+
if @@downloaded_repos.has_key? repo
|
48
|
+
return @@downloaded_repos[repo]
|
49
|
+
end
|
50
|
+
|
51
|
+
Dir.mktmpdir do |dir|
|
52
|
+
jar = File.join dir, 'index-v1.jar'
|
53
|
+
open(jar, 'wb') do |file|
|
54
|
+
file.write(Net::HTTP.get(repo))
|
55
|
+
end
|
56
|
+
|
57
|
+
Zip::File.open(jar) do |zip_file|
|
58
|
+
entry = zip_file.glob('index-v1.json').first
|
59
|
+
@@downloaded_repos[repo] = entry.get_input_stream.read
|
60
|
+
next @@downloaded_repos[repo]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(index, locale)
|
66
|
+
@apps = index['apps'].map do |app_json|
|
67
|
+
packages_json = index['packages'][app_json['packageName']]
|
68
|
+
App.new(app_json, packages_json, locale)
|
69
|
+
end
|
70
|
+
|
71
|
+
@repo = Repo.new(index['repo'])
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# F-Droid's Jekyll Plugin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2017 Nico Alt
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require_relative './Permission'
|
19
|
+
|
20
|
+
module FDroid
|
21
|
+
class Package
|
22
|
+
def initialize(package)
|
23
|
+
@package = package
|
24
|
+
end
|
25
|
+
|
26
|
+
def <=> (other)
|
27
|
+
self.version_code <=> other.version_code
|
28
|
+
end
|
29
|
+
|
30
|
+
def version_code
|
31
|
+
@package['versionCode']
|
32
|
+
end
|
33
|
+
|
34
|
+
def version_name
|
35
|
+
@package['versionName']
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_data
|
39
|
+
added = nil
|
40
|
+
if @package['added'] != nil then
|
41
|
+
added = Date.strptime("#{@package['added'] / 1000}", '%s')
|
42
|
+
end
|
43
|
+
|
44
|
+
{
|
45
|
+
'version_name' => version_name,
|
46
|
+
'version_code' => version_code,
|
47
|
+
'added' => added,
|
48
|
+
'apk_name' => @package['apkName'],
|
49
|
+
'hash' => @package['hash'],
|
50
|
+
'hash_type' => @package['hashType'],
|
51
|
+
'min_sdk_version' => @package['minSdkVersion'],
|
52
|
+
'max_sdk_version' => @package['maxSdkVersion'],
|
53
|
+
'target_sdk_version' => @package['targetSdkVersion'],
|
54
|
+
'native_code' => @package['nativecode'],
|
55
|
+
'sig' => @package['sig'],
|
56
|
+
'size' => @package['size'],
|
57
|
+
'uses_permission' => permission,
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def permission
|
62
|
+
if @package['uses-permission'] == nil then
|
63
|
+
[]
|
64
|
+
else
|
65
|
+
@package['uses-permission'].map {|perm| Permission.new(perm).to_data }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def field(name)
|
72
|
+
@app.key?(name) ? name : nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -15,12 +15,18 @@
|
|
15
15
|
# You should have received a copy of the GNU Affero General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
module FDroid
|
19
|
+
class Permission
|
20
|
+
def initialize(permission)
|
21
|
+
@permission = permission[0]
|
22
|
+
@min_sdk = permission[1]
|
23
|
+
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
25
|
+
def to_data
|
26
|
+
{
|
27
|
+
'permission' => @permission,
|
28
|
+
'min_sdk' => @min_sdk,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/fdroid/Repo.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# F-Droid's Jekyll Plugin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2017 Peter Serwylo
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
module FDroid
|
19
|
+
class Repo
|
20
|
+
def initialize(repo)
|
21
|
+
@repo = repo
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
@repo['name']
|
26
|
+
end
|
27
|
+
|
28
|
+
def address
|
29
|
+
@repo['address']
|
30
|
+
end
|
31
|
+
|
32
|
+
def icon_url
|
33
|
+
"#{self.address}/icons/#{@repo['icon']}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
@repo['description']
|
38
|
+
end
|
39
|
+
|
40
|
+
def date
|
41
|
+
added = Date.strptime("#{@repo['timestamp'] / 1000}", '%s')
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/jekyll-fdroid.rb
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
# You should have received a copy of the GNU Affero General Public License
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
|
-
require "fdroid/
|
18
|
+
require "fdroid/IndexV1"
|
19
19
|
require "jekyll/ReadYamlPage"
|
20
20
|
require "jekyll/FDroidBrowsingPage"
|
21
21
|
require "jekyll/FDroidFilters"
|
@@ -24,6 +24,7 @@ require "jekyll/FDroidLatestPackagesTag"
|
|
24
24
|
require "jekyll/FDroidPackageDetailGenerator"
|
25
25
|
require "jekyll/FDroidPackageDetailPage"
|
26
26
|
require "jekyll/FDroidSearchAutocompleteTag"
|
27
|
+
require "jekyll/FDroidRepoInfoTag"
|
27
28
|
require "lunr/LunrIndexer"
|
28
29
|
require "lunr/SearchIndexFile"
|
29
30
|
require "lunr/Javascript"
|
@@ -44,13 +44,13 @@ module Jekyll
|
|
44
44
|
end
|
45
45
|
site.config["pagination"]["enabled"] = true
|
46
46
|
|
47
|
-
|
47
|
+
index = FDroid::IndexV1.download(site.config["fdroid-repo"], site.active_lang || 'en_US')
|
48
48
|
|
49
|
-
Jekyll::LunrJsSearch::Indexer.new.generate(site,
|
49
|
+
Jekyll::LunrJsSearch::Indexer.new.generate(site, index.apps)
|
50
50
|
|
51
51
|
# Generate detail page for every package
|
52
52
|
site.collections["packages"] = Collection.new(site, "packages")
|
53
|
-
|
53
|
+
index.apps.each do |package|
|
54
54
|
# This page needs to be created twice, once for site.pages, and once for site.collections.
|
55
55
|
# If not, then the i18n code in jekyll-polyglot will end up processing the page twice, as
|
56
56
|
# it iterates over all pages and all packages. The end result is a double prefix for "/en/en"
|
@@ -17,112 +17,29 @@
|
|
17
17
|
|
18
18
|
module Jekyll
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# Hash with relation between Jekyll and XML variable name
|
46
|
-
assignments = {
|
47
|
-
"added" => "added",
|
48
|
-
"antifeatures" => "antifeatures",
|
49
|
-
"bitcoin" => "bitcoin",
|
50
|
-
"categories" => "categories",
|
51
|
-
"changelog" => "changelog",
|
52
|
-
"description" => "desc",
|
53
|
-
"donate" => "donate",
|
54
|
-
"flattr" => "flattr",
|
55
|
-
"icon" => "icon",
|
56
|
-
"issueTracker" => "tracker",
|
57
|
-
"lastUpdated" => "lastupdated",
|
58
|
-
"license" => "license",
|
59
|
-
"suggestedVersionCode" => "marketvercode",
|
60
|
-
"suggestedVersion" => "marketversion",
|
61
|
-
"package" => "id",
|
62
|
-
"sourceCode" => "source",
|
63
|
-
"summary" => "summary",
|
64
|
-
"title" => "name",
|
65
|
-
"webSite" => "web"
|
66
|
-
}
|
67
|
-
# Add information from XML to front matter
|
68
|
-
assignments.each do |jekyll, xml|
|
69
|
-
addGeneralFrontMatterData(jekyll, xml)
|
70
|
-
end
|
71
|
-
self.data["description"] = processFdroidAppLinks(self.data["description"])
|
72
|
-
self.data["beautifulURL"] = "/packages/" + self.data["package"]
|
73
|
-
end
|
74
|
-
|
75
|
-
def processFdroidAppLinks(string)
|
76
|
-
string.gsub(/fdroid\.app:([\w._]*)/, '/packages/\1')
|
77
|
-
end
|
78
|
-
|
79
|
-
def addGeneralFrontMatterData(jekyll, xml)
|
80
|
-
xmlData = $package.at_xpath(xml)
|
81
|
-
if xmlData != nil
|
82
|
-
self.data[jekyll] = xmlData.content
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Hash with relation between Jekyll and XML variable names for the package metadata
|
87
|
-
@@jekyllToXmlPackageAssignments = {
|
88
|
-
"added" => "added",
|
89
|
-
"apkName" => "apkname",
|
90
|
-
"hash" => "hash",
|
91
|
-
"nativeCode" => "nativecode",
|
92
|
-
"maxSDKVersion" => "maxsdkver",
|
93
|
-
"permissions" => "permissions",
|
94
|
-
"sdkVersion" => "sdkver",
|
95
|
-
"sig" => "sig",
|
96
|
-
"size" => "size",
|
97
|
-
"srcName" => "srcname",
|
98
|
-
"targetSdkVersion" => "targetSdkVersion",
|
99
|
-
"version" => "version",
|
100
|
-
"versionCode" => "versioncode",
|
101
|
-
}
|
102
|
-
|
103
|
-
def getPackagesFrontMatterData
|
104
|
-
self.data["packages"] = $package.xpath('package').map { |package| getPackageFromXml(package) }
|
105
|
-
end
|
106
|
-
|
107
|
-
def getPackageFromXml(packageXml)
|
108
|
-
packageInformation = Hash.new
|
109
|
-
|
110
|
-
# Add information from XML to front matter
|
111
|
-
@@jekyllToXmlPackageAssignments.each do |jekyll, xml|
|
112
|
-
xmlData = packageXml.at_xpath(xml)
|
113
|
-
if xmlData == nil
|
114
|
-
next
|
115
|
-
end
|
116
|
-
|
117
|
-
# nativeCode and permissions can be comma separated arrays
|
118
|
-
if ["nativeCode", "permissions"].include? jekyll
|
119
|
-
packageInformation[jekyll] = xmlData.content.split(",")
|
120
|
-
else
|
121
|
-
packageInformation[jekyll] = xmlData.content
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
return packageInformation
|
126
|
-
end
|
127
|
-
end
|
20
|
+
class FDroidPackageDetailPage < ReadYamlPage
|
21
|
+
|
22
|
+
# @param [Jekyll::Site] site
|
23
|
+
# @param [string] base
|
24
|
+
# @param [FDfroid::App] package
|
25
|
+
def initialize(site, base, package)
|
26
|
+
@site = site
|
27
|
+
@base = base
|
28
|
+
@dir = 'packages'
|
29
|
+
@name = "#{package.package_name}/index.html"
|
30
|
+
|
31
|
+
self.process(@name)
|
32
|
+
self.read_yaml(get_layout_dir, 'package.html')
|
33
|
+
self.data.update(package.to_data)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_layout_dir()
|
37
|
+
layout_dir_override = File.join(site.source, '_layouts')
|
38
|
+
if File.exists? File.join(layout_dir_override, 'package.html')
|
39
|
+
layout_dir_override
|
40
|
+
else
|
41
|
+
File.expand_path '../../_layouts', File.dirname(__FILE__)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
128
45
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# F-Droid's Jekyll Plugin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2017 Peter Serwylo
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as
|
7
|
+
# published by the Free Software Foundation, either version 3 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require_relative '../fdroid/IndexV1'
|
19
|
+
|
20
|
+
module Jekyll
|
21
|
+
|
22
|
+
# Used to output the repo name/timestamp used to generate this F-Droid site.
|
23
|
+
class FDroidRepoInfoTag < Liquid::Tag
|
24
|
+
|
25
|
+
def initialize(tag_name, text, tokens)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(context)
|
30
|
+
site = context.registers[:site]
|
31
|
+
url = site.config['fdroid-repo']
|
32
|
+
index = FDroid::IndexV1.download(url, 'en')
|
33
|
+
|
34
|
+
"#{index.repo.name} #{index.repo.date}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Liquid::Template.register_tag('fdroid_repo_info', Jekyll::FDroidRepoInfoTag)
|
@@ -17,29 +17,76 @@
|
|
17
17
|
|
18
18
|
module Jekyll
|
19
19
|
|
20
|
-
class
|
21
|
-
def self.
|
22
|
-
context['result_item_template'] =
|
20
|
+
class SearchForm
|
21
|
+
def self.render_form(context, search_form_template_path, result_item_template_contents)
|
22
|
+
context['result_item_template'] = result_item_template_contents
|
23
23
|
context['search_id'] = rand(1000000)
|
24
24
|
|
25
|
-
|
26
|
-
template = Liquid::Template.parse(IO.read((File.expand_path path, File.dirname(__FILE__))))
|
25
|
+
template = Liquid::Template.parse(IO.read((File.expand_path( search_form_template_path, File.dirname(__FILE__)))))
|
27
26
|
template.render(context)
|
28
27
|
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# As the user types, a list of results is shown below the text input (floating above other content).
|
31
|
+
# When an item is selected, it will navigate to that packages page.
|
32
|
+
# Designed to be used in a sidebar widget.
|
33
|
+
|
34
|
+
class DropDownWithTemplate < Liquid::Block
|
35
|
+
def render(context)
|
36
|
+
search_form_template_path = "../../_layouts/search-autocomplete.html"
|
37
|
+
SearchForm.render_form(context, search_form_template_path, super.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class DefaultDropDown < Liquid::Tag
|
42
|
+
def render(context)
|
43
|
+
search_form_template_path = "../../_layouts/search-autocomplete.html"
|
44
|
+
|
45
|
+
result_item_template_path = "../../_includes/search-autocomplete-default-result-template.html"
|
46
|
+
result_item_template = IO.read((File.expand_path(result_item_template_path, File.dirname(__FILE__))))
|
47
|
+
|
48
|
+
SearchForm.render_form(context, search_form_template_path, result_item_template)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# As the user types, a div is populated with search results.
|
53
|
+
# Differs from DropDownAutocomplete in that once you move focus away from the text input, the results
|
54
|
+
# are still displayed.
|
55
|
+
# Designed for a fully fledged search form on its own page.
|
29
56
|
|
57
|
+
# For each result result, this will render the template found between
|
58
|
+
# the {% fdroid_search_full_with_template %}{% endfdroid_search_full_with_template %} tags.
|
59
|
+
class FullSearchWithTemplate < Liquid::Block
|
30
60
|
def render(context)
|
31
|
-
|
61
|
+
search_form_template_path = "../../_layouts/search-full.html"
|
62
|
+
SearchForm.render_form(context, search_form_template_path, super.to_s)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# For each search result, this will render the contents of
|
67
|
+
# "_includes/search-full-default-result-template.html" from this plugin.
|
68
|
+
class DefaultFullSearch < Liquid::Tag
|
69
|
+
def initialize(tag_name, argument, tokens)
|
70
|
+
super
|
71
|
+
@empty_search_id = argument.strip
|
32
72
|
end
|
33
|
-
end
|
34
73
|
|
35
|
-
class FDroidSearchAutocompleteTag < Liquid::Tag
|
36
74
|
def render(context)
|
37
|
-
|
38
|
-
|
39
|
-
|
75
|
+
search_form_template_path = "../../_layouts/search-full.html"
|
76
|
+
|
77
|
+
result_item_template_path = "../../_includes/search-full-default-result-template.html"
|
78
|
+
result_item_template = IO.read((File.expand_path(result_item_template_path, File.dirname(__FILE__))))
|
79
|
+
|
80
|
+
context['empty_search_id'] = @empty_search_id
|
81
|
+
SearchForm.render_form(context, search_form_template_path, result_item_template)
|
40
82
|
end
|
41
83
|
end
|
42
84
|
end
|
43
85
|
|
44
|
-
Liquid::Template.register_tag('fdroid_search_autocomplete', Jekyll::
|
45
|
-
Liquid::Template.register_tag('fdroid_search_autocomplete_with_template', Jekyll::
|
86
|
+
Liquid::Template.register_tag('fdroid_search_autocomplete', Jekyll::DefaultDropDown)
|
87
|
+
Liquid::Template.register_tag('fdroid_search_autocomplete_with_template', Jekyll::DropDownWithTemplate)
|
88
|
+
|
89
|
+
# You can optionally specify the ID of a div where the results are to be rendered as the argument to these tags.
|
90
|
+
# Note that if you do so, it will hide all elements from this div when rendering it.
|
91
|
+
Liquid::Template.register_tag('fdroid_search_full', Jekyll::DefaultFullSearch)
|
92
|
+
Liquid::Template.register_tag('fdroid_search_full_with_template', Jekyll::FullSearchWithTemplate)
|
data/lib/lunr/LunrIndexer.rb
CHANGED
@@ -13,97 +13,90 @@ require 'uri'
|
|
13
13
|
require 'v8'
|
14
14
|
|
15
15
|
module Jekyll
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
def self.content_from_xml(xml_node, element_name)
|
103
|
-
xml_data = xml_node.at_xpath(element_name)
|
104
|
-
return xml_data == nil ? nil : xml_data.content
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
end
|
16
|
+
module LunrJsSearch
|
17
|
+
class Indexer
|
18
|
+
|
19
|
+
# @param [Jekyll::Site] site
|
20
|
+
# @param [Array<App>] packages
|
21
|
+
# @return [Object]
|
22
|
+
def generate(site, packages)
|
23
|
+
@js_dir = 'js'
|
24
|
+
|
25
|
+
ctx = V8::Context.new
|
26
|
+
ctx.load(Indexer.path_to_asset('bower_components/lunr.js/lunr.js'))
|
27
|
+
|
28
|
+
ctx['indexer'] = proc do |this|
|
29
|
+
this.ref('id')
|
30
|
+
this.field('name')
|
31
|
+
this.field('summary')
|
32
|
+
end
|
33
|
+
|
34
|
+
ctx.eval('builder = new lunr.Builder')
|
35
|
+
ctx.eval('builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter)')
|
36
|
+
ctx.eval('indexer.call(builder, builder)')
|
37
|
+
|
38
|
+
@lunr_version = ctx.eval('lunr.version')
|
39
|
+
@docs = {}
|
40
|
+
|
41
|
+
Jekyll.logger.info "Lunr:", "Creating search index (lunr.js version #{@lunr_version})..."
|
42
|
+
|
43
|
+
@site = site
|
44
|
+
|
45
|
+
packages.each_with_index do |package, i|
|
46
|
+
doc = {
|
47
|
+
'id' => i,
|
48
|
+
'packageName' => package.package_name,
|
49
|
+
'icon' => package.icon,
|
50
|
+
'name' => package.name,
|
51
|
+
'summary' => package.summary
|
52
|
+
}
|
53
|
+
|
54
|
+
ctx['builder'].add(doc)
|
55
|
+
@docs[i] = doc
|
56
|
+
|
57
|
+
Jekyll.logger.debug "Lunr:", package.package_name
|
58
|
+
end
|
59
|
+
|
60
|
+
@index = ctx.eval('builder.build()')
|
61
|
+
|
62
|
+
FileUtils.mkdir_p(File.join(site.dest, @js_dir))
|
63
|
+
filename = File.join(@js_dir, 'index.json')
|
64
|
+
|
65
|
+
total = {
|
66
|
+
"docs" => @docs,
|
67
|
+
"index" => @index.to_hash
|
68
|
+
}
|
69
|
+
|
70
|
+
filepath = File.join(site.dest, filename)
|
71
|
+
File.open(filepath, "w") {|f| f.write(JSON.dump(total))}
|
72
|
+
Jekyll.logger.info "Lunr:", "Index ready (lunr.js v#{@lunr_version})"
|
73
|
+
added_files = [filename]
|
74
|
+
|
75
|
+
site_js = File.join(site.dest, @js_dir)
|
76
|
+
extras = [
|
77
|
+
'assets/fdroid-search-autocomplete.js',
|
78
|
+
'bower_components/lunr.js/lunr.js',
|
79
|
+
'bower_components/mustache.js/mustache.min.js',
|
80
|
+
'bower_components/awesomplete/awesomplete.min.js',
|
81
|
+
'bower_components/awesomplete/awesomplete.css'
|
82
|
+
]
|
83
|
+
Jekyll.logger.info "Lunr:", "Added required assets to #{@js_dir}"
|
84
|
+
extras.each do |path|
|
85
|
+
src = Indexer.path_to_asset(path)
|
86
|
+
Jekyll.logger.debug "Lunr:", "Copying asset from #{src} to #{site_js}"
|
87
|
+
FileUtils.cp(src, site_js)
|
88
|
+
added_files.push(File.join(@js_dir, File.basename(src)))
|
89
|
+
end
|
90
|
+
|
91
|
+
# Keep the written files from being cleaned by Jekyll
|
92
|
+
added_files.each do |filename|
|
93
|
+
site.static_files << SearchIndexFile.new(site, site.dest, "/", filename)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.path_to_asset(path)
|
98
|
+
return File.join(File.dirname(__FILE__), "../../#{path}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
109
102
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-fdroid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nico Alt
|
@@ -11,7 +11,7 @@ cert_chain: []
|
|
11
11
|
date: 2017-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: jekyll-include-cache
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,35 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: jekyll-
|
28
|
+
name: jekyll-paginate-v2
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "<="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "<="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.7.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: therubyracer
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.12'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubyzip
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - ">="
|
@@ -39,40 +67,44 @@ dependencies:
|
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
70
|
+
name: json
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
|
-
- - "
|
73
|
+
- - ">="
|
46
74
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
75
|
+
version: 1.8.5
|
48
76
|
type: :runtime
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
|
-
- - "
|
80
|
+
- - ">="
|
53
81
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
82
|
+
version: 1.8.5
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
84
|
+
name: rspec
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
58
86
|
requirements:
|
59
|
-
- - "
|
87
|
+
- - ">="
|
60
88
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0
|
62
|
-
type: :
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
|
-
- - "
|
94
|
+
- - ">="
|
67
95
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0
|
96
|
+
version: '0'
|
69
97
|
description: Browse packages of a F-Droid repository.
|
70
98
|
email: nicoalt@posteo.org
|
71
99
|
executables: []
|
72
100
|
extensions: []
|
73
101
|
extra_rdoc_files: []
|
74
102
|
files:
|
75
|
-
- lib/fdroid/
|
103
|
+
- lib/fdroid/App.rb
|
104
|
+
- lib/fdroid/IndexV1.rb
|
105
|
+
- lib/fdroid/Package.rb
|
106
|
+
- lib/fdroid/Permission.rb
|
107
|
+
- lib/fdroid/Repo.rb
|
76
108
|
- lib/jekyll-fdroid.rb
|
77
109
|
- lib/jekyll/FDroidBrowsingPage.rb
|
78
110
|
- lib/jekyll/FDroidFilters.rb
|
@@ -80,6 +112,7 @@ files:
|
|
80
112
|
- lib/jekyll/FDroidLatestPackagesTag.rb
|
81
113
|
- lib/jekyll/FDroidPackageDetailGenerator.rb
|
82
114
|
- lib/jekyll/FDroidPackageDetailPage.rb
|
115
|
+
- lib/jekyll/FDroidRepoInfoTag.rb
|
83
116
|
- lib/jekyll/FDroidSearchAutocompleteTag.rb
|
84
117
|
- lib/jekyll/ReadYamlPage.rb
|
85
118
|
- lib/lunr/Javascript.rb
|