gkhtmltopdf 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Dockerfile +3 -0
- data/README.md +67 -23
- data/TODO.md +13 -6
- data/exe/gkhtmltopdf +7 -3
- data/lib/errors.rb +19 -0
- data/lib/gkhtmltopdf/converter.rb +40 -39
- data/lib/gkhtmltopdf/dsl.rb +19 -0
- data/lib/gkhtmltopdf/version.rb +1 -1
- data/lib/gkhtmltopdf.rb +18 -7
- data/spec/gkhtmltopdf/converter_spec.rb +47 -9
- data/spec/gkhtmltopdf_spec.rb +24 -6
- data/spec/spec_helper.rb +11 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 450c50b4a300eb003c28a7d3df213ed05ec5ec0626172ef178966f693af4cf5d
|
|
4
|
+
data.tar.gz: 0a80041e7a625a55ba11f7636b25a2ebee2a2500e3a6df9d0d8e60c49300a4c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca97c9e7eeb874af9258869c4ead4d3dfbdd68f641afed5baaa66f5bdbf20dc1fd9b851039ba7fdc231df8f7ecd7b01b2b5768dddeace29c0c42608c469725ec
|
|
7
|
+
data.tar.gz: 2feea957d0d915498bb92378a6ff734ffce7922da83f1cadb4765090eb09d5d3030b38525c3295ec31fe1826453f34a4bfde5f0343aad8fef9d6fc943ac6234c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,18 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All noteworthy changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.0.0 / 2026-03-19
|
|
6
|
+
|
|
7
|
+
- Added serial processing for multiple files and URLs.
|
|
8
|
+
- 複数ファイル&URLの直列処理追加
|
|
9
|
+
- Separated the Gkhtmltopdf::Error into many.
|
|
10
|
+
- エラーを分離
|
|
11
|
+
- Add and Fixed test.
|
|
12
|
+
- テスト追加&修正
|
|
13
|
+
|
|
5
14
|
## 0.9.0 / 2026-03-13
|
|
6
15
|
|
|
7
16
|
- Achieved 100% test coverage.🎉
|
|
17
|
+
- テストカバレッジ100%!
|
|
8
18
|
- Fixed github url.
|
|
19
|
+
- github URL 修正
|
|
9
20
|
|
|
10
21
|
## 0.8.0 / 2026-03-10
|
|
11
22
|
|
|
12
23
|
- Added parallel processing support with automatic free port checking.
|
|
24
|
+
- 空きポートの処理追加による並列実行追加
|
|
13
25
|
- Added print options.
|
|
26
|
+
- プリントオプションの追加
|
|
14
27
|
- Added Dockerfile (Debian compatibility verified).
|
|
28
|
+
- Dockerfile追加(Debian互換検証)
|
|
15
29
|
- Added error message display when PATH errors occur.
|
|
30
|
+
- PATH関連のエラーメッセージ追加
|
|
16
31
|
|
|
17
32
|
## 0.1.0 / 2026-01-09
|
|
18
33
|
|
|
19
34
|
- Initial release.
|
|
35
|
+
- 初期リリース
|
data/Dockerfile
CHANGED
data/README.md
CHANGED
|
@@ -12,40 +12,45 @@ This gem converts HTML to PDF using Firefox's Geckodriver.
|
|
|
12
12
|
### 1. Install
|
|
13
13
|
|
|
14
14
|
1. [Firefox](https://www.firefox.com)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
- Ubuntu
|
|
16
|
+
```bash
|
|
17
|
+
$ apt install -y firefox
|
|
18
|
+
$ apt install -y fonts-noto # recommended
|
|
19
|
+
```
|
|
20
|
+
- Debian
|
|
21
|
+
```bash
|
|
22
|
+
$ apt install -y firefox-esr
|
|
23
|
+
$ apt install -y fonts-noto # recommended
|
|
24
|
+
```
|
|
25
|
+
|
|
23
26
|
2. [geckodriver](https://github.com/mozilla/geckodriver)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
- Linux (Ubuntu / Debian)
|
|
28
|
+
```bash
|
|
29
|
+
$ wget "https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz" -O /tmp/geckodriver.tar.gz
|
|
30
|
+
$ tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin
|
|
31
|
+
```
|
|
32
|
+
|
|
29
33
|
3. gem install
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
- bundler
|
|
35
|
+
```bash
|
|
36
|
+
$ bundle add gkhtmltopdf
|
|
37
|
+
```
|
|
38
|
+
- other
|
|
39
|
+
```bash
|
|
40
|
+
$ gem install gkhtmltopdf
|
|
41
|
+
```
|
|
38
42
|
|
|
39
43
|
---
|
|
40
44
|
|
|
41
45
|
### 2. Using
|
|
42
46
|
|
|
43
|
-
####
|
|
47
|
+
#### Ruby
|
|
44
48
|
|
|
45
49
|
> **⚠️ Security Warning for Web Frameworks (e.g., Ruby on Rails):**
|
|
46
50
|
> If you are accepting URLs from untrusted users, you must implement strict SSRF protection. Do not pass user-input URLs directly without network-level isolation. Please read the [SSRF](#what-is-ssrf) section below for details.
|
|
47
51
|
|
|
48
52
|
```ruby
|
|
53
|
+
require 'gkhtmltopdf'
|
|
49
54
|
# over network
|
|
50
55
|
Gkhtmltopdf.convert('https://example.com', 'example_com.pdf')
|
|
51
56
|
# local file
|
|
@@ -54,7 +59,19 @@ Gkhtmltopdf.convert('file:///foo/bar/test.html', 'local.pdf')
|
|
|
54
59
|
Gkhtmltopdf.convert('https://f6a.net/oss/', 'with_bg.pdf', print_options: {background: true})
|
|
55
60
|
```
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
Additionally, in version 1.0.0 we added the following syntax.
|
|
63
|
+
If you want to generate multiple PDFs faster, use this:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
require 'gkhtmltopdf'
|
|
67
|
+
Gkhtmltopdf.open do |gkh2p|
|
|
68
|
+
gkh2p.save_pdf('https://example.com', 'example_com.pdf')
|
|
69
|
+
gkh2p.save_pdf('file:///foo/bar/test.html', 'local.pdf')
|
|
70
|
+
gkh2p.save_pdf('https://f6a.net/oss/', 'with_bg.pdf', print_options: {background: true})
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Shell
|
|
58
75
|
|
|
59
76
|
```bash
|
|
60
77
|
# over network
|
|
@@ -88,6 +105,33 @@ Attackers could potentially generate PDFs of internal network resources (e.g., `
|
|
|
88
105
|
|
|
89
106
|
---
|
|
90
107
|
|
|
108
|
+
## Errors
|
|
109
|
+
|
|
110
|
+
The following errors inherit `Gkhtmltopdf::Error`, so you can handle them as follows:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
begin
|
|
114
|
+
Gkhtmltopdf.convert('ftp://example.com', 'example_com.pdf')
|
|
115
|
+
rescue Gkhtmltopdf::Error => e
|
|
116
|
+
puts e.class # -> Gkhtmltopdf::URLSchemeInvalid
|
|
117
|
+
puts e.message # -> Invalid URL scheme: (ftp)
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Gkhtmltopdf::PathUnresolvedError
|
|
122
|
+
|
|
123
|
+
Firefox or Geckodriver path unresolved. Follow the installation steps in [1. Install](#1-install) to set up.
|
|
124
|
+
|
|
125
|
+
### Gkhtmltopdf::URLSchemeInvalid
|
|
126
|
+
|
|
127
|
+
Raised when the URL scheme is invalid (e.g., `ftp://`, `about://`) or the hostname is missing (e.g., `f6a.net`).
|
|
128
|
+
|
|
129
|
+
### Gkhtmltopdf::BrowserError
|
|
130
|
+
|
|
131
|
+
Response from Firefox/Geckodriver is not as expected.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
91
135
|
## Acknowledgments & Third-Party Licenses
|
|
92
136
|
|
|
93
137
|
This gem acts as a wrapper and communicates with the following external open-source tools.
|
data/TODO.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
-
## 完了
|
|
3
|
+
## 完了 v0.9.0
|
|
4
4
|
|
|
5
5
|
- [x] 並列処理対応(自動空きportチェック)
|
|
6
6
|
- [x] 印刷オプションの追加
|
|
@@ -8,11 +8,18 @@
|
|
|
8
8
|
- [x] PATHエラー時の表示追加
|
|
9
9
|
- [x] 入力値検証
|
|
10
10
|
- [x] テストカバレッジ100%
|
|
11
|
+
- [x] RubyGemsで公開
|
|
12
|
+
- [x] 多言語対応について
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## 完了 v1.0.0
|
|
15
|
+
|
|
16
|
+
- [x] 複数ファイル&URLの直列実行による高速化
|
|
17
|
+
- [x] エラー処理
|
|
18
|
+
- [x] 起動時の待機時間オプション
|
|
19
|
+
|
|
20
|
+
## 今後検討
|
|
13
21
|
|
|
14
|
-
- [ ] RubyGemsで公開
|
|
15
22
|
- [ ] UA設定機能
|
|
16
|
-
- [ ]
|
|
17
|
-
- [ ]
|
|
18
|
-
- [ ]
|
|
23
|
+
- [ ] ポート範囲設定?
|
|
24
|
+
- [ ] configファイルからオプションを設定?
|
|
25
|
+
- [ ] YARD追加
|
data/exe/gkhtmltopdf
CHANGED
|
@@ -34,10 +34,14 @@ parser = OptionParser.new do |opts|
|
|
|
34
34
|
options[:firefox_path] = v
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
opts.on("--geckodriver-path [PATH]", "
|
|
37
|
+
opts.on("--geckodriver-path [PATH]", "Geckodriver custom PATH") do |v|
|
|
38
38
|
options[:geckodriver_path] = v
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
opts.on("--launch-max-wait-time [NUM]", Integer, "Launch max wait time (approx: NUM * 0.1sec)") do |v|
|
|
42
|
+
options[:wait_time] = v
|
|
43
|
+
end
|
|
44
|
+
|
|
41
45
|
opts.on("-v", "--version", "display version") do
|
|
42
46
|
puts "Gkhtmltopdf version #{Gkhtmltopdf::VERSION}"
|
|
43
47
|
exit
|
|
@@ -69,9 +73,9 @@ begin
|
|
|
69
73
|
init_options = {}
|
|
70
74
|
init_options[:firefox_path] = options.delete(:firefox_path) if options[:firefox_path]
|
|
71
75
|
init_options[:geckodriver_path] = options.delete(:geckodriver_path) if options[:geckodriver_path]
|
|
76
|
+
init_options[:wait_time] = options.delete(:wait_time) if options[:wait_time]
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
converter.convert(input_url, output_path, print_options: options[:print_options])
|
|
78
|
+
Gkhtmltopdf.convert(input_url, output_path, print_options: options[:print_options], **init_options)
|
|
75
79
|
|
|
76
80
|
puts "✅ Completed PDF generation!"
|
|
77
81
|
|
data/lib/errors.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Gkhtmltopdf
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
class PathUnresolvedError < Error
|
|
5
|
+
def initialize(name)
|
|
6
|
+
message = "#{name} is not found. Please ensure #{name} is installed and either in your PATH or specify the path during initialization."
|
|
7
|
+
super(message)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class BrowserError < Error; end
|
|
12
|
+
|
|
13
|
+
class URLSchemeInvalid < Error
|
|
14
|
+
def initialize(url_scheme)
|
|
15
|
+
message = "Invalid URL scheme: (#{url_scheme})"
|
|
16
|
+
super(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -6,35 +6,35 @@ require 'socket'
|
|
|
6
6
|
|
|
7
7
|
module Gkhtmltopdf
|
|
8
8
|
class Converter
|
|
9
|
-
def
|
|
9
|
+
def open(geckodriver_path: nil, firefox_path: nil, wait_time: nil, port: nil)
|
|
10
10
|
@geckodriver_path = resolve_geckodriver_path!(geckodriver_path)
|
|
11
11
|
@firefox_path = resolve_firefox_path!(firefox_path)
|
|
12
12
|
@port = port || get_free_port
|
|
13
13
|
@base_url = "http://127.0.0.1:#{@port}"
|
|
14
|
+
@pid = spawn("#{@geckodriver_path} --port #{@port}", out: File::NULL, err: File::NULL)
|
|
15
|
+
wait_time ||= 20
|
|
16
|
+
wait_for_gk(wait_time)
|
|
17
|
+
create_session!
|
|
14
18
|
end
|
|
15
19
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
pid = spawn("#{@geckodriver_path} --port #{@port}", out: File::NULL, err: File::NULL)
|
|
20
|
-
wait_for_server
|
|
21
|
-
|
|
22
|
-
session_id = nil
|
|
20
|
+
def close
|
|
21
|
+
delete_session! if @session_id
|
|
23
22
|
begin
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pdf_base64 = print_pdf(session_id, print_options)
|
|
28
|
-
File.binwrite(output_path, Base64.decode64(pdf_base64))
|
|
29
|
-
ensure
|
|
30
|
-
delete_session(session_id) if session_id
|
|
31
|
-
begin
|
|
32
|
-
Process.kill('TERM', pid)
|
|
33
|
-
Process.wait(pid)
|
|
34
|
-
rescue Errno::ESRCH, Errno::ECHILD
|
|
35
|
-
# nothing to do if the process is already terminated
|
|
23
|
+
unless @pid.nil?
|
|
24
|
+
Process.kill('TERM', @pid)
|
|
25
|
+
Process.wait(@pid)
|
|
36
26
|
end
|
|
27
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
28
|
+
# nothing to do if the process is already terminated
|
|
37
29
|
end
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def save_pdf(url, output_path, print_options: {})
|
|
34
|
+
validate_url_scheme!(url)
|
|
35
|
+
navigate(url)
|
|
36
|
+
pdf_base64 = print_pdf(print_options)
|
|
37
|
+
File.binwrite(output_path, Base64.decode64(pdf_base64))
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
private
|
|
@@ -49,7 +49,7 @@ module Gkhtmltopdf
|
|
|
49
49
|
def resolve_geckodriver_path!(provided_path)
|
|
50
50
|
path = provided_path || find_default_geckodriver
|
|
51
51
|
unless path
|
|
52
|
-
raise
|
|
52
|
+
raise PathUnresolvedError, 'Geckodriver'
|
|
53
53
|
end
|
|
54
54
|
path
|
|
55
55
|
end
|
|
@@ -57,7 +57,7 @@ module Gkhtmltopdf
|
|
|
57
57
|
def resolve_firefox_path!(provided_path)
|
|
58
58
|
path = provided_path || find_default_firefox
|
|
59
59
|
unless path
|
|
60
|
-
raise
|
|
60
|
+
raise PathUnresolvedError, 'Firefox'
|
|
61
61
|
end
|
|
62
62
|
path
|
|
63
63
|
end
|
|
@@ -89,16 +89,16 @@ module Gkhtmltopdf
|
|
|
89
89
|
common_paths.find { |path| File.executable?(path) && !File.directory?(path) }
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
def
|
|
93
|
-
|
|
92
|
+
def wait_for_gk(num)
|
|
93
|
+
num.times do
|
|
94
94
|
begin
|
|
95
95
|
Net::HTTP.get(URI("#{@base_url}/status"))
|
|
96
96
|
return
|
|
97
97
|
rescue Errno::ECONNREFUSED
|
|
98
|
-
sleep 0.
|
|
98
|
+
sleep 0.1
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
|
-
raise
|
|
101
|
+
raise BrowserError, "Failed to launch geckodriver (port #{@port})"
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def post(path, payload)
|
|
@@ -110,11 +110,11 @@ module Gkhtmltopdf
|
|
|
110
110
|
begin
|
|
111
111
|
JSON.parse(res.body)
|
|
112
112
|
rescue JSON::ParserError
|
|
113
|
-
raise
|
|
113
|
+
raise BrowserError, "Invalid json response (Status: #{res.code}): #{res.body}"
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def create_session
|
|
117
|
+
def create_session!
|
|
118
118
|
firefox_options = { args: ["-headless"] }
|
|
119
119
|
firefox_options[:binary] = @firefox_path if @firefox_path != 'firefox'
|
|
120
120
|
|
|
@@ -129,16 +129,16 @@ module Gkhtmltopdf
|
|
|
129
129
|
|
|
130
130
|
response = post("/session", payload)
|
|
131
131
|
value = response["value"]
|
|
132
|
-
raise
|
|
132
|
+
raise BrowserError, "Failed to create session: #{value}" if value["error"]
|
|
133
133
|
|
|
134
|
-
value["sessionId"]
|
|
134
|
+
@session_id = value["sessionId"]
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
def navigate(
|
|
138
|
-
post("/session/#{session_id}/url", { url: url })
|
|
137
|
+
def navigate(url)
|
|
138
|
+
post("/session/#{@session_id}/url", { url: url })
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
def print_pdf(
|
|
141
|
+
def print_pdf(user_options)
|
|
142
142
|
default_options = {
|
|
143
143
|
background: false,
|
|
144
144
|
shrinkToFit: true,
|
|
@@ -149,25 +149,26 @@ module Gkhtmltopdf
|
|
|
149
149
|
|
|
150
150
|
payload = default_options.merge(user_options)
|
|
151
151
|
|
|
152
|
-
response = post("/session/#{session_id}/print", payload)
|
|
152
|
+
response = post("/session/#{@session_id}/print", payload)
|
|
153
153
|
value = response["value"]
|
|
154
|
-
raise
|
|
154
|
+
raise BrowserError, "Failed to generate PDF: #{value}" if value["error"]
|
|
155
155
|
|
|
156
156
|
value
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
def delete_session
|
|
160
|
-
uri = URI("#{@base_url}/session/#{session_id}")
|
|
159
|
+
def delete_session!
|
|
160
|
+
uri = URI("#{@base_url}/session/#{@session_id}")
|
|
161
161
|
req = Net::HTTP::Delete.new(uri)
|
|
162
162
|
Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
|
|
163
|
+
@session_id = nil
|
|
163
164
|
end
|
|
164
165
|
|
|
165
166
|
def validate_url_scheme!(url_string)
|
|
166
167
|
parsed_url = URI.parse(url_string)
|
|
167
168
|
allowed_schemes = ['http', 'https', 'file']
|
|
168
|
-
raise
|
|
169
|
+
raise URLSchemeInvalid, nil if parsed_url.scheme.nil?
|
|
169
170
|
unless allowed_schemes.include?(parsed_url.scheme)
|
|
170
|
-
raise
|
|
171
|
+
raise URLSchemeInvalid, parsed_url.scheme
|
|
171
172
|
end
|
|
172
173
|
end
|
|
173
174
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Gkhtmltopdf
|
|
2
|
+
class DSL
|
|
3
|
+
def initialize
|
|
4
|
+
@converter = Converter.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def open(options)
|
|
8
|
+
@converter.open(**options)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def close
|
|
12
|
+
@converter.close
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save_pdf(url, output_path, print_options: {})
|
|
16
|
+
@converter.save_pdf(url, output_path, print_options: print_options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/gkhtmltopdf/version.rb
CHANGED
data/lib/gkhtmltopdf.rb
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative 'gkhtmltopdf/version'
|
|
4
|
+
require_relative 'gkhtmltopdf/converter'
|
|
5
|
+
require_relative 'gkhtmltopdf/dsl'
|
|
6
|
+
require_relative 'errors'
|
|
5
7
|
|
|
6
8
|
module Gkhtmltopdf
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
converter
|
|
11
|
-
|
|
9
|
+
def self.convert(url, output_path, geckodriver_path: nil, firefox_path: nil, wait_time: nil, port: nil, print_options: {})
|
|
10
|
+
converter = DSL.new
|
|
11
|
+
converter.open(geckodriver_path: geckodriver_path, firefox_path: firefox_path, wait_time: wait_time, port: port)
|
|
12
|
+
converter.save_pdf(url, output_path, print_options: print_options)
|
|
13
|
+
ensure
|
|
14
|
+
converter.close
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.open(geckodriver_path: nil, firefox_path: nil, wait_time: nil, port: nil, &block)
|
|
18
|
+
converter = DSL.new
|
|
19
|
+
converter.open(geckodriver_path: geckodriver_path, firefox_path: firefox_path, wait_time: wait_time, port: port)
|
|
20
|
+
yield converter
|
|
21
|
+
ensure
|
|
22
|
+
converter.close
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'gkhtmltopdf'
|
|
3
2
|
|
|
4
3
|
RSpec.describe Gkhtmltopdf::Converter do
|
|
5
|
-
let(:converter) { Gkhtmltopdf::Converter.
|
|
4
|
+
let(:converter) { Gkhtmltopdf::Converter.new }
|
|
5
|
+
after { converter.close }
|
|
6
|
+
describe '#open' do
|
|
7
|
+
subject { converter.open }
|
|
8
|
+
it {
|
|
9
|
+
subject
|
|
10
|
+
expect(converter.instance_variable_get(:@geckodriver_path)).to be_a(String)
|
|
11
|
+
expect(converter.instance_variable_get(:@firefox_path)).to be_a(String)
|
|
12
|
+
expect(converter.instance_variable_get(:@port)).to be_a(Integer)
|
|
13
|
+
expect(converter.instance_variable_get(:@base_url)).to be_a(String)
|
|
14
|
+
expect(converter.instance_variable_get(:@pid)).to be_a(Integer)
|
|
15
|
+
expect(converter.instance_variable_get(:@session_id)).to be_a(String)
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
describe '#save_pdf' do
|
|
19
|
+
before { converter.open }
|
|
20
|
+
subject { converter.save_pdf(url, output_path) }
|
|
21
|
+
let(:url) { "file://#{file_fixture('test.html')}" }
|
|
22
|
+
let(:output_path) { File.join(Dir.mktmpdir, 'output.pdf') }
|
|
23
|
+
it {
|
|
24
|
+
expect { subject }.to change { Dir.glob(output_path).count }.from(0).to(1)
|
|
25
|
+
expect(File.binread(output_path)).to include('/FontName')
|
|
26
|
+
}
|
|
27
|
+
end
|
|
6
28
|
describe '#resolve_geckodriver_path!' do
|
|
7
29
|
subject { converter.send(:resolve_geckodriver_path!, nil) }
|
|
8
30
|
context 'geckodriver is not available' do
|
|
9
31
|
before { allow(File).to receive(:executable?).and_return(false) }
|
|
10
32
|
it 'raises an error' do
|
|
11
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
33
|
+
expect { subject }.to raise_error(Gkhtmltopdf::PathUnresolvedError, /\AGeckodriver is not found./)
|
|
12
34
|
end
|
|
13
35
|
end
|
|
14
36
|
end
|
|
@@ -18,29 +40,45 @@ RSpec.describe Gkhtmltopdf::Converter do
|
|
|
18
40
|
context 'firefox is not available' do
|
|
19
41
|
before { allow(File).to receive(:executable?).and_return(false) }
|
|
20
42
|
it 'raises an error' do
|
|
21
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
43
|
+
expect { subject }.to raise_error(Gkhtmltopdf::PathUnresolvedError, /\AFirefox is not found./)
|
|
22
44
|
end
|
|
23
45
|
end
|
|
24
46
|
end
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
|
|
48
|
+
describe '#wait_for_gk' do
|
|
49
|
+
subject { converter.send(:wait_for_gk, 0) }
|
|
27
50
|
context 'fail launch geckodriver' do
|
|
28
51
|
before { allow(Net::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED, 'Dummy error') }
|
|
29
52
|
it 'raises an error' do
|
|
30
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
53
|
+
expect { subject }.to raise_error(Gkhtmltopdf::BrowserError, /\AFailed to launch geckodriver \(port \)\Z/)
|
|
31
54
|
end
|
|
32
55
|
end
|
|
33
56
|
end
|
|
57
|
+
|
|
34
58
|
describe '#post' do
|
|
35
|
-
let(:converter) { Gkhtmltopdf::Converter.new }
|
|
36
59
|
subject { converter.send(:post, '/dummy', {test: :value}) }
|
|
37
60
|
context 'Invalid json response from geckodriver' do
|
|
38
61
|
before {
|
|
62
|
+
converter.instance_variable_set(:@base_url, 'http://test')
|
|
39
63
|
allow(Net::HTTP).to receive(:start).and_return(Struct.new(:code, :body).new('200', 'invalid_json: 0123'))
|
|
40
64
|
}
|
|
41
65
|
it 'raises an error' do
|
|
42
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
66
|
+
expect { subject }.to raise_error(Gkhtmltopdf::BrowserError, 'Invalid json response (Status: 200): invalid_json: 0123')
|
|
43
67
|
end
|
|
44
68
|
end
|
|
45
69
|
end
|
|
70
|
+
|
|
71
|
+
describe '#validate_url_scheme!' do
|
|
72
|
+
subject { converter.send(:validate_url_scheme!, url_string) }
|
|
73
|
+
let(:url_string) { 'http://f6a.net' }
|
|
74
|
+
it { expect { subject }.not_to raise_error }
|
|
75
|
+
context 'scheme is nil' do
|
|
76
|
+
let(:url_string) { 'f6a.net' }
|
|
77
|
+
it { expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: ()') }
|
|
78
|
+
end
|
|
79
|
+
context 'invalid scheme' do
|
|
80
|
+
let(:url_string) { 'about://version' }
|
|
81
|
+
it { expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (about)') }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
46
84
|
end
|
data/spec/gkhtmltopdf_spec.rb
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'gkhtmltopdf'
|
|
3
|
-
require 'tmpdir'
|
|
4
|
-
require 'base64'
|
|
5
2
|
|
|
6
3
|
RSpec.describe Gkhtmltopdf do
|
|
7
4
|
describe '.convert' do
|
|
8
5
|
let(:url) { 'https://f6a.net/oss/' }
|
|
9
6
|
let(:output) { File.join(Dir.mktmpdir, 'output.pdf') }
|
|
10
|
-
let(:hash) { {} }
|
|
11
7
|
|
|
12
|
-
subject { Gkhtmltopdf.convert(url, output
|
|
8
|
+
subject { Gkhtmltopdf.convert(url, output) }
|
|
13
9
|
|
|
14
10
|
it 'successful conversion' do
|
|
15
11
|
expect { subject }.not_to raise_error
|
|
@@ -18,7 +14,29 @@ RSpec.describe Gkhtmltopdf do
|
|
|
18
14
|
context 'invalid URL' do
|
|
19
15
|
let(:url) { 'ftp://example.com' }
|
|
20
16
|
it 'raises an error' do
|
|
21
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
17
|
+
expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (ftp)')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
describe '.open' do
|
|
22
|
+
let(:url) { 'https://f6a.net/oss/' }
|
|
23
|
+
let(:output_path) { Dir.mktmpdir }
|
|
24
|
+
|
|
25
|
+
subject do
|
|
26
|
+
Gkhtmltopdf.open do |gk|
|
|
27
|
+
(1..3).each { |n| gk.save_pdf("#{url}?test=#{n}", File.join(output_path, "#{n}.pdf")) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'successful conversion' do
|
|
32
|
+
expect { subject }.to change { Dir.glob(File.join(output_path, '*.pdf')).count }.from(0).to(3)
|
|
33
|
+
expect { subject }.not_to raise_error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'invalid URL' do
|
|
37
|
+
let(:url) { 'ftp://example.com' }
|
|
38
|
+
it 'raises an error' do
|
|
39
|
+
expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (ftp)')
|
|
22
40
|
end
|
|
23
41
|
end
|
|
24
42
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -10,6 +10,15 @@ SimpleCov.start do
|
|
|
10
10
|
)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
require 'gkhtmltopdf'
|
|
14
|
+
require 'tmpdir'
|
|
15
|
+
require 'base64'
|
|
16
|
+
module FileFixtureHelper
|
|
17
|
+
def file_fixture(filename)
|
|
18
|
+
File.join(File.expand_path('fixtures', __dir__), filename)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
13
22
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
14
23
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
|
15
24
|
# this file to always be loaded, without a need to explicitly require it in any
|
|
@@ -104,4 +113,6 @@ RSpec.configure do |config|
|
|
|
104
113
|
# # test failures related to randomization by passing the same `--seed` value
|
|
105
114
|
# # as the one that triggered the failure.
|
|
106
115
|
# Kernel.srand config.seed
|
|
116
|
+
|
|
117
|
+
config.include FileFixtureHelper
|
|
107
118
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gkhtmltopdf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kazuki Sakane
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: base64
|
|
@@ -85,8 +84,10 @@ files:
|
|
|
85
84
|
- Rakefile
|
|
86
85
|
- TODO.md
|
|
87
86
|
- exe/gkhtmltopdf
|
|
87
|
+
- lib/errors.rb
|
|
88
88
|
- lib/gkhtmltopdf.rb
|
|
89
89
|
- lib/gkhtmltopdf/converter.rb
|
|
90
|
+
- lib/gkhtmltopdf/dsl.rb
|
|
90
91
|
- lib/gkhtmltopdf/version.rb
|
|
91
92
|
- spec/fixtures/test.html
|
|
92
93
|
- spec/gkhtmltopdf/converter_spec.rb
|
|
@@ -98,6 +99,7 @@ licenses:
|
|
|
98
99
|
metadata:
|
|
99
100
|
homepage_uri: https://f6a.net/oss/
|
|
100
101
|
source_code_uri: https://github.com/fantasia-tech/gkhtmltopdf-rb
|
|
102
|
+
changelog_uri: https://github.com/fantasia-tech/gkhtmltopdf-rb/blob/main/CHANGELOG.md
|
|
101
103
|
post_install_message: "=====================================================================\nGkhtmltopdf
|
|
102
104
|
has been installed successfully. \U0001F389\n\n⚠️ Caution\nRequired: To run this
|
|
103
105
|
gem, you need to have `firefox` and `geckodriver` installed and added to your PATH.\n\ncheck
|
|
@@ -119,8 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
121
|
requirements:
|
|
120
122
|
- Firefox
|
|
121
123
|
- Geckodriver
|
|
122
|
-
rubygems_version:
|
|
123
|
-
signing_key:
|
|
124
|
+
rubygems_version: 4.0.8
|
|
124
125
|
specification_version: 4
|
|
125
126
|
summary: Gkhtmltopdf is mean Gecko HTML to PDF converter.
|
|
126
127
|
test_files: []
|