nano-pro-lib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/nano-pro-lib.gemspec +11 -0
- data/wicked_pdf-2.8.2/CHANGELOG.md +254 -0
- data/wicked_pdf-2.8.2/Gemfile +3 -0
- data/wicked_pdf-2.8.2/LICENSE.txt +22 -0
- data/wicked_pdf-2.8.2/README.md +504 -0
- data/wicked_pdf-2.8.2/Rakefile +58 -0
- data/wicked_pdf-2.8.2/gemfiles/5.0.gemfile +8 -0
- data/wicked_pdf-2.8.2/gemfiles/5.1.gemfile +8 -0
- data/wicked_pdf-2.8.2/gemfiles/5.2.gemfile +9 -0
- data/wicked_pdf-2.8.2/gemfiles/6.0.gemfile +10 -0
- data/wicked_pdf-2.8.2/gemfiles/6.1.gemfile +11 -0
- data/wicked_pdf-2.8.2/gemfiles/7.0.gemfile +11 -0
- data/wicked_pdf-2.8.2/generators/wicked_pdf/templates/wicked_pdf.rb +30 -0
- data/wicked_pdf-2.8.2/generators/wicked_pdf/wicked_pdf_generator.rb +7 -0
- data/wicked_pdf-2.8.2/init.rb +2 -0
- data/wicked_pdf-2.8.2/lib/generators/wicked_pdf_generator.rb +7 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/binary.rb +65 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/middleware.rb +92 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/option_parser.rb +230 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/pdf_helper.rb +128 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/progress.rb +33 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/railtie.rb +17 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/tempfile.rb +43 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/version.rb +3 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/wicked_pdf_helper/assets.rb +332 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf/wicked_pdf_helper.rb +36 -0
- data/wicked_pdf-2.8.2/lib/wicked_pdf.rb +132 -0
- data/wicked_pdf-2.8.2/test/fixtures/database.yml +4 -0
- data/wicked_pdf-2.8.2/test/fixtures/document_with_long_line.html +16 -0
- data/wicked_pdf-2.8.2/test/fixtures/manifest.js +3 -0
- data/wicked_pdf-2.8.2/test/fixtures/subdirectory/nested.js +1 -0
- data/wicked_pdf-2.8.2/test/fixtures/wicked.css +1 -0
- data/wicked_pdf-2.8.2/test/fixtures/wicked.js +1 -0
- data/wicked_pdf-2.8.2/test/functional/pdf_helper_test.rb +96 -0
- data/wicked_pdf-2.8.2/test/functional/wicked_pdf_helper_assets_test.rb +252 -0
- data/wicked_pdf-2.8.2/test/functional/wicked_pdf_helper_test.rb +28 -0
- data/wicked_pdf-2.8.2/test/test_helper.rb +40 -0
- data/wicked_pdf-2.8.2/test/unit/wicked_pdf_binary_test.rb +26 -0
- data/wicked_pdf-2.8.2/test/unit/wicked_pdf_option_parser_test.rb +133 -0
- data/wicked_pdf-2.8.2/test/unit/wicked_pdf_test.rb +100 -0
- data/wicked_pdf-2.8.2/test/unit/wkhtmltopdf_location_test.rb +48 -0
- data/wicked_pdf-2.8.2/wicked_pdf.gemspec +42 -0
- metadata +82 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'delegate'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
class WickedPdf
|
|
6
|
+
module WickedPdfHelper
|
|
7
|
+
module Assets
|
|
8
|
+
ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/
|
|
9
|
+
|
|
10
|
+
class MissingAsset < StandardError; end
|
|
11
|
+
|
|
12
|
+
class MissingLocalAsset < MissingAsset
|
|
13
|
+
attr_reader :path
|
|
14
|
+
|
|
15
|
+
def initialize(path)
|
|
16
|
+
@path = path
|
|
17
|
+
super("Could not find asset '#{path}'")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class MissingRemoteAsset < MissingAsset
|
|
22
|
+
attr_reader :url, :response
|
|
23
|
+
|
|
24
|
+
def initialize(url, response)
|
|
25
|
+
@url = url
|
|
26
|
+
@response = response
|
|
27
|
+
super("Could not fetch asset '#{url}': server responded with #{response.code} #{response.message}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class PropshaftAsset < SimpleDelegator
|
|
32
|
+
def content_type
|
|
33
|
+
super.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
content
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def filename
|
|
41
|
+
path.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class SprocketsEnvironment
|
|
46
|
+
def self.instance
|
|
47
|
+
@instance ||= Sprockets::Railtie.build_environment(Rails.application)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.find_asset(*args)
|
|
51
|
+
instance.find_asset(*args)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class LocalAsset
|
|
56
|
+
attr_reader :path
|
|
57
|
+
|
|
58
|
+
def initialize(path)
|
|
59
|
+
@path = path
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def content_type
|
|
63
|
+
Mime::Type.lookup_by_extension(File.extname(path).delete('.'))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_s
|
|
67
|
+
IO.read(path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def filename
|
|
71
|
+
path.to_s
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def wicked_pdf_asset_base64(path)
|
|
76
|
+
asset = find_asset(path)
|
|
77
|
+
raise MissingLocalAsset, path if asset.nil?
|
|
78
|
+
|
|
79
|
+
base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '')
|
|
80
|
+
"data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Using `image_tag` with URLs when generating PDFs (specifically large PDFs with lots of pages) can cause buffer/stack overflows.
|
|
84
|
+
#
|
|
85
|
+
def wicked_pdf_url_base64(url)
|
|
86
|
+
response = Net::HTTP.get_response(URI(url))
|
|
87
|
+
|
|
88
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
89
|
+
base64 = Base64.encode64(response.body).gsub(/\s+/, '')
|
|
90
|
+
"data:#{response.content_type};base64,#{Rack::Utils.escape(base64)}"
|
|
91
|
+
else
|
|
92
|
+
Rails.logger.warn("[wicked_pdf] #{response.code} #{response.message}: #{url}")
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def wicked_pdf_stylesheet_link_tag(*sources)
|
|
98
|
+
stylesheet_contents = sources.collect do |source|
|
|
99
|
+
source = WickedPdfHelper.add_extension(source, 'css')
|
|
100
|
+
"<style type='text/css'>#{read_asset(source)}</style>"
|
|
101
|
+
end.join("\n")
|
|
102
|
+
|
|
103
|
+
stylesheet_contents.gsub(ASSET_URL_REGEX) do
|
|
104
|
+
if Regexp.last_match[1].starts_with?('data:')
|
|
105
|
+
"url(#{Regexp.last_match[1]})"
|
|
106
|
+
else
|
|
107
|
+
"url(#{wicked_pdf_asset_path(Regexp.last_match[1])})"
|
|
108
|
+
end
|
|
109
|
+
end.html_safe
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def wicked_pdf_stylesheet_pack_tag(*sources)
|
|
113
|
+
return unless defined?(Webpacker)
|
|
114
|
+
|
|
115
|
+
if running_in_development?
|
|
116
|
+
stylesheet_pack_tag(*sources)
|
|
117
|
+
else
|
|
118
|
+
css_text = sources.collect do |source|
|
|
119
|
+
source = WickedPdfHelper.add_extension(source, 'css')
|
|
120
|
+
wicked_pdf_stylesheet_link_tag(webpacker_source_url(source))
|
|
121
|
+
end.join("\n")
|
|
122
|
+
css_text.respond_to?(:html_safe) ? css_text.html_safe : css_text
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def wicked_pdf_javascript_pack_tag(*sources)
|
|
127
|
+
return unless defined?(Webpacker)
|
|
128
|
+
|
|
129
|
+
if running_in_development?
|
|
130
|
+
javascript_pack_tag(*sources)
|
|
131
|
+
else
|
|
132
|
+
sources.collect do |source|
|
|
133
|
+
source = WickedPdfHelper.add_extension(source, 'js')
|
|
134
|
+
"<script type='text/javascript'>#{read_asset(webpacker_source_url(source))}</script>"
|
|
135
|
+
end.join("\n").html_safe
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def wicked_pdf_image_tag(img, options = {})
|
|
140
|
+
image_tag wicked_pdf_asset_path(img), options
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def wicked_pdf_javascript_src_tag(jsfile, options = {})
|
|
144
|
+
jsfile = WickedPdfHelper.add_extension(jsfile, 'js')
|
|
145
|
+
javascript_include_tag wicked_pdf_asset_path(jsfile), options
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def wicked_pdf_javascript_include_tag(*sources)
|
|
149
|
+
sources.collect do |source|
|
|
150
|
+
source = WickedPdfHelper.add_extension(source, 'js')
|
|
151
|
+
"<script type='text/javascript'>#{read_asset(source)}</script>"
|
|
152
|
+
end.join("\n").html_safe
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def wicked_pdf_asset_path(asset)
|
|
156
|
+
if (pathname = asset_pathname(asset).to_s) =~ URI_REGEXP
|
|
157
|
+
pathname
|
|
158
|
+
else
|
|
159
|
+
"file:///#{pathname}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def wicked_pdf_asset_pack_path(asset)
|
|
164
|
+
return unless defined?(Webpacker)
|
|
165
|
+
|
|
166
|
+
if running_in_development?
|
|
167
|
+
asset_pack_path(asset)
|
|
168
|
+
else
|
|
169
|
+
wicked_pdf_asset_path webpacker_source_url(asset)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
# borrowed from actionpack/lib/action_view/helpers/asset_url_helper.rb
|
|
176
|
+
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
|
|
177
|
+
|
|
178
|
+
def asset_pathname(source)
|
|
179
|
+
if precompiled_or_absolute_asset?(source)
|
|
180
|
+
asset = asset_path(source)
|
|
181
|
+
pathname = prepend_protocol(asset)
|
|
182
|
+
if pathname =~ URI_REGEXP
|
|
183
|
+
# asset_path returns an absolute URL using asset_host if asset_host is set
|
|
184
|
+
pathname
|
|
185
|
+
else
|
|
186
|
+
File.join(Rails.public_path, asset.sub(/\A#{Rails.application.config.action_controller.relative_url_root}/, ''))
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
asset = find_asset(source)
|
|
190
|
+
if asset
|
|
191
|
+
# older versions need pathname, Sprockets 4 supports only filename
|
|
192
|
+
asset.respond_to?(:filename) ? asset.filename : asset.pathname
|
|
193
|
+
else
|
|
194
|
+
File.join(Rails.public_path, source)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def find_asset(path)
|
|
200
|
+
if Rails.application.assets.respond_to?(:find_asset)
|
|
201
|
+
Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s)
|
|
202
|
+
elsif defined?(Propshaft::Assembly) && Rails.application.assets.is_a?(Propshaft::Assembly)
|
|
203
|
+
PropshaftAsset.new(Rails.application.assets.load_path.find(path))
|
|
204
|
+
elsif Rails.application.respond_to?(:assets_manifest)
|
|
205
|
+
relative_asset_path = get_asset_path_from_manifest(path)
|
|
206
|
+
return unless relative_asset_path
|
|
207
|
+
|
|
208
|
+
asset_path = File.join(Rails.application.assets_manifest.dir, relative_asset_path)
|
|
209
|
+
LocalAsset.new(asset_path) if File.file?(asset_path)
|
|
210
|
+
else
|
|
211
|
+
SprocketsEnvironment.find_asset(path, :base_path => Rails.application.root.to_s)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def get_asset_path_from_manifest(path)
|
|
216
|
+
assets = Rails.application.assets_manifest.assets
|
|
217
|
+
|
|
218
|
+
if File.extname(path).empty?
|
|
219
|
+
assets.find do |asset, _v|
|
|
220
|
+
directory = File.dirname(asset)
|
|
221
|
+
asset_path = File.basename(asset, File.extname(asset))
|
|
222
|
+
asset_path = File.join(directory, asset_path) if directory != '.'
|
|
223
|
+
|
|
224
|
+
asset_path == path
|
|
225
|
+
end&.last
|
|
226
|
+
else
|
|
227
|
+
assets[path]
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# will prepend a http or default_protocol to a protocol relative URL
|
|
232
|
+
# or when no protcol is set.
|
|
233
|
+
def prepend_protocol(source)
|
|
234
|
+
protocol = WickedPdf.config[:default_protocol] || 'http'
|
|
235
|
+
if source[0, 2] == '//'
|
|
236
|
+
source = [protocol, ':', source].join
|
|
237
|
+
elsif source[0] != '/' && !source[0, 8].include?('://')
|
|
238
|
+
source = [protocol, '://', source].join
|
|
239
|
+
end
|
|
240
|
+
source
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def precompiled_or_absolute_asset?(source)
|
|
244
|
+
!Rails.configuration.respond_to?(:assets) ||
|
|
245
|
+
Rails.configuration.assets.compile == false ||
|
|
246
|
+
source.to_s[0] == '/' ||
|
|
247
|
+
source.to_s.match(/\Ahttps?\:\/\//)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def read_asset(source)
|
|
251
|
+
asset = find_asset(source)
|
|
252
|
+
return asset.to_s.force_encoding('UTF-8') if asset
|
|
253
|
+
|
|
254
|
+
unless precompiled_or_absolute_asset?(source)
|
|
255
|
+
raise MissingLocalAsset, source if WickedPdf.config[:raise_on_missing_assets]
|
|
256
|
+
|
|
257
|
+
return
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
pathname = asset_pathname(source)
|
|
261
|
+
if pathname =~ URI_REGEXP
|
|
262
|
+
read_from_uri(pathname)
|
|
263
|
+
elsif File.file?(pathname)
|
|
264
|
+
IO.read(pathname)
|
|
265
|
+
elsif WickedPdf.config[:raise_on_missing_assets]
|
|
266
|
+
raise MissingLocalAsset, pathname if WickedPdf.config[:raise_on_missing_assets]
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def read_from_uri(uri)
|
|
271
|
+
response = Net::HTTP.get_response(URI(uri))
|
|
272
|
+
|
|
273
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
274
|
+
raise MissingRemoteAsset.new(uri, response) if WickedPdf.config[:raise_on_missing_assets]
|
|
275
|
+
|
|
276
|
+
return
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
asset = response.body
|
|
280
|
+
asset.force_encoding('UTF-8') if asset
|
|
281
|
+
asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets]
|
|
282
|
+
asset
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def gzip(asset)
|
|
286
|
+
stringified_asset = StringIO.new(asset)
|
|
287
|
+
gzipper = Zlib::GzipReader.new(stringified_asset)
|
|
288
|
+
gzipper.read
|
|
289
|
+
rescue Zlib::GzipFile::Error
|
|
290
|
+
nil
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def webpacker_source_url(source)
|
|
294
|
+
return unless webpacker_version
|
|
295
|
+
|
|
296
|
+
# In Webpacker 3.2.0 asset_pack_url is introduced
|
|
297
|
+
if webpacker_version >= '3.2.0'
|
|
298
|
+
if (host = Rails.application.config.asset_host)
|
|
299
|
+
asset_pack_path(source, :host => host)
|
|
300
|
+
else
|
|
301
|
+
asset_pack_url(source)
|
|
302
|
+
end
|
|
303
|
+
else
|
|
304
|
+
source_path = asset_pack_path(source)
|
|
305
|
+
# Remove last slash from root path
|
|
306
|
+
root_url[0...-1] + source_path
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def running_in_development?
|
|
311
|
+
return unless webpacker_version
|
|
312
|
+
|
|
313
|
+
# :dev_server method was added in webpacker 3.0.0
|
|
314
|
+
if Webpacker.respond_to?(:dev_server)
|
|
315
|
+
Webpacker.dev_server.running?
|
|
316
|
+
else
|
|
317
|
+
Rails.env.development? || Rails.env.test?
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def webpacker_version
|
|
322
|
+
if defined?(Shakapacker)
|
|
323
|
+
require 'shakapacker/version'
|
|
324
|
+
Shakapacker::VERSION
|
|
325
|
+
elsif defined?(Webpacker)
|
|
326
|
+
require 'webpacker/version'
|
|
327
|
+
Webpacker::VERSION
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class WickedPdf
|
|
2
|
+
module WickedPdfHelper
|
|
3
|
+
def self.root_path
|
|
4
|
+
String === Rails.root ? Pathname.new(Rails.root) : Rails.root
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.add_extension(filename, extension)
|
|
8
|
+
filename.to_s.split('.').include?(extension) ? filename : "#{filename}.#{extension}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def wicked_pdf_stylesheet_link_tag(*sources)
|
|
12
|
+
css_dir = WickedPdfHelper.root_path.join('public', 'stylesheets')
|
|
13
|
+
css_text = sources.collect do |source|
|
|
14
|
+
source = WickedPdfHelper.add_extension(source, 'css')
|
|
15
|
+
"<style type='text/css'>#{File.read(css_dir.join(source))}</style>"
|
|
16
|
+
end.join("\n")
|
|
17
|
+
css_text.respond_to?(:html_safe) ? css_text.html_safe : css_text
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wicked_pdf_image_tag(img, options = {})
|
|
21
|
+
image_tag "file:///#{WickedPdfHelper.root_path.join('public', 'images', img)}", options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def wicked_pdf_javascript_src_tag(jsfile, options = {})
|
|
25
|
+
jsfile = WickedPdfHelper.add_extension(jsfile, 'js')
|
|
26
|
+
type = ::Mime.respond_to?(:[]) ? ::Mime[:js] : ::Mime::JS # ::Mime[:js] cannot be used in Rails 2.3.
|
|
27
|
+
src = "file:///#{WickedPdfHelper.root_path.join('public', 'javascripts', jsfile)}"
|
|
28
|
+
content_tag('script', '', { 'type' => type, 'src' => path_to_javascript(src) }.merge(options))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def wicked_pdf_javascript_include_tag(*sources)
|
|
32
|
+
js_text = sources.collect { |source| wicked_pdf_javascript_src_tag(source, {}) }.join("\n")
|
|
33
|
+
js_text.respond_to?(:html_safe) ? js_text.html_safe : js_text
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# wkhtml2pdf Ruby interface
|
|
2
|
+
# http://wkhtmltopdf.org/
|
|
3
|
+
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'digest/md5'
|
|
6
|
+
require 'rbconfig'
|
|
7
|
+
require 'open3'
|
|
8
|
+
require 'ostruct'
|
|
9
|
+
|
|
10
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
11
|
+
require 'active_support/core_ext/object/blank'
|
|
12
|
+
|
|
13
|
+
require 'wicked_pdf/version'
|
|
14
|
+
require 'wicked_pdf/railtie'
|
|
15
|
+
require 'wicked_pdf/option_parser'
|
|
16
|
+
require 'wicked_pdf/tempfile'
|
|
17
|
+
require 'wicked_pdf/binary'
|
|
18
|
+
require 'wicked_pdf/middleware'
|
|
19
|
+
require 'wicked_pdf/progress'
|
|
20
|
+
|
|
21
|
+
class WickedPdf
|
|
22
|
+
DEFAULT_BINARY_VERSION = Gem::Version.new('0.9.9')
|
|
23
|
+
@@config = {}
|
|
24
|
+
cattr_accessor :config, :silence_deprecations
|
|
25
|
+
|
|
26
|
+
include Progress
|
|
27
|
+
|
|
28
|
+
def self.config=(config)
|
|
29
|
+
::Kernel.warn 'WickedPdf.config= is deprecated and will be removed in future versions. Use WickedPdf.configure instead.' unless @@silence_deprecations
|
|
30
|
+
|
|
31
|
+
@@config = config
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.configure
|
|
35
|
+
config = OpenStruct.new(@@config)
|
|
36
|
+
yield config
|
|
37
|
+
|
|
38
|
+
@@config.merge! config.to_h
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.clear_config
|
|
42
|
+
@@config = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(wkhtmltopdf_binary_path = nil)
|
|
46
|
+
@binary = Binary.new(wkhtmltopdf_binary_path, DEFAULT_BINARY_VERSION)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def binary_version
|
|
50
|
+
@binary.version
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pdf_from_html_file(filepath, options = {})
|
|
54
|
+
pdf_from_url("file:///#{filepath}", options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pdf_from_string(string, options = {})
|
|
58
|
+
options = options.dup
|
|
59
|
+
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
|
60
|
+
string_file = WickedPdf::Tempfile.new('wicked_pdf.html', options[:temp_path])
|
|
61
|
+
string_file.write_in_chunks(string)
|
|
62
|
+
pdf_from_html_file(string_file.path, options)
|
|
63
|
+
ensure
|
|
64
|
+
if options[:delete_temporary_files] && string_file
|
|
65
|
+
string_file.close!
|
|
66
|
+
elsif string_file
|
|
67
|
+
string_file.close
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def pdf_from_url(url, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
|
|
72
|
+
# merge in global config options
|
|
73
|
+
options.merge!(WickedPdf.config) { |_key, option, _config| option }
|
|
74
|
+
generated_pdf_file = WickedPdf::Tempfile.new('wicked_pdf_generated_file.pdf', options[:temp_path])
|
|
75
|
+
command = [@binary.path]
|
|
76
|
+
command.unshift(@binary.xvfb_run_path) if options[:use_xvfb]
|
|
77
|
+
command += option_parser.parse(options)
|
|
78
|
+
command << url
|
|
79
|
+
command << generated_pdf_file.path.to_s
|
|
80
|
+
|
|
81
|
+
print_command(command.inspect) if in_development_mode?
|
|
82
|
+
|
|
83
|
+
if track_progress?(options)
|
|
84
|
+
invoke_with_progress(command, options)
|
|
85
|
+
else
|
|
86
|
+
_out, err, status = Open3.capture3(*command)
|
|
87
|
+
err = [status.to_s, err].join("\n") if !err.empty? || !status.success?
|
|
88
|
+
end
|
|
89
|
+
if options[:return_file]
|
|
90
|
+
return_file = options.delete(:return_file)
|
|
91
|
+
return generated_pdf_file
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
pdf = generated_pdf_file.read_in_chunks
|
|
95
|
+
|
|
96
|
+
raise "Error generating PDF\n Command Error: #{err}" if options[:raise_on_all_errors] && !err.empty?
|
|
97
|
+
raise "PDF could not be generated!\n Command Error: #{err}" if pdf && pdf.rstrip.empty?
|
|
98
|
+
|
|
99
|
+
pdf
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
raise "Failed to execute:\n#{command}\nError: #{e}"
|
|
102
|
+
ensure
|
|
103
|
+
clean_temp_files
|
|
104
|
+
generated_pdf_file.close! if generated_pdf_file && !return_file
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def in_development_mode?
|
|
110
|
+
return Rails.env == 'development' if defined?(Rails.env)
|
|
111
|
+
|
|
112
|
+
RAILS_ENV == 'development' if defined?(RAILS_ENV)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def on_windows?
|
|
116
|
+
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def print_command(cmd)
|
|
120
|
+
Rails.logger.debug '[wicked_pdf]: ' + cmd
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def option_parser
|
|
124
|
+
@option_parser ||= OptionParser.new(binary_version)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def clean_temp_files
|
|
128
|
+
return unless option_parser.hf_tempfiles.present?
|
|
129
|
+
|
|
130
|
+
option_parser.hf_tempfiles.each { |file| File.delete(file) }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
4
|
+
<body>
|
|
5
|
+
<div>
|
|
6
|
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
7
|
+
</div>
|
|
8
|
+
</body>
|
|
9
|
+
</html>
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Nested js
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* Wicked styles */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Wicked js
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module ActionController
|
|
4
|
+
class Base
|
|
5
|
+
def render_to_string(opts = {})
|
|
6
|
+
opts.to_s
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.alias_method_chain(_target, _feature); end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ActionControllerMock
|
|
14
|
+
class Base
|
|
15
|
+
def render(_)
|
|
16
|
+
[:base]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render_to_string; end
|
|
20
|
+
|
|
21
|
+
def self.after_action(_); end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class PdfHelperTest < ActionController::TestCase
|
|
26
|
+
module SomePatch
|
|
27
|
+
def render(_)
|
|
28
|
+
super.tap do |s|
|
|
29
|
+
s << :patched
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def setup
|
|
35
|
+
@ac = ActionController::Base.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def teardown
|
|
39
|
+
@ac = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test 'should prerender header and footer :template options' do
|
|
43
|
+
options = @ac.send(:prerender_header_and_footer,
|
|
44
|
+
:header => { :html => { :template => 'hf.html.erb' } })
|
|
45
|
+
assert_match %r{^file:\/\/\/.*wicked_header_pdf.*\.html}, options[:header][:html][:url]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
test 'should not interfere with already prepended patches' do
|
|
49
|
+
# Emulate railtie
|
|
50
|
+
if Rails::VERSION::MAJOR >= 5
|
|
51
|
+
# this spec tests the following:
|
|
52
|
+
# if another gem prepends a render method to ActionController::Base
|
|
53
|
+
# before wicked_pdf does, does calling render trigger an infinite loop?
|
|
54
|
+
# this spec fails with 6392bea1fe3a41682dfd7c20fd9c179b5a758f59 because PdfHelper
|
|
55
|
+
# aliases the render method prepended by the other gem to render_without_pdf, then
|
|
56
|
+
# base_evals its own definition of render, which calls render_with_pdf -> render_without_pdf.
|
|
57
|
+
# If the other gem uses the prepend inhertinance pattern (calling super instead of aliasing),
|
|
58
|
+
# when it calls super it calls the base_eval'd version of render instead of going up the
|
|
59
|
+
# inheritance chain, causing an infinite loop.
|
|
60
|
+
|
|
61
|
+
# This fiddling with consts is required to get around the fact that PdfHelper checks
|
|
62
|
+
# that it is being prepended to ActionController::Base
|
|
63
|
+
OriginalBase = ActionController::Base
|
|
64
|
+
ActionController.send(:remove_const, :Base)
|
|
65
|
+
ActionController.const_set(:Base, ActionControllerMock::Base)
|
|
66
|
+
|
|
67
|
+
# Emulate another gem being loaded before wicked
|
|
68
|
+
ActionController::Base.prepend(SomePatch)
|
|
69
|
+
ActionController::Base.prepend(::WickedPdf::PdfHelper)
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
# test that wicked's render method is actually called
|
|
73
|
+
ac = ActionController::Base.new
|
|
74
|
+
ac.expects(:render_with_wicked_pdf)
|
|
75
|
+
ac.render(:cats)
|
|
76
|
+
|
|
77
|
+
# test that calling render does not trigger infinite loop
|
|
78
|
+
ac = ActionController::Base.new
|
|
79
|
+
assert_equal %i[base patched], ac.render(:cats)
|
|
80
|
+
rescue SystemStackError
|
|
81
|
+
assert_equal true, false # force spec failure
|
|
82
|
+
ensure
|
|
83
|
+
ActionController.send(:remove_const, :Base)
|
|
84
|
+
ActionController.const_set(:Base, OriginalBase)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
test 'should call after_action instead of after_filter when able' do
|
|
90
|
+
ActionController::Base.expects(:after_filter).with(:clean_temp_files).never
|
|
91
|
+
ActionController::Base.expects(:after_action).with(:clean_temp_files).once
|
|
92
|
+
ActionController::Base.class_eval do
|
|
93
|
+
include ::WickedPdf::PdfHelper
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|