gotenberg 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 +7 -0
- data/lib/gotenberg/client.rb +123 -0
- data/lib/gotenberg/helper.rb +124 -0
- data/lib/gotenberg/railtie.rb +14 -0
- data/lib/gotenberg/version.rb +5 -0
- data/lib/gotenberg.rb +10 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d984cff5f3354d5dd5ab3a8eb7210fa49532125cf9d8d7ef57a729243d3434aa
|
4
|
+
data.tar.gz: c1f52733269daac044db7c1930d0b276924484aaa365a2919719fa2e55e70c98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a86064442da347abc5a5bc1aebdced0921935f545bc300d49ef69ad63c33f86a4f24a129dfc158aec5ffda08a9de331f4bb77fc218017b88e7602e8662bd080
|
7
|
+
data.tar.gz: ef1dd7bc42f1763bf4b1a1d22c5cf5937cc924db422c56d353dc7c92be48683ea0d531b332ff9cfbf88391e641d0006dec06b2c7c91ddda1ea5d6689c7bfbe5b
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require "uri"
|
5
|
+
require "json"
|
6
|
+
require "mime/types"
|
7
|
+
require "net/http/post/multipart"
|
8
|
+
require "tempfile"
|
9
|
+
require "securerandom"
|
10
|
+
require "fileutils"
|
11
|
+
|
12
|
+
module Gotenberg
|
13
|
+
class IndexFileMissing < StandardError; end
|
14
|
+
|
15
|
+
# Client class for interacting with the Gotenberg API
|
16
|
+
class Client
|
17
|
+
# Initialize a new Client
|
18
|
+
#
|
19
|
+
# @param api_url [String] The base URL of the Gotenberg API
|
20
|
+
def initialize(api_url)
|
21
|
+
@api_url = api_url
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert HTML files to PDF and write it to the output file
|
25
|
+
#
|
26
|
+
# @param htmls [Hash{Symbol => String}] A hash with the file name as the key and the HTML content as the value
|
27
|
+
# @param asset_paths [Array<String>] Paths to the asset files (like CSS, images) required by the HTML files
|
28
|
+
# @param properties [Hash] Additional properties for PDF conversion
|
29
|
+
# @option properties [Float] :paperWidth The width of the paper
|
30
|
+
# @option properties [Float] :paperHeight The height of the paper
|
31
|
+
# @option properties [Float] :marginTop The top margin
|
32
|
+
# @option properties [Float] :marginBottom The bottom margin
|
33
|
+
# @option properties [Float] :marginLeft The left margin
|
34
|
+
# @option properties [Float] :marginRight The right margin
|
35
|
+
# @option properties [Boolean] :preferCssPageSize Whether to prefer CSS page size
|
36
|
+
# @option properties [Boolean] :printBackground Whether to print the background
|
37
|
+
# @option properties [Boolean] :omitBackground Whether to omit the background
|
38
|
+
# @option properties [Boolean] :landscape Whether to use landscape orientation
|
39
|
+
# @option properties [Float] :scale The scale of the PDF
|
40
|
+
# @option properties [String] :nativePageRanges The page ranges to include
|
41
|
+
# @return [String] The resulting PDF content
|
42
|
+
# @raise [GotenbergDownError] if the Gotenberg API is down
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# htmls = { index: "<h1>Html</h1>", header: "<h1>Header</h1>", footer: "<h1>Footer</h1>" }
|
46
|
+
# asset_paths = ["path/to/style.css", "path/to/image.png"]
|
47
|
+
# properties = { paperWidth: 8.27, paperHeight: 11.7, marginTop: 1, marginBottom: 1 }
|
48
|
+
# client = Gotenberg::Client.new("http://localhost:3000")
|
49
|
+
# pdf_content = client.html(htmls, asset_paths, properties)
|
50
|
+
#
|
51
|
+
def html(htmls, asset_paths, properties = {}) # rubocop:disable Metrics/CyclomaticComplexity
|
52
|
+
raise GotenbergDownError unless up?
|
53
|
+
|
54
|
+
raise IndexFileMissing unless (htmls.keys & ["index", :index]).any?
|
55
|
+
|
56
|
+
dir_name = SecureRandom.uuid
|
57
|
+
dir_path = File.join(Dir.tmpdir, dir_name)
|
58
|
+
FileUtils.mkdir_p(dir_path)
|
59
|
+
|
60
|
+
htmls.each do |key, value|
|
61
|
+
File.write(File.join(dir_path, "#{key}.html"), value)
|
62
|
+
end
|
63
|
+
|
64
|
+
uri = URI("#{@api_url}/forms/chromium/convert/html")
|
65
|
+
|
66
|
+
# Gotenberg requires all files to be in the same directory
|
67
|
+
asset_paths.each do |path|
|
68
|
+
FileUtils.cp(path, dir_path)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Rejecting .. and .
|
72
|
+
entries = Dir.entries(dir_path).reject { |f| f.start_with?(".") }
|
73
|
+
|
74
|
+
payload = entries.each_with_object({}).with_index do |(entry, obj), index|
|
75
|
+
entry_abs_path = File.join(dir_path, entry)
|
76
|
+
mime_type = MIME::Types.type_for(entry_abs_path).first.content_type
|
77
|
+
obj["files[#{index}]"] = UploadIO.new(entry_abs_path, mime_type)
|
78
|
+
end
|
79
|
+
|
80
|
+
response = multipart_post(uri, payload.merge(properties))
|
81
|
+
response.body.dup.force_encoding("utf-8")
|
82
|
+
ensure
|
83
|
+
FileUtils.rm_rf(dir_path) if dir_path
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check if the Gotenberg API is up and healthy
|
87
|
+
#
|
88
|
+
# @return [Boolean] true if the API is up, false otherwise
|
89
|
+
def up?
|
90
|
+
uri = URI("#{@api_url}/health")
|
91
|
+
request = Net::HTTP::Get.new(uri)
|
92
|
+
request.basic_auth(
|
93
|
+
ENV.fetch("GOTENBERG_API_BASIC_AUTH_USERNAME", nil),
|
94
|
+
ENV.fetch("GOTENBERG_API_BASIC_AUTH_PASSWORD", nil)
|
95
|
+
)
|
96
|
+
|
97
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
98
|
+
http.use_ssl = uri.scheme == "https"
|
99
|
+
response = http.request(request)
|
100
|
+
|
101
|
+
response.is_a?(Net::HTTPSuccess) && JSON.parse(response.body)["status"] == "up"
|
102
|
+
rescue StandardError
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def multipart_post(uri, payload)
|
109
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, payload)
|
110
|
+
request.basic_auth(
|
111
|
+
ENV.fetch("GOTENBERG_API_BASIC_AUTH_USERNAME", nil),
|
112
|
+
ENV.fetch("GOTENBERG_API_BASIC_AUTH_PASSWORD", nil)
|
113
|
+
)
|
114
|
+
|
115
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
116
|
+
http.use_ssl = uri.scheme == "https"
|
117
|
+
http.request(request)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Custom error class for Gotenberg API downtime
|
122
|
+
class GotenbergDownError < StandardError; end
|
123
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gotenberg
|
4
|
+
module Helper # rubocop:disable Style/Documentation
|
5
|
+
class ExtensionMissing < StandardError; end
|
6
|
+
|
7
|
+
class PropshaftAsset # rubocop:disable Style/Documentation
|
8
|
+
attr_reader :asset
|
9
|
+
|
10
|
+
def initialize(asset)
|
11
|
+
@asset = asset
|
12
|
+
end
|
13
|
+
|
14
|
+
def content_type
|
15
|
+
asset.content_type.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
asset.content
|
20
|
+
end
|
21
|
+
|
22
|
+
def filename
|
23
|
+
asset.path.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MissingAsset < StandardError # rubocop:disable Style/Documentation
|
28
|
+
attr_reader :path
|
29
|
+
|
30
|
+
def initialize(path, message)
|
31
|
+
@path = path
|
32
|
+
super(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class LocalAsset # rubocop:disable Style/Documentation
|
37
|
+
attr_reader :path
|
38
|
+
|
39
|
+
def initialize(path)
|
40
|
+
@path = path
|
41
|
+
end
|
42
|
+
|
43
|
+
def content_type
|
44
|
+
Mime::Type.lookup_by_extension(File.extname(path).delete("."))
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
File.read(path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def filename
|
52
|
+
path.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class SprocketsEnvironment # rubocop:disable Style/Documentation
|
57
|
+
def self.instance
|
58
|
+
@instance ||= Sprockets::Railtie.build_environment(Rails.application)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.find_asset(*args)
|
62
|
+
instance.find_asset(*args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def goten_asset_base64(asset_name)
|
67
|
+
asset = find_asset(goten_static_asset_path(asset_name))
|
68
|
+
raise MissingAsset.new(asset_name, "Could not find asset '#{asset_name}'") if asset.nil?
|
69
|
+
|
70
|
+
base64 = Base64.encode64(asset.to_s).delete("\n")
|
71
|
+
"data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def goten_static_asset_path(asset_name)
|
75
|
+
ext = File.extname(asset_name).delete(".")
|
76
|
+
|
77
|
+
raise ExtensionMissing if ext.empty?
|
78
|
+
|
79
|
+
asset_type =
|
80
|
+
case ext
|
81
|
+
when "js" then "javascripts"
|
82
|
+
when "css" then "stylesheets"
|
83
|
+
else "images"
|
84
|
+
end
|
85
|
+
|
86
|
+
determine_static_path(asset_type, asset_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def goten_compiled_asset_path(asset_name)
|
90
|
+
Rails.public_path.to_s +
|
91
|
+
ActionController::Base.helpers.asset_path(asset_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def determine_static_path(asset_type, asset_name)
|
97
|
+
asset_root = Rails.root.join("app", "assets")
|
98
|
+
path = asset_root.join(asset_type, asset_name)
|
99
|
+
|
100
|
+
unless File.exist?(path)
|
101
|
+
raise MissingAsset.new(
|
102
|
+
asset_name,
|
103
|
+
"Could not find static asset '#{asset_name}'"
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
path.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
# Thanks WickedPDF 🙏
|
111
|
+
def find_asset(path)
|
112
|
+
if Rails.application.assets.respond_to?(:find_asset)
|
113
|
+
Rails.application.assets.find_asset(path, base_path: Rails.application.root.to_s)
|
114
|
+
elsif defined?(Propshaft::Assembly) && Rails.application.assets.is_a?(Propshaft::Assembly)
|
115
|
+
PropshaftAsset.new(Rails.application.assets.load_path.find(path))
|
116
|
+
elsif Rails.application.respond_to?(:assets_manifest)
|
117
|
+
asset_path = File.join(Rails.application.assets_manifest.dir, Rails.application.assets_manifest.assets[path])
|
118
|
+
LocalAsset.new(asset_path) if File.file?(asset_path)
|
119
|
+
else
|
120
|
+
SprocketsEnvironment.find_asset(path, base_path: Rails.application.root.to_s)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "gotenberg/helper"
|
5
|
+
|
6
|
+
module Gotenberg
|
7
|
+
class Railtie < Rails::Railtie # rubocop:disable Style/Documentation
|
8
|
+
initializer "gotenberg.register" do
|
9
|
+
ActiveSupport.on_load :action_view do
|
10
|
+
include Gotenberg::Helper
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/gotenberg.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "gotenberg/version"
|
4
|
+
require_relative "gotenberg/client"
|
5
|
+
require_relative "gotenberg/railtie" if defined?(Rails::Railtie)
|
6
|
+
require_relative "gotenberg/helper"
|
7
|
+
|
8
|
+
module Gotenberg
|
9
|
+
class GotenbergDownError < StandardError; end
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gotenberg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bugloper
|
8
|
+
- teknatha136
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2024-06-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mime-types
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: multipart-post
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.1'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.1'
|
42
|
+
description: A simple Ruby client for gotenberg
|
43
|
+
email:
|
44
|
+
- bugloper@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- lib/gotenberg.rb
|
50
|
+
- lib/gotenberg/client.rb
|
51
|
+
- lib/gotenberg/helper.rb
|
52
|
+
- lib/gotenberg/railtie.rb
|
53
|
+
- lib/gotenberg/version.rb
|
54
|
+
homepage: https://github.com/SELISEdigitalplatforms/l3-ruby-gem-gotenberg
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata:
|
58
|
+
allowed_push_host: https://rubygems.org
|
59
|
+
homepage_uri: https://github.com/SELISEdigitalplatforms/l3-ruby-gem-gotenberg
|
60
|
+
source_code_uri: https://github.com/SELISEdigitalplatforms/l3-ruby-gem-gotenberg
|
61
|
+
changelog_uri: https://github.com/SELISEdigitalplatforms/l3-ruby-gem-gotenberg/blob/main/CHANGELOG.md
|
62
|
+
rubygems_mfa_required: 'false'
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 3.0.0
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubygems_version: 3.5.11
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: A simple Ruby client for gotenberg
|
82
|
+
test_files: []
|