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 +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
|