jekyll-fdroid 0.1.1 → 0.2.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: f4b78b9f00daf8c96e99905e33a38149b2ea0f2e
4
- data.tar.gz: 841d3465e9c14da0bd3bda315672d6d0c2b5f7c4
3
+ metadata.gz: 5223ec6f44fa3c3131928d083cee93e06c28045b
4
+ data.tar.gz: 1acae1bbdda2af1529df4d8fc955e27d6e428514
5
5
  SHA512:
6
- metadata.gz: 4cb43683d09abcf6a5cf4fb0b4168bcb61582017f632aa2d7b92d509bb9ac16bc215c2eaf93c55f0d312468d1a8a6343827e8542786f064293b26f2ae87ce73f
7
- data.tar.gz: bffd6df0de24f340df08d420e3b38aa0372f6476a2de610cd5fde5bcf8bbfeec47dfee23edf0e6d12559dcbc8dc695578e909b5706ff0639bcd6b91fd8db8141
6
+ metadata.gz: 52dd3848f495b9cf4a2c973faf5f3324e90bdacacbd21545e63a93a455771c4d1522739aba09968727e8e48f4d7bc496320142138ec9425cc7ce420fcad98bf6
7
+ data.tar.gz: c6227570b89a1c4edad316e55678bca5bd1278f28511d3474f41f58d6eda57bcd625c760626bebde750f80a960b6dd8fb828a08c87ca82ca1bc62e10d6469e78
@@ -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
- require 'nokogiri'
19
- require 'open-uri'
18
+ module FDroid
19
+ class Permission
20
+ def initialize(permission)
21
+ @permission = permission[0]
22
+ @min_sdk = permission[1]
23
+ end
20
24
 
21
- class FDroidIndex
22
- def getIndex(repo)
23
- index = open(URI.parse(repo + '/index.xml')).read
24
- Nokogiri::XML(index).xpath('fdroid').xpath('application')
25
- end
26
- end
25
+ def to_data
26
+ {
27
+ 'permission' => @permission,
28
+ 'min_sdk' => @min_sdk,
29
+ }
30
+ end
31
+ end
32
+ end
@@ -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
@@ -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/FDroidIndex"
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
- packages = FDroidIndex.new.getIndex(site.config["fdroid-repo"])
47
+ index = FDroid::IndexV1.download(site.config["fdroid-repo"], site.active_lang || 'en_US')
48
48
 
49
- Jekyll::LunrJsSearch::Indexer.new.generate(site, packages)
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
- packages.each do |package|
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
- class FDroidPackageDetailPage < ReadYamlPage
21
- def initialize(site, base, package)
22
- $package = package
23
- @site = site
24
- @base = base
25
- @dir = "packages"
26
- @name = $package.at_xpath('id').content + "/index.html"
27
-
28
- self.process(@name)
29
- self.read_yaml(getLayoutDir(), 'package.html')
30
-
31
- getGeneralFrontMatterData
32
- getPackagesFrontMatterData
33
- end
34
-
35
- def getLayoutDir()
36
- layout_dir_override = File.join(site.source, "_layouts")
37
- if File.exists? File.join(layout_dir_override, "package.html")
38
- return layout_dir_override
39
- else
40
- return File.expand_path "../../_layouts", File.dirname(__FILE__)
41
- end
42
- end
43
-
44
- def getGeneralFrontMatterData
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 FDroidSearchTemplateableAutocompleteBlock < Liquid::Block
21
- def self.render_template(context, template)
22
- context['result_item_template'] = 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
- path = "../../_layouts/search-autocomplete.html"
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
- FDroidSearchTemplateableAutocompleteBlock.render_template(context, super.to_s)
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
- path = "../../_includes/search-autocomplete-default-result-template.html"
38
- result_item_template = IO.read((File.expand_path path, File.dirname(__FILE__)))
39
- FDroidSearchTemplateableAutocompleteBlock.render_template(context, result_item_template)
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::FDroidSearchAutocompleteTag)
45
- Liquid::Template.register_tag('fdroid_search_autocomplete_with_template', Jekyll::FDroidSearchTemplateableAutocompleteBlock)
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)
@@ -13,97 +13,90 @@ require 'uri'
13
13
  require 'v8'
