gkhtmltopdf 0.9.0 → 1.1.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 +22 -0
- data/README.md +94 -25
- data/TODO.md +25 -7
- data/{Dockerfile → dockerfiles/Dockerfile.debian13-ruby32} +5 -0
- data/dockerfiles/Dockerfile.ubuntu24-ruby32 +28 -0
- data/docs/ForDeveloper.md +17 -0
- data/exe/gkhtmltopdf +16 -7
- data/lib/errors.rb +21 -0
- data/lib/gkhtmltopdf/converter.rb +66 -41
- data/lib/gkhtmltopdf/dsl.rb +21 -0
- data/lib/gkhtmltopdf/version.rb +1 -1
- data/lib/gkhtmltopdf.rb +18 -7
- data/spec/gkhtmltopdf/converter_spec.rb +51 -9
- data/spec/gkhtmltopdf_spec.rb +28 -7
- data/spec/spec_helper.rb +11 -0
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 693828b2a9f004f9038b744c5d4a527a46f27ca55ad7c3d4638478aa2a747ade
|
|
4
|
+
data.tar.gz: 418727b131d4305641bcb66df639c961aabc73de334fd599b06d772f2b0557ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8776f073004b0e5dba496316e4c95e18121784be2430b12f679dc900cfee41e7002befda8fe022f30739e8bdf33c76ce7c51cd625ed5e5095cc95f4eb60b4d3f
|
|
7
|
+
data.tar.gz: 3df53840690e009d5c39a18710867505eff22e0bbb0d6711dfc6f8cc44a73442c02f1d6f5b5c1c3ac9f5c5c327f61d377bbab9a73dafea0124ff4a20e378426b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,18 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
All noteworthy changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.1.0 / 2026-06-29
|
|
6
|
+
|
|
7
|
+
- UA設定機能の追加
|
|
8
|
+
- mktmpdirの明示的な削除
|
|
9
|
+
- ubuntuでの動かし方とか修正
|
|
10
|
+
|
|
11
|
+
## 1.0.0 / 2026-03-19
|
|
12
|
+
|
|
13
|
+
- Added serial processing for multiple files and URLs.
|
|
14
|
+
- 複数ファイル&URLの直列処理追加
|
|
15
|
+
- Separated the Gkhtmltopdf::Error into many.
|
|
16
|
+
- エラーを分離
|
|
17
|
+
- Add and Fixed test.
|
|
18
|
+
- テスト追加&修正
|
|
19
|
+
|
|
5
20
|
## 0.9.0 / 2026-03-13
|
|
6
21
|
|
|
7
22
|
- Achieved 100% test coverage.🎉
|
|
23
|
+
- テストカバレッジ100%!
|
|
8
24
|
- Fixed github url.
|
|
25
|
+
- github URL 修正
|
|
9
26
|
|
|
10
27
|
## 0.8.0 / 2026-03-10
|
|
11
28
|
|
|
12
29
|
- Added parallel processing support with automatic free port checking.
|
|
30
|
+
- 空きポートの処理追加による並列実行追加
|
|
13
31
|
- Added print options.
|
|
32
|
+
- プリントオプションの追加
|
|
14
33
|
- Added Dockerfile (Debian compatibility verified).
|
|
34
|
+
- Dockerfile追加(Debian互換検証)
|
|
15
35
|
- Added error message display when PATH errors occur.
|
|
36
|
+
- PATH関連のエラーメッセージ追加
|
|
16
37
|
|
|
17
38
|
## 0.1.0 / 2026-01-09
|
|
18
39
|
|
|
19
40
|
- Initial release.
|
|
41
|
+
- 初期リリース
|
data/README.md
CHANGED
|
@@ -5,6 +5,11 @@ Gkhtmltopdf is mean Gecko HTML to PDF converter.
|
|
|
5
5
|
Developed as an alternative to wkhtmltopdf.
|
|
6
6
|
This gem converts HTML to PDF using Firefox's Geckodriver.
|
|
7
7
|
|
|
8
|
+
[](https://badge.fury.io/rb/gkhtmltopdf)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
8
13
|
---
|
|
9
14
|
|
|
10
15
|
## How to
|
|
@@ -12,49 +17,78 @@ This gem converts HTML to PDF using Firefox's Geckodriver.
|
|
|
12
17
|
### 1. Install
|
|
13
18
|
|
|
14
19
|
1. [Firefox](https://www.firefox.com)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
- Ubuntu
|
|
21
|
+
> The snap does not work correctly, so please install it from the [official source](https://support.mozilla.org/en-US/kb/install-firefox-linux).
|
|
22
|
+
```bash
|
|
23
|
+
$ wget -q https://packages.mozilla.org/apt/repo-signing-key.gpg -O- | sudo tee /etc/apt/keyrings/packages.mozilla.org.asc > /dev/null
|
|
24
|
+
$ echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" | tee -a /etc/apt/sources.list.d/mozilla.list > /dev/null
|
|
25
|
+
$ tee /etc/apt/preferences.d/mozilla > /dev/null << EOF
|
|
26
|
+
Package: *
|
|
27
|
+
Pin: origin packages.mozilla.org
|
|
28
|
+
Pin-Priority: 1000
|
|
29
|
+
EOF
|
|
30
|
+
$ apt install -y firefox
|
|
31
|
+
$ apt install -y fonts-noto # recommended
|
|
32
|
+
```
|
|
33
|
+
- Debian
|
|
34
|
+
```bash
|
|
35
|
+
$ apt install -y firefox-esr
|
|
36
|
+
$ apt install -y fonts-noto # recommended
|
|
37
|
+
```
|
|
38
|
+
|
|
23
39
|
2. [geckodriver](https://github.com/mozilla/geckodriver)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
3. gem install
|
|
30
|
-
- bundler
|
|
31
|
-
```bash
|
|
32
|
-
$ bundle add gkhtmltopdf
|
|
33
|
-
```
|
|
34
|
-
- other
|
|
35
|
-
```bash
|
|
36
|
-
$ gem install gkhtmltopdf
|
|
37
|
-
```
|
|
40
|
+
- Linux (Ubuntu / Debian)
|
|
41
|
+
```bash
|
|
42
|
+
$ wget "https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz" -O /tmp/geckodriver.tar.gz
|
|
43
|
+
$ tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin
|
|
44
|
+
```
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
3. gem install
|
|
47
|
+
- bundler
|
|
48
|
+
```bash
|
|
49
|
+
$ bundle add gkhtmltopdf
|
|
50
|
+
```
|
|
51
|
+
- other
|
|
52
|
+
```bash
|
|
53
|
+
$ gem install gkhtmltopdf
|
|
54
|
+
```
|
|
40
55
|
|
|
41
56
|
### 2. Using
|
|
42
57
|
|
|
43
|
-
####
|
|
58
|
+
#### Ruby
|
|
44
59
|
|
|
45
60
|
> **⚠️ Security Warning for Web Frameworks (e.g., Ruby on Rails):**
|
|
46
61
|
> 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
62
|
|
|
48
63
|
```ruby
|
|
64
|
+
require 'gkhtmltopdf'
|
|
49
65
|
# over network
|
|
50
66
|
Gkhtmltopdf.convert('https://example.com', 'example_com.pdf')
|
|
51
67
|
# local file
|
|
52
68
|
Gkhtmltopdf.convert('file:///foo/bar/test.html', 'local.pdf')
|
|
53
69
|
# with option (print background)
|
|
54
70
|
Gkhtmltopdf.convert('https://f6a.net/oss/', 'with_bg.pdf', print_options: {background: true})
|
|
71
|
+
# with option (set custom user-agent)
|
|
72
|
+
Gkhtmltopdf.convert('https://f6a.net/oss/', 'ua.pdf', user_agent: 'YOUR USER AGENT')
|
|
55
73
|
```
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
Additionally, in version 1.0.0 we added the following syntax.
|
|
76
|
+
If you want to generate multiple PDFs faster, use this:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
require 'gkhtmltopdf'
|
|
80
|
+
Gkhtmltopdf.open do |gkh2p|
|
|
81
|
+
gkh2p.save_pdf('https://example.com', 'example_com.pdf')
|
|
82
|
+
gkh2p.save_pdf('file:///foo/bar/test.html', 'local.pdf')
|
|
83
|
+
gkh2p.save_pdf('https://f6a.net/oss/', 'with_bg.pdf', print_options: {background: true})
|
|
84
|
+
end
|
|
85
|
+
# set custom user-agent
|
|
86
|
+
Gkhtmltopdf.open(user_agent: 'YOUR USER AGENT') do |gkh2p|
|
|
87
|
+
gkh2p.save_pdf('https://f6a.net/oss/', 'ua.pdf')
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Shell
|
|
58
92
|
|
|
59
93
|
```bash
|
|
60
94
|
# over network
|
|
@@ -63,6 +97,8 @@ $ gkhtmltopdf https://example.com/ example_com.pdf
|
|
|
63
97
|
$ gkhtmltopdf /foo/bar/test.html local.pdf
|
|
64
98
|
# with option (print background)
|
|
65
99
|
$ gkhtmltopdf https://f6a.net/oss/ with_bg.pdf --background
|
|
100
|
+
# with option (set custom user-agent)
|
|
101
|
+
$ gkhtmltopdf https://f6a.net/oss/ ua.pdf --user-agent "YOUR USER AGENT"
|
|
66
102
|
# other option
|
|
67
103
|
$ gkhtmltopdf --help
|
|
68
104
|
```
|
|
@@ -88,6 +124,39 @@ Attackers could potentially generate PDFs of internal network resources (e.g., `
|
|
|
88
124
|
|
|
89
125
|
---
|
|
90
126
|
|
|
127
|
+
## Expected Errors
|
|
128
|
+
|
|
129
|
+
The following errors inherit `Gkhtmltopdf::Error`, so you can handle them as follows:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
begin
|
|
133
|
+
Gkhtmltopdf.convert('ftp://example.com', 'example_com.pdf')
|
|
134
|
+
rescue Gkhtmltopdf::Error => e
|
|
135
|
+
puts e.class # -> Gkhtmltopdf::URLSchemeInvalid
|
|
136
|
+
puts e.message # -> Invalid URL scheme: (ftp)
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Gkhtmltopdf::PathUnresolvedError
|
|
141
|
+
|
|
142
|
+
Firefox or Geckodriver path unresolved. Follow the installation steps in [1. Install](#1-install) to set up.
|
|
143
|
+
|
|
144
|
+
### Gkhtmltopdf::URLSchemeInvalid
|
|
145
|
+
|
|
146
|
+
Raised when the URL scheme is invalid (e.g., `ftp://`, `about://`) or the hostname is missing (e.g., `f6a.net`).
|
|
147
|
+
|
|
148
|
+
### Gkhtmltopdf::BrowserError
|
|
149
|
+
|
|
150
|
+
Response from Firefox/Geckodriver is not as expected.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Documents
|
|
155
|
+
|
|
156
|
+
- [ForDeveloper](/docs/ForDeveloper.md)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
91
160
|
## Acknowledgments & Third-Party Licenses
|
|
92
161
|
|
|
93
162
|
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,29 @@
|
|
|
8
8
|
- [x] PATHエラー時の表示追加
|
|
9
9
|
- [x] 入力値検証
|
|
10
10
|
- [x] テストカバレッジ100%
|
|
11
|
+
- [x] RubyGemsで公開
|
|
12
|
+
- [x] 多言語対応について
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## 完了 v1.0.0
|
|
13
15
|
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
- [x] 複数ファイル&URLの直列実行による高速化
|
|
17
|
+
- [x] エラー処理
|
|
18
|
+
- [x] 起動時の待機時間オプション
|
|
19
|
+
|
|
20
|
+
## 完了 v1.1.0
|
|
21
|
+
|
|
22
|
+
- [x] UA設定機能の追加
|
|
23
|
+
- [x] mktmpdirの明示的な削除
|
|
24
|
+
|
|
25
|
+
## つぎ v1.2.0
|
|
26
|
+
|
|
27
|
+
- [ ] 設定のStruct化?(default切り出し)
|
|
28
|
+
- [ ] pdfをsaveせずバイナリを直接返却するメソッドの追加
|
|
29
|
+
- [ ] 肥大化したconvert.rbの分割( `Gkhtmltopdf::PDF` とかつくるか)
|
|
30
|
+
|
|
31
|
+
## 検討中
|
|
32
|
+
|
|
33
|
+
- [ ] ポート範囲設定?
|
|
34
|
+
- [ ] YARD追加
|
|
35
|
+
- [ ] FireFox timeout
|
|
36
|
+
- [ ] configファイルからオプションを設定?
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
# escape=\
|
|
2
|
+
# syntax=docker/dockerfile:1.7
|
|
1
3
|
FROM ruby:3.2-slim
|
|
2
4
|
|
|
3
5
|
RUN apt-get update
|
|
4
6
|
RUN apt-get install -y git wget xz-utils build-essential libyaml-dev
|
|
5
7
|
|
|
8
|
+
# Install Noto Fonts
|
|
9
|
+
RUN apt-get install -y fonts-noto
|
|
10
|
+
|
|
6
11
|
# Install Firefox
|
|
7
12
|
RUN apt-get install -y firefox-esr
|
|
8
13
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
FROM ubuntu:noble
|
|
2
|
+
|
|
3
|
+
RUN apt-get update
|
|
4
|
+
RUN apt-get install -y ruby3.2 bundler
|
|
5
|
+
RUN apt-get install -y git wget xz-utils build-essential libyaml-dev
|
|
6
|
+
|
|
7
|
+
# Install Noto Fonts
|
|
8
|
+
RUN apt-get install -y fonts-noto
|
|
9
|
+
|
|
10
|
+
# Install Firefox
|
|
11
|
+
RUN wget -q https://packages.mozilla.org/apt/repo-signing-key.gpg -O- | tee /etc/apt/keyrings/packages.mozilla.org.asc > /dev/null
|
|
12
|
+
RUN echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" | tee -a /etc/apt/sources.list.d/mozilla.list > /dev/null
|
|
13
|
+
RUN tee /etc/apt/preferences.d/mozilla > /dev/null << EOF
|
|
14
|
+
Package: *
|
|
15
|
+
Pin: origin packages.mozilla.org
|
|
16
|
+
Pin-Priority: 1000
|
|
17
|
+
EOF
|
|
18
|
+
RUN apt-get update
|
|
19
|
+
RUN apt-get install -y firefox
|
|
20
|
+
|
|
21
|
+
# Install Geckodriver
|
|
22
|
+
RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz" -O geckodriver.tar.gz
|
|
23
|
+
RUN tar -xzf geckodriver.tar.gz -C /usr/local/bin
|
|
24
|
+
|
|
25
|
+
COPY . /app
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
RUN bundle install
|
|
28
|
+
CMD ["bundle", "exec", "rspec"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# for Developer
|
|
2
|
+
|
|
3
|
+
## Test
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
$ docker build -f ./dockerfiles/Dockerfile.debian13-ruby32 . -t gkhtmltopdf-d13r32
|
|
7
|
+
$ docker build -f ./dockerfiles/Dockerfile.ubuntu24-ruby32 . -t gkhtmltopdf-u24r32
|
|
8
|
+
$ docker run --rm gkhtmltopdf-d13r32
|
|
9
|
+
$ docker run --rm gkhtmltopdf-u24r32
|
|
10
|
+
$ docker rmi gkhtmltopdf-d13r32
|
|
11
|
+
$ docker rmi gkhtmltopdf-u24r32
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Build
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
```
|
data/exe/gkhtmltopdf
CHANGED
|
@@ -12,32 +12,40 @@ options = {
|
|
|
12
12
|
parser = OptionParser.new do |opts|
|
|
13
13
|
opts.banner = "Usage: gkhtmltopdf [options] <URL_OR_FILE> <OUTPUT_PDF>"
|
|
14
14
|
|
|
15
|
-
opts.on("-O", "--orientation [
|
|
15
|
+
opts.on("-O", "--orientation ORIENTATION", ["portrait", "landscape"], "ORIENTATION portrait or landscape (default: portrait)") do |v|
|
|
16
16
|
options[:print_options][:orientation] = v.downcase
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
opts.on("--user-agent USERAGENT", String, "Browser custom USERAGENT") do |v|
|
|
20
|
+
options[:user_agent] = v
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
opts.on("--background", "Print background") do
|
|
20
24
|
options[:print_options][:background] = true
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
opts.on("--margin-top
|
|
27
|
+
opts.on("--margin-top CM", Float, "margin top CM") do |v|
|
|
24
28
|
options[:print_options][:margin] ||= {}
|
|
25
29
|
options[:print_options][:margin][:top] = v
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
opts.on("--margin-bottom
|
|
32
|
+
opts.on("--margin-bottom CM", Float, "margin bottom CM") do |v|
|
|
29
33
|
options[:print_options][:margin] ||= {}
|
|
30
34
|
options[:print_options][:margin][:bottom] = v
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
opts.on("--firefox-path
|
|
37
|
+
opts.on("--firefox-path PATH", String, "Firefox custom PATH") do |v|
|
|
34
38
|
options[:firefox_path] = v
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
opts.on("--geckodriver-path
|
|
41
|
+
opts.on("--geckodriver-path PATH", String, "Geckodriver custom PATH") do |v|
|
|
38
42
|
options[:geckodriver_path] = v
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
opts.on("--launch-max-wait-time NUM", Integer, "Launch max wait time (approx: NUM * 0.1sec)") do |v|
|
|
46
|
+
options[:wait_time] = v
|
|
47
|
+
end
|
|
48
|
+
|
|
41
49
|
opts.on("-v", "--version", "display version") do
|
|
42
50
|
puts "Gkhtmltopdf version #{Gkhtmltopdf::VERSION}"
|
|
43
51
|
exit
|
|
@@ -69,9 +77,10 @@ begin
|
|
|
69
77
|
init_options = {}
|
|
70
78
|
init_options[:firefox_path] = options.delete(:firefox_path) if options[:firefox_path]
|
|
71
79
|
init_options[:geckodriver_path] = options.delete(:geckodriver_path) if options[:geckodriver_path]
|
|
80
|
+
init_options[:wait_time] = options.delete(:wait_time) if options[:wait_time]
|
|
81
|
+
init_options[:user_agent] = options.delete(:user_agent) if options[:user_agent]
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
converter.convert(input_url, output_path, print_options: options[:print_options])
|
|
83
|
+
Gkhtmltopdf.convert(input_url, output_path, print_options: options[:print_options], **init_options)
|
|
75
84
|
|
|
76
85
|
puts "✅ Completed PDF generation!"
|
|
77
86
|
|
data/lib/errors.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gkhtmltopdf
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class PathUnresolvedError < Error
|
|
7
|
+
def initialize(name)
|
|
8
|
+
message = "#{name} is not found. Please ensure #{name} is installed and either in your PATH or specify the path during initialization."
|
|
9
|
+
super(message)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class BrowserError < Error; end
|
|
14
|
+
|
|
15
|
+
class URLSchemeInvalid < Error
|
|
16
|
+
def initialize(url_scheme)
|
|
17
|
+
message = "Invalid URL scheme: (#{url_scheme})"
|
|
18
|
+
super(message)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,40 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'net/http'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'base64'
|
|
4
6
|
require 'uri'
|
|
5
7
|
require 'socket'
|
|
8
|
+
require 'tmpdir'
|
|
9
|
+
require 'fileutils'
|
|
6
10
|
|
|
7
11
|
module Gkhtmltopdf
|
|
8
12
|
class Converter
|
|
9
|
-
|
|
13
|
+
DEFAULT_FX_USER_AGENT = "gkhtmltopdf-rb(v#{VERSION}) by firefox and gecko".freeze
|
|
14
|
+
|
|
15
|
+
def open(geckodriver_path: nil, firefox_path: nil, wait_time: nil, port: nil, user_agent: nil)
|
|
10
16
|
@geckodriver_path = resolve_geckodriver_path!(geckodriver_path)
|
|
11
17
|
@firefox_path = resolve_firefox_path!(firefox_path)
|
|
12
18
|
@port = port || get_free_port
|
|
13
19
|
@base_url = "http://127.0.0.1:#{@port}"
|
|
20
|
+
@pid = spawn("#{@geckodriver_path} --port #{@port}", out: File::NULL, err: File::NULL)
|
|
21
|
+
wait_time ||= 20
|
|
22
|
+
@profile_path = gen_tmp_profile(user_agent)
|
|
23
|
+
wait_for_gk(wait_time)
|
|
24
|
+
create_session!
|
|
14
25
|
end
|
|
15
26
|
|
|
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
|
|
27
|
+
def close
|
|
28
|
+
delete_session! if @session_id
|
|
29
|
+
delete_tmp_profile! if @profile_path
|
|
23
30
|
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
|
|
31
|
+
unless @pid.nil?
|
|
32
|
+
Process.kill('TERM', @pid)
|
|
33
|
+
Process.wait(@pid)
|
|
36
34
|
end
|
|
35
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
36
|
+
# nothing to do if the process is already terminated
|
|
37
37
|
end
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def save_pdf(url, output_path, print_options: {})
|
|
42
|
+
validate_url_scheme!(url)
|
|
43
|
+
navigate(url)
|
|
44
|
+
pdf_base64 = print_pdf(print_options)
|
|
45
|
+
File.binwrite(output_path, Base64.decode64(pdf_base64))
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
private
|
|
@@ -49,7 +57,7 @@ module Gkhtmltopdf
|
|
|
49
57
|
def resolve_geckodriver_path!(provided_path)
|
|
50
58
|
path = provided_path || find_default_geckodriver
|
|
51
59
|
unless path
|
|
52
|
-
raise
|
|
60
|
+
raise PathUnresolvedError, 'Geckodriver'
|
|
53
61
|
end
|
|
54
62
|
path
|
|
55
63
|
end
|
|
@@ -57,7 +65,7 @@ module Gkhtmltopdf
|
|
|
57
65
|
def resolve_firefox_path!(provided_path)
|
|
58
66
|
path = provided_path || find_default_firefox
|
|
59
67
|
unless path
|
|
60
|
-
raise
|
|
68
|
+
raise PathUnresolvedError, 'Firefox'
|
|
61
69
|
end
|
|
62
70
|
path
|
|
63
71
|
end
|
|
@@ -89,16 +97,29 @@ module Gkhtmltopdf
|
|
|
89
97
|
common_paths.find { |path| File.executable?(path) && !File.directory?(path) }
|
|
90
98
|
end
|
|
91
99
|
|
|
92
|
-
def
|
|
93
|
-
|
|
100
|
+
def wait_for_gk(num)
|
|
101
|
+
num.times do
|
|
94
102
|
begin
|
|
95
103
|
Net::HTTP.get(URI("#{@base_url}/status"))
|
|
96
104
|
return
|
|
97
105
|
rescue Errno::ECONNREFUSED
|
|
98
|
-
sleep 0.
|
|
106
|
+
sleep 0.1
|
|
99
107
|
end
|
|
100
108
|
end
|
|
101
|
-
raise
|
|
109
|
+
raise BrowserError, "Failed to launch geckodriver (port #{@port})"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def gen_tmp_profile(ua = nil)
|
|
113
|
+
tmp_profile_path = Dir.mktmpdir
|
|
114
|
+
ua ||= DEFAULT_FX_USER_AGENT
|
|
115
|
+
escaped_user_agent = JSON.generate(ua)
|
|
116
|
+
profile = []
|
|
117
|
+
profile << '# set gkhtmltopdf default profile'
|
|
118
|
+
profile << "user_pref(\"general.useragent.override\", #{escaped_user_agent});\n"
|
|
119
|
+
File.open(File.join(tmp_profile_path, 'user.js'), 'w') do |f|
|
|
120
|
+
profile.each { |line| f.puts(line) }
|
|
121
|
+
end
|
|
122
|
+
tmp_profile_path
|
|
102
123
|
end
|
|
103
124
|
|
|
104
125
|
def post(path, payload)
|
|
@@ -106,16 +127,15 @@ module Gkhtmltopdf
|
|
|
106
127
|
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
|
|
107
128
|
req.body = payload.to_json
|
|
108
129
|
res = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
|
|
109
|
-
|
|
110
130
|
begin
|
|
111
131
|
JSON.parse(res.body)
|
|
112
132
|
rescue JSON::ParserError
|
|
113
|
-
raise
|
|
133
|
+
raise BrowserError, "Invalid json response (Status: #{res.code}): #{res.body}"
|
|
114
134
|
end
|
|
115
135
|
end
|
|
116
136
|
|
|
117
|
-
def create_session
|
|
118
|
-
firefox_options = { args: ["-headless"] }
|
|
137
|
+
def create_session!
|
|
138
|
+
firefox_options = { args: ["-headless", '--profile', @profile_path] }
|
|
119
139
|
firefox_options[:binary] = @firefox_path if @firefox_path != 'firefox'
|
|
120
140
|
|
|
121
141
|
payload = {
|
|
@@ -129,16 +149,16 @@ module Gkhtmltopdf
|
|
|
129
149
|
|
|
130
150
|
response = post("/session", payload)
|
|
131
151
|
value = response["value"]
|
|
132
|
-
raise
|
|
152
|
+
raise BrowserError, "Failed to create session: #{value}" if value["error"]
|
|
133
153
|
|
|
134
|
-
value["sessionId"]
|
|
154
|
+
@session_id = value["sessionId"]
|
|
135
155
|
end
|
|
136
156
|
|
|
137
|
-
def navigate(
|
|
138
|
-
post("/session/#{session_id}/url", { url: url })
|
|
157
|
+
def navigate(url)
|
|
158
|
+
post("/session/#{@session_id}/url", { url: url })
|
|
139
159
|
end
|
|
140
160
|
|
|
141
|
-
def print_pdf(
|
|
161
|
+
def print_pdf(user_options)
|
|
142
162
|
default_options = {
|
|
143
163
|
background: false,
|
|
144
164
|
shrinkToFit: true,
|
|
@@ -149,25 +169,30 @@ module Gkhtmltopdf
|
|
|
149
169
|
|
|
150
170
|
payload = default_options.merge(user_options)
|
|
151
171
|
|
|
152
|
-
response = post("/session/#{session_id}/print", payload)
|
|
172
|
+
response = post("/session/#{@session_id}/print", payload)
|
|
153
173
|
value = response["value"]
|
|
154
|
-
raise
|
|
174
|
+
raise BrowserError, "Failed to generate PDF: #{value}" if value["error"]
|
|
155
175
|
|
|
156
176
|
value
|
|
157
177
|
end
|
|
158
178
|
|
|
159
|
-
def delete_session
|
|
160
|
-
uri = URI("#{@base_url}/session/#{session_id}")
|
|
179
|
+
def delete_session!
|
|
180
|
+
uri = URI("#{@base_url}/session/#{@session_id}")
|
|
161
181
|
req = Net::HTTP::Delete.new(uri)
|
|
162
182
|
Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
|
|
183
|
+
@session_id = nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def delete_tmp_profile!
|
|
187
|
+
FileUtils.remove_entry_secure(@profile_path)
|
|
163
188
|
end
|
|
164
189
|
|
|
165
190
|
def validate_url_scheme!(url_string)
|
|
166
191
|
parsed_url = URI.parse(url_string)
|
|
167
192
|
allowed_schemes = ['http', 'https', 'file']
|
|
168
|
-
raise
|
|
193
|
+
raise URLSchemeInvalid, nil if parsed_url.scheme.nil?
|
|
169
194
|
unless allowed_schemes.include?(parsed_url.scheme)
|
|
170
|
-
raise
|
|
195
|
+
raise URLSchemeInvalid, parsed_url.scheme
|
|
171
196
|
end
|
|
172
197
|
end
|
|
173
198
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gkhtmltopdf
|
|
4
|
+
class DSL
|
|
5
|
+
def initialize
|
|
6
|
+
@converter = Converter.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def open(options)
|
|
10
|
+
@converter.open(**options)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def close
|
|
14
|
+
@converter.close
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save_pdf(url, output_path, print_options: {})
|
|
18
|
+
@converter.save_pdf(url, output_path, print_options: print_options)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
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, user_agent: nil, print_options: {})
|
|
10
|
+
converter = DSL.new
|
|
11
|
+
converter.open(geckodriver_path: geckodriver_path, firefox_path: firefox_path, wait_time: wait_time, port: port, user_agent: user_agent)
|
|
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, user_agent: nil, &block)
|
|
18
|
+
converter = DSL.new
|
|
19
|
+
converter.open(geckodriver_path: geckodriver_path, firefox_path: firefox_path, wait_time: wait_time, port: port, user_agent: user_agent)
|
|
20
|
+
yield converter
|
|
21
|
+
ensure
|
|
22
|
+
converter.close
|
|
12
23
|
end
|
|
13
24
|
end
|
|
@@ -1,14 +1,40 @@
|
|
|
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
|
+
let(:url) { "file://#{file_fixture('test.html')}" }
|
|
21
|
+
let(:output_path) { Dir.mktmpdir }
|
|
22
|
+
let(:output) { File.join(output_path, 'output.pdf') }
|
|
23
|
+
after { FileUtils.remove_entry_secure(output_path) }
|
|
24
|
+
|
|
25
|
+
subject { converter.save_pdf(url, output) }
|
|
26
|
+
|
|
27
|
+
it {
|
|
28
|
+
expect { subject }.to change { Dir.glob(output).count }.from(0).to(1)
|
|
29
|
+
expect(File.binread(output)).to include('/FontName')
|
|
30
|
+
}
|
|
31
|
+
end
|
|
6
32
|
describe '#resolve_geckodriver_path!' do
|
|
7
33
|
subject { converter.send(:resolve_geckodriver_path!, nil) }
|
|
8
34
|
context 'geckodriver is not available' do
|
|
9
35
|
before { allow(File).to receive(:executable?).and_return(false) }
|
|
10
36
|
it 'raises an error' do
|
|
11
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
37
|
+
expect { subject }.to raise_error(Gkhtmltopdf::PathUnresolvedError, /\AGeckodriver is not found./)
|
|
12
38
|
end
|
|
13
39
|
end
|
|
14
40
|
end
|
|
@@ -18,29 +44,45 @@ RSpec.describe Gkhtmltopdf::Converter do
|
|
|
18
44
|
context 'firefox is not available' do
|
|
19
45
|
before { allow(File).to receive(:executable?).and_return(false) }
|
|
20
46
|
it 'raises an error' do
|
|
21
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
47
|
+
expect { subject }.to raise_error(Gkhtmltopdf::PathUnresolvedError, /\AFirefox is not found./)
|
|
22
48
|
end
|
|
23
49
|
end
|
|
24
50
|
end
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
|
|
52
|
+
describe '#wait_for_gk' do
|
|
53
|
+
subject { converter.send(:wait_for_gk, 0) }
|
|
27
54
|
context 'fail launch geckodriver' do
|
|
28
55
|
before { allow(Net::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED, 'Dummy error') }
|
|
29
56
|
it 'raises an error' do
|
|
30
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
57
|
+
expect { subject }.to raise_error(Gkhtmltopdf::BrowserError, /\AFailed to launch geckodriver \(port \)\Z/)
|
|
31
58
|
end
|
|
32
59
|
end
|
|
33
60
|
end
|
|
61
|
+
|
|
34
62
|
describe '#post' do
|
|
35
|
-
let(:converter) { Gkhtmltopdf::Converter.new }
|
|
36
63
|
subject { converter.send(:post, '/dummy', {test: :value}) }
|
|
37
64
|
context 'Invalid json response from geckodriver' do
|
|
38
65
|
before {
|
|
66
|
+
converter.instance_variable_set(:@base_url, 'http://test')
|
|
39
67
|
allow(Net::HTTP).to receive(:start).and_return(Struct.new(:code, :body).new('200', 'invalid_json: 0123'))
|
|
40
68
|
}
|
|
41
69
|
it 'raises an error' do
|
|
42
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
70
|
+
expect { subject }.to raise_error(Gkhtmltopdf::BrowserError, 'Invalid json response (Status: 200): invalid_json: 0123')
|
|
43
71
|
end
|
|
44
72
|
end
|
|
45
73
|
end
|
|
74
|
+
|
|
75
|
+
describe '#validate_url_scheme!' do
|
|
76
|
+
subject { converter.send(:validate_url_scheme!, url_string) }
|
|
77
|
+
let(:url_string) { 'http://f6a.net' }
|
|
78
|
+
it { expect { subject }.not_to raise_error }
|
|
79
|
+
context 'scheme is nil' do
|
|
80
|
+
let(:url_string) { 'f6a.net' }
|
|
81
|
+
it { expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: ()') }
|
|
82
|
+
end
|
|
83
|
+
context 'invalid scheme' do
|
|
84
|
+
let(:url_string) { 'about://version' }
|
|
85
|
+
it { expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (about)') }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
46
88
|
end
|
data/spec/gkhtmltopdf_spec.rb
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
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
|
-
let(:
|
|
10
|
-
let(:
|
|
6
|
+
let(:output_path) { Dir.mktmpdir }
|
|
7
|
+
let(:output) { File.join(output_path, 'output.pdf') }
|
|
8
|
+
after { FileUtils.remove_entry_secure(output_path) }
|
|
11
9
|
|
|
12
|
-
subject { Gkhtmltopdf.convert(url, output
|
|
10
|
+
subject { Gkhtmltopdf.convert(url, output) }
|
|
13
11
|
|
|
14
12
|
it 'successful conversion' do
|
|
15
13
|
expect { subject }.not_to raise_error
|
|
@@ -18,7 +16,30 @@ RSpec.describe Gkhtmltopdf do
|
|
|
18
16
|
context 'invalid URL' do
|
|
19
17
|
let(:url) { 'ftp://example.com' }
|
|
20
18
|
it 'raises an error' do
|
|
21
|
-
expect { subject }.to raise_error(Gkhtmltopdf::
|
|
19
|
+
expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (ftp)')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
describe '.open' do
|
|
24
|
+
let(:url) { 'https://f6a.net/oss/' }
|
|
25
|
+
let(:output_path) { Dir.mktmpdir }
|
|
26
|
+
after { FileUtils.remove_entry_secure(output_path) }
|
|
27
|
+
|
|
28
|
+
subject do
|
|
29
|
+
Gkhtmltopdf.open do |gk|
|
|
30
|
+
(1..3).each { |n| gk.save_pdf("#{url}?test=#{n}", File.join(output_path, "#{n}.pdf")) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'successful conversion' do
|
|
35
|
+
expect { subject }.to change { Dir.glob(File.join(output_path, '*.pdf')).count }.from(0).to(3)
|
|
36
|
+
expect { subject }.not_to raise_error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'invalid URL' do
|
|
40
|
+
let(:url) { 'ftp://example.com' }
|
|
41
|
+
it 'raises an error' do
|
|
42
|
+
expect { subject }.to raise_error(Gkhtmltopdf::URLSchemeInvalid, 'Invalid URL scheme: (ftp)')
|
|
22
43
|
end
|
|
23
44
|
end
|
|
24
45
|
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:
|
|
4
|
+
version: 1.1.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
|
|
@@ -79,14 +78,18 @@ files:
|
|
|
79
78
|
- ".rspec"
|
|
80
79
|
- ".ruby-version"
|
|
81
80
|
- CHANGELOG.md
|
|
82
|
-
- Dockerfile
|
|
83
81
|
- LICENSE
|
|
84
82
|
- README.md
|
|
85
83
|
- Rakefile
|
|
86
84
|
- TODO.md
|
|
85
|
+
- dockerfiles/Dockerfile.debian13-ruby32
|
|
86
|
+
- dockerfiles/Dockerfile.ubuntu24-ruby32
|
|
87
|
+
- docs/ForDeveloper.md
|
|
87
88
|
- exe/gkhtmltopdf
|
|
89
|
+
- lib/errors.rb
|
|
88
90
|
- lib/gkhtmltopdf.rb
|
|
89
91
|
- lib/gkhtmltopdf/converter.rb
|
|
92
|
+
- lib/gkhtmltopdf/dsl.rb
|
|
90
93
|
- lib/gkhtmltopdf/version.rb
|
|
91
94
|
- spec/fixtures/test.html
|
|
92
95
|
- spec/gkhtmltopdf/converter_spec.rb
|
|
@@ -98,6 +101,7 @@ licenses:
|
|
|
98
101
|
metadata:
|
|
99
102
|
homepage_uri: https://f6a.net/oss/
|
|
100
103
|
source_code_uri: https://github.com/fantasia-tech/gkhtmltopdf-rb
|
|
104
|
+
changelog_uri: https://github.com/fantasia-tech/gkhtmltopdf-rb/blob/main/CHANGELOG.md
|
|
101
105
|
post_install_message: "=====================================================================\nGkhtmltopdf
|
|
102
106
|
has been installed successfully. \U0001F389\n\n⚠️ Caution\nRequired: To run this
|
|
103
107
|
gem, you need to have `firefox` and `geckodriver` installed and added to your PATH.\n\ncheck
|
|
@@ -119,8 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
123
|
requirements:
|
|
120
124
|
- Firefox
|
|
121
125
|
- Geckodriver
|
|
122
|
-
rubygems_version:
|
|
123
|
-
signing_key:
|
|
126
|
+
rubygems_version: 4.0.8
|
|
124
127
|
specification_version: 4
|
|
125
128
|
summary: Gkhtmltopdf is mean Gecko HTML to PDF converter.
|
|
126
129
|
test_files: []
|