bidi2pdf 0.1.4 β†’ 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f132ccc5d9db5bb621f7e1a0adfa4150b77b4618542ba27873a602cf9c342a40
4
- data.tar.gz: f3df228dc1de555d14baad4347a1b5b8d0bc8b63f88d6adfbcec86f0da571703
3
+ metadata.gz: b92ff566ca3ae322033cd9602818c94ae30da2594c7075bbb86f4c16351d58ce
4
+ data.tar.gz: 1b4d46aa7fe35c1810522a51400c3558f04d8fdc4093c7b3efa0f507a4959392
5
5
  SHA512:
6
- metadata.gz: d74e6e1b505493d47f66c2f1cb4d13bc07aacfa82dbba536439e3b458395bbcd7213103e5fbed167a65945af28bbfb239f6510e93bed2838fa583b95352df8d6
7
- data.tar.gz: '096bb337a61db6d5392ba64c38bf4632695ca647b2fe95a227f3d56b189241cae1b19cc65e98115dff73997bb3df1cdcc2821058611683aa449acd11db4fe66f'
6
+ metadata.gz: 63aa303c4dc6245510cb212adaf22523190d8e00892f4ffe9dc9ba9b65e966f74517ee748f4a232736701a3271ae086879fcbf0adc02ce9f9cd82d103ea3e70d
7
+ data.tar.gz: 320a0a03c399541157bd192b6389fecf5541c994cdcaa186694085887ecc8de0829b63badc2be5fa0cd2b8c9baa4ee60bfe7a2f457743d4f21506f1d4bb44850
data/CHANGELOG.md CHANGED
@@ -6,10 +6,23 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
- [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.4..HEAD
9
+ [unreleased]: https://github.com/dieter-medium/bidi2pdf/compare/v0.1.5..HEAD
10
10
 
11
11
  <!-- generated by git-cliff end -->
12
12
 
13
+ ## [0.1.5] - 2025-04-10
14
+
15
+ ### πŸ“ Docs
16
+
17
+ - Improve README structure and update content by @dieter-medium
18
+ - Add step-by-step usage guide to README by @dieter-medium
19
+
20
+ ### πŸš€ Added
21
+
22
+ - Add support for rendering local HTML files or in memory HTML by @dieter-medium
23
+ - Add DSL and specs for generating PDFs with browser tabs by @dieter-medium
24
+ - Enhance print method with block support for PDF handling by @dieter-medium
25
+
13
26
  ## [0.1.4] - 2025-04-10
14
27
 
15
28
  ### 🎨 Refactored
data/README.md CHANGED
@@ -3,52 +3,58 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/bidi2pdf.svg)](https://badge.fury.io/rb/bidi2pdf)
4
4
  [![Test Coverage](https://api.codeclimate.com/v1/badges/6425d9893aa3a9ca243e/test_coverage)](https://codeclimate.com/github/dieter-medium/bidi2pdf/test_coverage)
5
5
 
6
- # Bidi2pdf
6
+ ---
7
7
 
8
- Bidi2pdf is a Ruby gem that generates high-quality PDFs from web pages using Chrome's BiDi (BiDirectional) protocol. It
9
- offers precise control over PDF generation with support for modern web technologies.
8
+ # πŸ“„ Bidi2pdf – Bulletproof PDF generation via Chrome's BiDi Protocol
10
9
 
11
- ## Features
10
+ **Bidi2pdf** is a powerful Ruby gem that transforms modern web pages into high-fidelity PDFs using Chrome’s *
11
+ *BiDirectional (BiDi)** protocol. Whether you're automating reports, archiving websites, or shipping documentation,
12
+ Bidi2pdf gives you **precision, flexibility, and full control**.
12
13
 
13
- - **Simple CLI** - Generate PDFs with a single command
14
- - **Rich Configuration** - Customize with cookies, headers, and authentication
15
- - **Waiting Conditions** - Wait for window loaded or network idle
16
- - **Headless Support** - Run without a visible browser
17
- - **Docker Ready** - Easy containerization
18
- - **Modern Architecture** - Uses Chrome's BiDi protocol for better control
14
+ ---
19
15
 
20
- ## Installation
16
+ ## ✨ Key Features
21
17
 
22
- Add to your application's Gemfile:
18
+ βœ… **One-liner CLI** – From URL to PDF in a single command
19
+ βœ… **Full customization** – Inject cookies, headers, auth credentials
20
+ βœ… **Smart waiting** – Wait for complete page load or network idle
21
+ βœ… **Headless support** – Run quietly in the background
22
+ βœ… **Docker-ready** – Plug and play with containers
23
+ βœ… **Modern architecture** – Built on Chrome's next-gen BiDi protocol
24
+
25
+ ---
26
+
27
+ ## πŸš€ Installation
28
+
29
+ ### Bundler
23
30
 
24
31
  ```ruby
25
32
  gem 'bidi2pdf'
26
33
  ```
27
34
 
28
- Or install manually:
35
+ ### Standalone
29
36
 
30
37
  ```bash
31
- $ gem install bidi2pdf
38
+ gem install bidi2pdf
32
39
  ```
33
40
 
34
- ### Dependencies
41
+ ### Requirements
35
42
 
36
- - **Ruby**: 3.3 or higher
37
- - **Bidi2pdf** automatically manages ChromeDriver binaries through
38
- the [chromedriver-binary](https://github.com/dieter-medium/chromedriver-binary) gem, which:
39
- Downloads and installs the ChromeDriver version matching your installed Chrome/Chromium browser
40
- Eliminates the need to manually install or update ChromeDriver
41
- Ensures compatibility between Chrome and ChromeDriver versions
43
+ - **Ruby** β‰₯ 3.3
44
+ - **Chrome/Chromium**
45
+ - Automatic ChromeDriver management via [chromedriver-binary](https://github.com/dieter-medium/chromedriver-binary)
42
46
 
43
- ## Usage
47
+ ---
44
48
 
45
- ### Basic Command Line Usage
49
+ ## βš™οΈ Basic Usage
50
+
51
+ ### Command-line
46
52
 
47
53
  ```bash
48
54
  bidi2pdf render --url https://example.com --output example.pdf
49
55
  ```
50
56
 
51
- ### Advanced Options
57
+ ### Advanced CLI Options
52
58
 
53
59
  ```bash
54
60
  bidi2pdf render \
@@ -62,14 +68,18 @@ bidi2pdf render \
62
68
  --log-level debug
63
69
  ```
64
70
 
65
- ### Ruby API
71
+ ---
72
+
73
+ ## 🧠 Programmatic API
74
+
75
+ ### Classic Approach
66
76
 
67
77
  ```ruby
68
78
  require 'bidi2pdf'
69
79
 
70
80
  launcher = Bidi2pdf::Launcher.new(
71
81
  url: 'https://example.com',
72
- output: 'example.pdf', # nil for base64 encoded string as result of launcher.launch
82
+ output: 'example.pdf',
73
83
  cookies: { 'session' => 'abc123' },
74
84
  headers: { 'X-API-KEY' => 'token' },
75
85
  auth: { username: 'admin', password: 'password' },
@@ -78,25 +88,90 @@ launcher = Bidi2pdf::Launcher.new(
78
88
  )
79
89
 
80
90
  launcher.launch
91
+ ```
92
+
93
+ ### DSL – Quick & Clean
94
+
95
+ ```ruby
96
+ require "bidi2pdf"
97
+
98
+ Bidi2pdf::DSL.with_tab(headless: true) do |tab|
99
+ tab.open_page("https://example.com")
100
+ tab.wait_until_all_finished
101
+ tab.print("example.pdf")
102
+ end
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 🧬 Deep Integration Example
108
+
109
+ Get fine-grained control using Chrome sessions, tabs, and BiDi commands:
110
+
111
+ <details>
112
+ <summary>πŸ” Show full example</summary>
113
+
114
+ ```ruby
115
+ require "bidi2pdf"
116
+
117
+ # 1. Remote or local session?
118
+ session = Bidi2pdf::Bidi::Session.new(
119
+ session_url: "http://localhost:9092/session",
120
+ headless: true,
121
+ )
122
+
123
+ # Alternative: local session via ChromeDriver
124
+ # manager = Bidi2pdf::ChromedriverManager.new(headless: false)
125
+ # manager.start
126
+ # session = manager.session
127
+
128
+ session.start
129
+ session.client.on_close { puts "WebSocket session closed" }
130
+
131
+ # 2. Create browser/tab
132
+ browser = session.browser
133
+ context = browser.create_user_context
134
+ window = context.create_browser_window
135
+ tab = window.create_browser_tab
136
+
137
+ # 3. Inject configuration
138
+ tab.set_cookie(name: "auth", value: "secret", domain: "example.com", secure: true)
139
+ tab.add_headers(url_patterns: [{ type: "pattern", protocol: "https", hostname: "example.com", port: "443" }],
140
+ headers: [{ name: "X-API-KEY", value: "12345678" }])
141
+ tab.basic_auth(url_patterns: [{ type: "pattern", protocol: "https", hostname: "example.com", port: "443" }],
142
+ username: "username", password: "secret")
81
143
 
82
- # see Bidi2pdf::SessionRunner for more options
144
+ # 4. Render PDF
145
+ tab.open_page "https://example.com"
146
+
147
+ # Alternative: send html code to the browser
148
+ # tab.view_html_page("<html>...</html>")
149
+
150
+ tab.wait_until_all_finished
151
+ tab.print("my.pdf")
152
+
153
+ # 5. Cleanup
154
+ tab.close
155
+ window.close
156
+ session.close
83
157
  ```
84
158
 
85
- ## Docker Support
159
+ </details>
160
+
161
+ ---
162
+
163
+ ## 🐳 Docker Support
86
164
 
87
- Build and run with Docker:
165
+ ### Build & Run
88
166
 
89
167
  ```bash
90
- # Build gem and Docker image
91
168
  rake build
92
169
  docker build -t bidi2pdf -f docker/Dockerfile .
93
-
94
- # Generate PDF using Docker
95
170
  docker run -it --rm -v ./output:/reports bidi2pdf \
96
171
  bidi2pdf render --url=https://example.com --output /reports/example.pdf
97
172
  ```
98
173
 
99
- ### Test it with docker compose
174
+ ### Docker Compose
100
175
 
101
176
  ```bash
102
177
  rake build
@@ -105,6 +180,10 @@ docker compose -f docker/docker-compose.yml up -d
105
180
  # simple example
106
181
  docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/sample.html --wait_window_loaded --wait_network_idle --output /reports/simple.pdf
107
182
 
183
+ # with a local file
184
+ docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=file:///reports/sample.html--wait_network_idle --output /reports/simple.pdf
185
+
186
+
108
187
  # basic auth example
109
188
  docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http://nginx/basic/sample.html --auth admin:secret --wait_window_loaded --wait_network_idle --output /reports/basic.pdf
110
189
 
@@ -120,31 +199,42 @@ docker compose -f docker/docker-compose.yml exec app bidi2pdf render --url=http:
120
199
  docker compose -f docker/docker-compose.yml down
121
200
  ```
122
201
 
123
- ## Configuration Options
202
+ ---
203
+
204
+ ## 🧩 Configuration Options
205
+
206
+ | Flag | Description |
207
+ |------------------------|--------------------------------------------|
208
+ | `--url` | Target URL (required) |
209
+ | `--output` | Output PDF file (default: output.pdf) |
210
+ | `--cookie` | Set cookie in `name=value` format |
211
+ | `--header` | Inject custom header `name=value` |
212
+ | `--auth` | Basic auth as `user:pass` |
213
+ | `--headless` | Run Chrome headless (default: true) |
214
+ | `--port` | ChromeDriver port (0 = auto) |
215
+ | `--wait_window_loaded` | Wait until `window.loaded` is set to true |
216
+ | `--wait_network_idle` | Wait until network is idle |
217
+ | `--log_level` | Log level: debug, info, warn, error, fatal |
218
+ | `--remote_browser_url` | Connect to remote Chrome session |
219
+ | `--default_timeout` | Operation timeout (default: 60s) |
124
220
 
125
- | Option | Description |
126
- |------------------------|---------------------------------------------------------------------------------------------------------------------|
127
- | `--url` | The URL to render (required) |
128
- | `--output` | Output PDF filename (default: output.pdf) |
129
- | `--cookie` | Cookies in name=value format |
130
- | `--header` | HTTP headers in name=value format |
131
- | `--auth` | Basic auth credentials (user:pass) |
132
- | `--headless` | Run Chrome in headless mode (default: true) |
133
- | `--port` | Port for ChromeDriver (0 = auto) |
134
- | `--wait_window_loaded` | Wait for the window to be fully loaded. You need to set a variable `window.loaded`. See ./spec/fixtures/sample.html |
135
- | `--wait_network_idle` | Wait for network to be idle |
136
- | `--log_level` | Log level (debug, info, warn, error, fatal) |
137
- | `--remote_browser_url` | URL of the remote Chrome instance (default: nil) |
138
- | `--default_timeout` | Default timeout for operations (default: 60 seconds) |
221
+ ---
139
222
 
140
- ## Development
223
+ ## πŸ›  Development
141
224
 
142
- After checking out the repo:
225
+ ```bash
226
+ # Setup
227
+ bin/setup
228
+
229
+ # Run tests
230
+ rake spec
231
+
232
+ # Open interactive console
233
+ bin/console
234
+ ```
143
235
 
144
- 1. Run `bin/setup` to install dependencies
145
- 2. Run `rake spec` to run the tests
146
- 3. Run `bin/console` for an interactive prompt
236
+ ---
147
237
 
148
- ## License
238
+ ## πŸ“œ License
149
239
 
150
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
240
+ This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
@@ -88,6 +88,13 @@ module Bidi2pdf
88
88
  end
89
89
  end
90
90
 
91
+ def view_html_page(html_content)
92
+ base64_encoded = Base64.strict_encode64(html_content)
93
+ data_url = "data:text/html;charset=utf-8;base64,#{base64_encoded}"
94
+
95
+ open_page(data_url)
96
+ end
97
+
91
98
  def execute_script(script)
92
99
  cmd = Bidi2pdf::Bidi::Commands::ScriptEvaluate.new context: browsing_context_id, expression: script
93
100
  client.send_cmd_and_wait(cmd) do |response|
@@ -111,7 +118,8 @@ module Bidi2pdf
111
118
  @open = false
112
119
  end
113
120
 
114
- def print(outputfile, print_options: { background: true })
121
+ # rubocop: disable Metrics/AbcSize, Metrics/PerceivedComplexity
122
+ def print(outputfile = nil, print_options: { background: true }, &block)
115
123
  cmd = Bidi2pdf::Bidi::Commands::BrowsingContextPrint.new context: browsing_context_id, print_options: print_options
116
124
 
117
125
  client.send_cmd_and_wait(cmd) do |response|
@@ -119,19 +127,25 @@ module Bidi2pdf
119
127
  pdf_base64 = response["result"]["data"]
120
128
 
121
129
  if outputfile
130
+ raise PrintError, "Folder does not exist: #{File.dirname(outputfile)}" unless File.directory?(File.dirname(outputfile))
131
+
122
132
  File.binwrite(outputfile, Base64.decode64(pdf_base64))
123
133
  Bidi2pdf.logger.info "PDF saved as '#{outputfile}'."
124
134
  else
125
135
  Bidi2pdf.logger.info "PDF generated successfully."
126
136
  end
127
137
 
128
- return pdf_base64 unless outputfile
138
+ block.call(pdf_base64) if block_given?
139
+
140
+ return pdf_base64 unless outputfile || block_given?
129
141
  else
130
142
  Bidi2pdf.logger.error "Error printing: #{response}"
131
143
  end
132
144
  end
133
145
  end
134
146
 
147
+ # rubocop: enable Metrics/AbcSize, Metrics/PerceivedComplexity
148
+
135
149
  private
136
150
 
137
151
  def close_context
@@ -6,6 +6,14 @@ module Bidi2pdf
6
6
  class SetTabCookie
7
7
  include Base
8
8
 
9
+ class << self
10
+ attr_writer :time_provider
11
+
12
+ def time_provider
13
+ @time_provider ||= -> { Time.now }
14
+ end
15
+ end
16
+
9
17
  attr_reader :name, :value, :domain, :path, :secure, :http_only, :same_site, :ttl, :browsing_context_id
10
18
 
11
19
  def initialize(name:,
@@ -29,7 +37,7 @@ module Bidi2pdf
29
37
  end
30
38
 
31
39
  def expiry
32
- Time.now.to_i + ttl
40
+ self.class.time_provider.call.to_i + ttl
33
41
  end
34
42
 
35
43
  def method_name
data/lib/bidi2pdf/cli.rb CHANGED
@@ -31,6 +31,10 @@ module Bidi2pdf
31
31
  USAGE
32
32
 
33
33
  option :url, desc: "The URL to render"
34
+ # of course, it's possible to render a local file via: --url file:///path/to/the/file.html
35
+ # but this should showcase the scenario that you render a string within ruby as pdf without the need
36
+ # to store it on disc
37
+ option :html_file, desc: "The local HTML file to render"
34
38
  option :output, default: "output.pdf", desc: "Filename for the output PDF", aliases: "-o"
35
39
  option :cookie, type: :array, default: [], banner: "name=value", desc: "One or more cookies", aliases: "-C"
36
40
  option :header, type: :array, default: [], banner: "name=value", desc: "One or more custom headers", aliases: "-H"
@@ -63,7 +67,7 @@ module Bidi2pdf
63
67
  def render
64
68
  load_config
65
69
 
66
- validate_required_options! :url
70
+ validate_required_options!
67
71
 
68
72
  configure
69
73
 
@@ -115,9 +119,11 @@ module Bidi2pdf
115
119
  YAML.load_file(options[:config]).transform_keys(&:to_sym)
116
120
  end
117
121
 
118
- def validate_required_options!(*keys)
119
- keys.each do |key|
120
- raise Thor::Error, "Missing required option: --#{key.to_s.tr("_", "-")}" unless merged_options[key]
122
+ def validate_required_options!
123
+ if merged_options[:url].nil? && merged_options[:html_file].nil?
124
+ raise Thor::Error, "Missing required option --url or --html_file needs to be specified"
125
+ elsif merged_options[:html_file] && !File.readable?(merged_options[:html_file])
126
+ raise Thor::Error, "HTML file '#{merged_options[:html_file]}' not found or not readable"
121
127
  end
122
128
  end
123
129
 
@@ -182,6 +188,7 @@ module Bidi2pdf
182
188
 
183
189
  Bidi2pdf::Launcher.new(
184
190
  url: merged_options[:url],
191
+ inputfile: merged_options[:hmtl_file],
185
192
  output: merged_options[:output],
186
193
  cookies: parse_key_values(merged_options[:cookie]),
187
194
  headers: parse_key_values(merged_options[:header]),
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bidi2pdf"
4
+
5
+ module Bidi2pdf
6
+ module DSL
7
+ # rubocop: disable Metrics/AbcSize
8
+ def self.with_tab(remote_browser_url: nil, port: 0, headless: true, chrome_args: Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup)
9
+ manager = nil
10
+ session = nil
11
+ tab = nil
12
+
13
+ begin
14
+ session = if remote_browser_url
15
+ Bidi2pdf::Bidi::Session.new(
16
+ session_url: remote_browser_url,
17
+ headless: true, # remote is always headless
18
+ chrome_args: chrome_args
19
+ )
20
+ else
21
+ manager = Bidi2pdf::ChromedriverManager.new(port: port, headless: headless)
22
+ manager.start
23
+ manager.session
24
+ end
25
+
26
+ session.start
27
+ session.client.on_close { Bidi2pdf.logger.info "WebSocket session closed" }
28
+
29
+ browser = session.browser
30
+ context = browser.create_user_context
31
+ window = context.create_browser_window
32
+ tab = window.create_browser_tab
33
+
34
+ yield(tab)
35
+ ensure
36
+ tab&.close
37
+ window&.close
38
+ session&.close
39
+ manager&.stop
40
+ end
41
+ end
42
+
43
+ # rubocop: enable Metrics/AbcSize
44
+ end
45
+ end
@@ -7,9 +7,10 @@ require_relative "bidi/session"
7
7
  module Bidi2pdf
8
8
  class Launcher
9
9
  # rubocop:disable Metrics/ParameterLists
10
- def initialize(url:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
10
+ def initialize(url:, inputfile:, output:, cookies:, headers:, auth:, headless: true, port: 0, wait_window_loaded: false,
11
11
  wait_network_idle: false, print_options: {}, remote_browser_url: nil)
12
12
  @url = url
13
+ @inputfile = inputfile
13
14
  @port = port
14
15
  @headless = headless
15
16
  @output = output
@@ -30,6 +31,7 @@ module Bidi2pdf
30
31
  runner = SessionRunner.new(
31
32
  session: session,
32
33
  url: @url,
34
+ inputfile: @inputfile,
33
35
  output: @output,
34
36
  cookies: @cookies,
35
37
  headers: @headers,
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Bidi2pdf
4
4
  class SessionRunner
5
- def initialize(session:, url:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
5
+ def initialize(session:, url:, inputfile:, output:, cookies: {}, headers: {}, auth: {}, wait_window_loaded: false,
6
6
  wait_network_idle: false, print_options: {})
7
7
  @session = session
8
8
  @url = url
9
+ @inputfile = inputfile
9
10
  @output = output
10
11
  @cookies = cookies || {}
11
12
  @headers = headers || {}
@@ -75,7 +76,13 @@ module Bidi2pdf
75
76
  @session.status
76
77
  @session.user_contexts
77
78
 
78
- @tab.open_page(@url)
79
+ if @url
80
+ @tab.open_page(@url)
81
+ else
82
+ Bidi2pdf.logger.info "Loading HTML file #{@inputfile}"
83
+ data = File.read(@inputfile)
84
+ @tab.view_html_page(data)
85
+ end
79
86
 
80
87
  if @wait_network_idle
81
88
  Bidi2pdf.logger.info "Waiting for network idle"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bidi2pdf
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/bidi2pdf.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "bidi2pdf/utils"
4
4
  require_relative "bidi2pdf/process_tree"
5
5
  require_relative "bidi2pdf/launcher"
6
6
  require_relative "bidi2pdf/bidi/session"
7
+ require_relative "bidi2pdf/dsl"
7
8
 
8
9
  require "logger"
9
10
 
@@ -22,8 +23,10 @@ module Bidi2pdf
22
23
 
23
24
  class CmdTimeoutError < ClientError; end
24
25
 
26
+ class PrintError < Error; end
27
+
25
28
  @logger = Logger.new($stdout)
26
- @logger.level = Logger::DEBUG
29
+ @logger.level = Logger::INFO
27
30
 
28
31
  @default_timeout = 60
29
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bidi2pdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dieter S.
@@ -353,6 +353,7 @@ files:
353
353
  - lib/bidi2pdf/bidi/web_socket_dispatcher.rb
354
354
  - lib/bidi2pdf/chromedriver_manager.rb
355
355
  - lib/bidi2pdf/cli.rb
356
+ - lib/bidi2pdf/dsl.rb
356
357
  - lib/bidi2pdf/launcher.rb
357
358
  - lib/bidi2pdf/process_tree.rb
358
359
  - lib/bidi2pdf/session_runner.rb