14
14
 
15
15
  module Jekyll
16
- module LunrJsSearch
17
- class Indexer
18
- def generate(site, packages)
19
- @js_dir = 'js'
20
-
21
- ctx = V8::Context.new
22
- ctx.load(Indexer.path_to_asset('bower_components/lunr.js/lunr.js'))
23
-
24
- ctx['indexer'] = proc do |this|
25
- this.ref('id')
26
- this.field('name')
27
- this.field('summary')
28
- end
29
-
30
- ctx.eval('builder = new lunr.Builder')
31
- ctx.eval('builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter)')
32
- ctx.eval('indexer.call(builder, builder)')
33
-
34
- @lunr_version = ctx.eval('lunr.version')
35
- @docs = {}
36
-
37
- Jekyll.logger.info "Lunr:", "Creating search index (lunr.js version #{@lunr_version})..."
38
-
39
- @site = site
40
-
41
- packages.each_with_index do |package, i|
42
- package_name = package['id']
43
- name = Indexer.content_from_xml(package, 'name')
44
- icon = Indexer.content_from_xml(package, 'icon')
45
- summary = Indexer.content_from_xml(package, 'summary')
46
-
47
- doc = {
48
- 'id' => i,
49
- 'packageName' => package_name,
50
- 'icon' => icon,
51
- 'name' => name,
52
- 'summary' => summary
53
- }
54
-
55
- ctx['builder'].add(doc)
56
- @docs[i] = doc
57
-
58
- Jekyll.logger.debug "Lunr:", package_name
59
- end
60
-
61
- @index = ctx.eval('builder.build()')
62
-
63
- FileUtils.mkdir_p(File.join(site.dest, @js_dir))
64
- filename = File.join(@js_dir, 'index.json')
65
-
66
- total = {
67
- "docs" => @docs,
68
- "index" => @index.to_hash
69
- }
70
-
71
- filepath = File.join(site.dest, filename)
72
- File.open(filepath, "w") { |f| f.write(JSON.dump(total)) }
73
- Jekyll.logger.info "Lunr:", "Index ready (lunr.js v#{@lunr_version})"
74
- added_files = [filename]
75
-
76
- site_js = File.join(site.dest, @js_dir)
77
- extras = [
78
- 'assets/fdroid-search-autocomplete.js',
79
- 'bower_components/lunr.js/lunr.js',
80
- 'bower_components/mustache.js/mustache.min.js',
81
- 'bower_components/awesomplete/awesomplete.min.js',
82
- 'bower_components/awesomplete/awesomplete.css'
83
- ]
84
- Jekyll.logger.info "Lunr:", "Added required assets to #{@js_dir}"
85
- extras.each do |path|
86
- src = Indexer.path_to_asset(path)
87
- Jekyll.logger.debug "Lunr:", "Copying asset from #{src} to #{site_js}"
88
- FileUtils.cp(src, site_js)
89
- added_files.push(File.join(@js_dir, File.basename(src)))
90
- end
91
-
92
- # Keep the written files from being cleaned by Jekyll
93
- added_files.each do |filename|
94
- site.static_files << SearchIndexFile.new(site, site.dest, "/", filename)
95
- end
96
- end
97
-
98
- def self.path_to_asset(path)
99
- return File.join(File.dirname(__FILE__), "../../#{path}")
100
- end
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.1.1
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: nokogiri
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-include-cache
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: jekyll-paginate-v2
70
+ name: json
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
- - - "<="
73
+ - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: 1.7.3
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.7.3
82
+ version: 1.8.5
55
83
  - !ruby/object:Gem::Dependency
56
- name: therubyracer
84
+ name: rspec
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
- - - "~>"
87
+ - - ">="
60
88
  - !ruby/object:Gem::Version
61
- version: '0.12'
62
- type: :runtime
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.12'
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/FDroidIndex.rb
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