twinshot 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+ require "json"
5
+ require "rbconfig"
6
+
7
+ module Twinshot
8
+ # Orchestrates a compare run: structure check first (cheap, sequential),
9
+ # then every common file through its adapter — in a pool of worker
10
+ # processes (libvips pixel compares are CPU-bound, so real processes beat
11
+ # threads under the GVL). Workers are fresh rubies exec'd via
12
+ # compare_worker.rb (~0.13s boot, in parallel): forking this process
13
+ # instead would be free, but once it has run any libvips operation
14
+ # (sequential group, fixtures, library usage) forked children can
15
+ # deadlock on their first image compare — idle libvips worker threads
16
+ # leave glib state that does not survive fork.
17
+ #
18
+ # Every configured group runs through the same pipeline in config order;
19
+ # each produced result carries its group name, and diff artifacts land in
20
+ # that group's report folder.
21
+ #
22
+ # Workers stream each finished result to the parent over a dedicated pipe
23
+ # (one pipe per worker, one JSON line per result), so progress callbacks
24
+ # fire in real time instead of after the whole run.
25
+ class Compare
26
+ class Error < StandardError; end
27
+
28
+ # A spawned worker: read its JSON result lines from #reader, then #finish
29
+ # (after drain saw EOF) and #crashed? to reap it.
30
+ Worker = Struct.new(:reader, :pid) do
31
+ def finish
32
+ reader.close unless reader.closed?
33
+ end
34
+
35
+ def crashed?
36
+ Process.wait(pid)
37
+ !Process.last_status.success?
38
+ end
39
+ end
40
+
41
+ MAX_WORKERS = 8
42
+
43
+ def initialize(config)
44
+ @config = config
45
+ end
46
+
47
+ # Returns the summary hash: { "total", "passed", "warned", "failed",
48
+ # "groups", "results" } — groups holds per-group stats plus the relative path to
49
+ # that group's report. progress (Twinshot::Progress or anything
50
+ # responding to #banner/#result) is optional; every produced result is
51
+ # announced to it in arrival order. groups limits the run to a subset of
52
+ # the configured ones (default: all, in config order).
53
+ def run(jobs: nil, progress: nil, groups: nil)
54
+ groups ||= @config.groups
55
+ results = []
56
+ group_stats = groups.map do |group|
57
+ group_results = run_group(group, jobs, progress)
58
+ results.concat(group_results)
59
+ {
60
+ "name" => group.name,
61
+ "total" => group_results.size,
62
+ "passed" => group_results.count { |r| r["status"] == "pass" },
63
+ "warned" => group_results.count { |r| r["status"] == "warn" },
64
+ "failed" => group_results.count { |r| r["status"] == "fail" },
65
+ "report" => File.join(Config::REPORT_DIRNAME, group.name, Config::HUMAN_REPORT)
66
+ }
67
+ end
68
+ summarize(results, group_stats)
69
+ end
70
+
71
+ private
72
+
73
+ def run_group(group, jobs, progress)
74
+ oracle_files = scan(group, "oracle")
75
+ current_files = scan(group, "current")
76
+ structure = Tree.diff(oracle_files, current_files)
77
+ # soft_check entries may legitimately exist on only one side (oracle or
78
+ # current): their missing/extra results are downgraded to warns in
79
+ # structure_results below, and they are never content-compared — a file
80
+ # on a single side has no pair to compare. Present on both sides, the
81
+ # content diff downgrades to a warn in soften/compare_worker.rb.
82
+ common = oracle_files & current_files
83
+ entries = common.to_h { |rel| [rel, Adapters.for(rel)] }
84
+ unknown = entries.filter_map { |rel, adapter| unknown_result(group, rel) if adapter.nil? }
85
+ comparable = entries.select { |_rel, adapter| adapter }
86
+ workers = resolve_workers(jobs, comparable.size)
87
+ total = common.size + structure["missing"].size + structure["extra"].size
88
+ ctx = Adapters::Ctx.new(max_diff_ratio: @config.max_diff_ratio, report_dir: @config.report_dir(group))
89
+
90
+ progress&.banner(@config, group: group, oracle_count: oracle_files.size, current_count: current_files.size,
91
+ total: total, workers: workers)
92
+ results = []
93
+ structure_results(group, structure).each do |r|
94
+ results << r
95
+ progress&.result(r)
96
+ end
97
+ unknown.each do |r|
98
+ results << r
99
+ progress&.result(r)
100
+ end
101
+
102
+ file_results = if workers > 1
103
+ parallel(group, ctx, comparable, workers, progress)
104
+ else
105
+ sequential(group, ctx, comparable, progress)
106
+ end
107
+ results.concat(file_results)
108
+ end
109
+
110
+ def scan(group, side)
111
+ path = side == "oracle" ? group.oracle_path : group.current_path
112
+ Tree.files(path)
113
+ rescue Errno::ENOENT
114
+ raise Config::Error, "#{side} path not found for group #{group.name}: #{path}"
115
+ end
116
+
117
+ # Structure results keep their missing/extra detail, but soft_check
118
+ # entries are warnings, not failures — a listed file may legitimately
119
+ # exist on only one side.
120
+ def structure_results(group, structure)
121
+ soften_structure = ->(rel) { @config.soft_check?(rel) ? "warn" : "fail" }
122
+ structure["missing"].map do |rel|
123
+ { "group" => group.name, "rel" => rel, "kind" => "structure", "status" => soften_structure.call(rel),
124
+ "detail" => "missing in current" }
125
+ end + structure["extra"].map do |rel|
126
+ { "group" => group.name, "rel" => rel, "kind" => "structure", "status" => soften_structure.call(rel),
127
+ "detail" => "extra in current (not in oracle)" }
128
+ end
129
+ end
130
+
131
+ def unknown_result(group, rel)
132
+ { "group" => group.name, "rel" => rel, "kind" => "unknown", "status" => "fail",
133
+ "detail" => "no adapter for #{File.extname(rel)}" }
134
+ end
135
+
136
+ def sequential(group, ctx, entries, progress)
137
+ entries.map do |rel, adapter|
138
+ run_one(group, ctx, rel, adapter).tap { |r| progress&.result(r) }
139
+ end
140
+ end
141
+
142
+ def run_one(group, ctx, rel, adapter)
143
+ result = adapter.call(rel, File.join(group.oracle_path, rel),
144
+ File.join(group.current_path, rel), ctx)
145
+ soften(result, rel)
146
+ result.merge("rel" => rel, "group" => group.name)
147
+ rescue StandardError => e
148
+ { "group" => group.name, "rel" => rel, "kind" => "error", "status" => "fail",
149
+ "detail" => "#{e.class}: #{e.message}" }
150
+ end
151
+
152
+ # soft_check paths tolerate content diffs: fail → warn, so the run stays
153
+ # green while the reports still show the diff. Mirrored in
154
+ # compare_worker.rb. Adapter crashes (kind "error") are not softened —
155
+ # that is not "the time changed".
156
+ def soften(result, rel)
157
+ result["status"] = "warn" if result["status"] == "fail" && @config.soft_check?(rel)
158
+ end
159
+
160
+ def parallel(group, ctx, entries, workers, progress)
161
+ spawned = spawn_workers(group, ctx, entries, workers)
162
+ results = drain(spawned.map(&:reader), progress)
163
+ spawned.each(&:finish)
164
+ crashed = spawned.any?(&:crashed?)
165
+ raise Error, "a compare worker crashed" if crashed
166
+ raise Error, "workers produced #{results.size}/#{entries.size} results" if results.size != entries.size
167
+
168
+ results
169
+ end
170
+
171
+ def spawn_workers(group, ctx, entries, workers)
172
+ script = File.expand_path("compare_worker.rb", __dir__)
173
+ libdir = File.expand_path("..", __dir__)
174
+ base = {
175
+ "group" => group.name,
176
+ "oracle" => group.oracle_path,
177
+ "current" => group.current_path,
178
+ "max_diff_ratio" => ctx.max_diff_ratio,
179
+ "report_dir" => ctx.report_dir,
180
+ "soft_check" => @config.soft_check,
181
+ "rels" => entries.map(&:first)
182
+ }
183
+ Array.new(workers) do |index|
184
+ payload_rd, payload_wr = IO.pipe
185
+ out_rd, out_wr = IO.pipe
186
+ pid = Process.spawn(RbConfig.ruby, "-I", libdir, script, in: payload_rd, out: out_wr)
187
+ payload_wr.write(JSON.generate(base.merge("index" => index, "workers" => workers)))
188
+ payload_rd.close
189
+ payload_wr.close
190
+ out_wr.close
191
+ Worker.new(out_rd, pid)
192
+ end
193
+ end
194
+
195
+ # Read newline-delimited JSON results from all worker pipes until every
196
+ # writer exits, announcing each result to progress as it arrives.
197
+ def drain(readers, progress)
198
+ results = []
199
+ buffers = {}
200
+ readers.each { |rd| buffers[rd] = +"" }
201
+ until readers.empty?
202
+ ready, = IO.select(readers)
203
+ ready.each do |io|
204
+ chunk = io.read_nonblock(65_536, exception: false)
205
+ case chunk
206
+ when :wait_readable then next
207
+ when nil, :eof # ruby 4.0 returns nil at EOF with exception: false
208
+ readers.delete(io)
209
+ io.close
210
+ else
211
+ buffer = buffers[io] << chunk
212
+ while (newline = buffer.index("\n"))
213
+ line = buffer.slice!(0..newline)
214
+ result = JSON.parse(line)
215
+ results << result
216
+ progress&.result(result)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ results
222
+ end
223
+
224
+ def resolve_workers(jobs, work_size)
225
+ return 1 if work_size < 2
226
+ return 1 if jobs && jobs <= 1
227
+
228
+ [jobs || Etc.nprocessors, MAX_WORKERS, work_size].min
229
+ end
230
+
231
+ def summarize(results, group_stats)
232
+ order = group_stats.each_with_index.to_h { |g, i| [g["name"], i] }
233
+ # fail first, then warn, then pass — the statuses the report is opened
234
+ # for lead their directory.
235
+ rank = { "fail" => 0, "warn" => 1 }
236
+ sorted = results.sort_by do |r|
237
+ [order[r["group"]], File.dirname(r["rel"]), rank.fetch(r["status"], 2), File.basename(r["rel"])]
238
+ end
239
+ {
240
+ "total" => sorted.size,
241
+ "passed" => sorted.count { |r| r["status"] == "pass" },
242
+ "warned" => sorted.count { |r| r["status"] == "warn" },
243
+ "failed" => sorted.count { |r| r["status"] == "fail" },
244
+ "groups" => group_stats,
245
+ "results" => sorted
246
+ }
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exec'd compare worker. Compare's worker pool is a set of fresh rubies
4
+ # running this script: forking the parent would be free, but once it has run
5
+ # any libvips operation (sequential group, fixtures, library usage) forked
6
+ # children can deadlock on their first image compare — idle libvips worker
7
+ # threads leave glib state that does not survive fork.
8
+ #
9
+ # Reads one JSON blob on stdin:
10
+ #
11
+ # { "group": "default", "oracle": "...", "current": "...", "rels": ["a.png"],
12
+ # "max_diff_ratio": 0.002, "report_dir": "...", "soft_check": ["b.png"],
13
+ # "index": 0, "workers": 4 }
14
+ #
15
+ # and writes one JSON result line per its slice of rels to stdout — the same
16
+ # protocol the forked workers use. Result construction mirrors
17
+ # Compare#run_one; keep the two in sync.
18
+
19
+ require "json"
20
+ require_relative "adapters"
21
+ require_relative "soft_check"
22
+
23
+ payload = JSON.parse($stdin.read)
24
+ ctx = Twinshot::Adapters::Ctx.new(max_diff_ratio: payload.fetch("max_diff_ratio"),
25
+ report_dir: payload.fetch("report_dir"))
26
+ group = payload.fetch("group")
27
+ oracle = payload.fetch("oracle")
28
+ current = payload.fetch("current")
29
+ soft_check = payload.fetch("soft_check")
30
+ index = payload.fetch("index")
31
+ workers = payload.fetch("workers")
32
+ $stdout.sync = true
33
+
34
+ payload.fetch("rels").each_with_index do |rel, i|
35
+ next unless i % workers == index
36
+
37
+ result = begin
38
+ r = Twinshot::Adapters.for(rel).call(rel, File.join(oracle, rel), File.join(current, rel), ctx)
39
+ .merge("rel" => rel, "group" => group)
40
+ r["status"] = "warn" if r["status"] == "fail" && Twinshot::SoftCheck.match?(soft_check, rel)
41
+ r
42
+ rescue StandardError => e
43
+ { "group" => group, "rel" => rel, "kind" => "error", "status" => "fail",
44
+ "detail" => "#{e.class}: #{e.message}" }
45
+ end
46
+ $stdout.write("#{JSON.generate(result)}\n")
47
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ require_relative "soft_check"
6
+
7
+ module Twinshot
8
+ # Minimal twinshot.yml:
9
+ #
10
+ # oracle: { path: artifacts/rails, generate: bin/record }
11
+ # current: { path: artifacts/crails, generate: bin/record --crails }
12
+ # png: { max_diff_ratio: 0.002 }
13
+ # soft_check: [dashboard/header.png] # content diffs there → warnings
14
+ # groups:
15
+ # dogs:
16
+ # oracle: { path: artifacts/dogs-rails }
17
+ # current: { path: artifacts/dogs-crails }
18
+ #
19
+ # The top-level oracle/current pair is the unnamed default group; groups:
20
+ # adds named groups (arbitrary names) with the same shape. Each group gets
21
+ # its own report folder twinshot-report/<name>/ — not configurable.
22
+ class Config
23
+ class Error < StandardError; end
24
+
25
+ # A named pair of artifact trees: oracle/current paths plus optional
26
+ # generate commands. label prefixes error messages ("" for the default
27
+ # group, "groups.<name>" for named ones).
28
+ class Group
29
+ attr_reader :name, :data, :root, :label
30
+
31
+ def initialize(name, data, root, file, label)
32
+ @name = name
33
+ @data = data
34
+ @root = root
35
+ @file = file
36
+ @label = label
37
+ end
38
+
39
+ def oracle_path
40
+ side_path("oracle")
41
+ end
42
+
43
+ def current_path
44
+ side_path("current")
45
+ end
46
+
47
+ def generate_command(side)
48
+ raise Error, "unknown side: #{side.inspect} (expected oracle or current)" unless SIDES.include?(side)
49
+
50
+ value = @data.dig(side, "generate")
51
+ case value
52
+ when nil then nil
53
+ when String then value
54
+ else raise Error, "#{prefix}#{side}.generate must be a shell command string"
55
+ end
56
+ end
57
+
58
+ def validate!
59
+ raise Error, "#{label.empty? ? "group" : label} must be a mapping" unless @data.is_a?(Hash)
60
+
61
+ oracle_path
62
+ current_path
63
+ SIDES.each { |side| generate_command(side) }
64
+ end
65
+
66
+ private
67
+
68
+ def prefix
69
+ @label.empty? ? "" : "#{@label}."
70
+ end
71
+
72
+ def side_path(side)
73
+ value = @data.dig(side, "path")
74
+ raise Error, "#{prefix}#{side}.path is required in #{@file}" unless value.is_a?(String) && !value.empty?
75
+
76
+ File.expand_path(value, @root)
77
+ end
78
+ end
79
+
80
+ DEFAULT_FILENAME = "twinshot.yml"
81
+ DEFAULT_MAX_DIFF_RATIO = 0.002
82
+ REPORT_DIRNAME = "twinshot-report"
83
+ HUMAN_REPORT = "report_for_human.html"
84
+ AGENT_REPORT = "report_for_agent.md"
85
+ DEFAULT_GROUP = "default"
86
+ SIDES = %w[oracle current].freeze
87
+
88
+ attr_reader :path, :root
89
+
90
+ def self.load(path = nil)
91
+ path = File.expand_path(path || DEFAULT_FILENAME)
92
+ raise Error, "config not found: #{path}" unless File.file?(path)
93
+
94
+ data = begin
95
+ YAML.safe_load(File.read(path), aliases: true) || {}
96
+ rescue Psych::SyntaxError => e
97
+ raise Error, "invalid YAML in #{path}: #{e.message}"
98
+ end
99
+ new(path, data)
100
+ end
101
+
102
+ def initialize(path, data)
103
+ @path = path
104
+ @root = File.dirname(path)
105
+ @data = data
106
+ validate!
107
+ end
108
+
109
+ # All groups in run order: the unnamed default first, then named ones
110
+ # in declaration order.
111
+ def groups
112
+ @groups ||= [default_group, *named_groups]
113
+ end
114
+
115
+ def group(name)
116
+ groups.find { |g| g.name == name } ||
117
+ raise(Error, "unknown group: #{name.inspect} (expected #{groups.map(&:name).join(", ")})")
118
+ end
119
+
120
+ # Default-group delegates (compatibility with the pre-groups API).
121
+ def oracle_path
122
+ default_group.oracle_path
123
+ end
124
+
125
+ def current_path
126
+ default_group.current_path
127
+ end
128
+
129
+ def generate_command(side)
130
+ default_group.generate_command(side)
131
+ end
132
+
133
+ def max_diff_ratio
134
+ value = @data.dig("png", "max_diff_ratio") || DEFAULT_MAX_DIFF_RATIO
135
+ Float(value)
136
+ rescue ArgumentError, TypeError
137
+ raise Error, "png.max_diff_ratio must be a number, got: #{value.inspect}"
138
+ end
139
+
140
+ # Exact tree-relative file paths or glob patterns (the same list applies
141
+ # to every group) whose diffs are tolerated — snapshots with a clock,
142
+ # dates or other dynamic content, and files that may legitimately exist
143
+ # on only one side. Content diffs come back as "warn": shown and counted
144
+ # in the reports, but they never fail the run. Missing/extra structure
145
+ # results are warns too. Glob entries ("code/capybara-tests/*") match
146
+ # nested files and dotfiles — see SoftCheck.
147
+ def soft_check
148
+ paths = case @data["soft_check"]
149
+ when nil then []
150
+ when Array then @data["soft_check"]
151
+ else raise Error,
152
+ "soft_check must be a list of paths or glob patterns, got: #{@data["soft_check"].inspect}"
153
+ end
154
+ unless paths.all? { |path| path.is_a?(String) && !path.empty? }
155
+ raise Error, "soft_check entries must be non-empty paths or glob patterns"
156
+ end
157
+
158
+ paths
159
+ end
160
+
161
+ def soft_check?(rel)
162
+ SoftCheck.match?(soft_check, rel)
163
+ end
164
+
165
+ def report_root
166
+ File.join(@root, REPORT_DIRNAME)
167
+ end
168
+
169
+ # Each group reports into twinshot-report/<name>/.
170
+ def report_dir(group = nil)
171
+ File.join(report_root, group ? group.name : DEFAULT_GROUP)
172
+ end
173
+
174
+ private
175
+
176
+ def default_group
177
+ Group.new(DEFAULT_GROUP,
178
+ { "oracle" => @data["oracle"], "current" => @data["current"] },
179
+ @root, File.basename(@path), "")
180
+ end
181
+
182
+ def named_groups
183
+ groups_data = @data["groups"] || {}
184
+ raise Error, "groups must be a mapping of name to oracle/current sides" unless groups_data.is_a?(Hash)
185
+
186
+ groups_data.map do |name, data|
187
+ unless name.is_a?(String) && !name.empty?
188
+ raise Error, "group names must be non-empty strings, got: #{name.inspect}"
189
+ end
190
+ if name == DEFAULT_GROUP
191
+ raise Error, "group name #{DEFAULT_GROUP.inspect} is reserved for the unnamed top-level group"
192
+ end
193
+ if name == "all" || SIDES.include?(name)
194
+ raise Error, "group name #{name.inspect} is reserved by the CLI selectors (all, oracle, current)"
195
+ end
196
+
197
+ Group.new(name, data, @root, File.basename(@path), "groups.#{name}")
198
+ end
199
+ end
200
+
201
+ def validate!
202
+ raise Error, "config root must be a mapping" unless @data.is_a?(Hash)
203
+
204
+ @data.each do |key, value|
205
+ raise Error, "#{key} must be a mapping" if key != "png" && key != "soft_check" && !value.is_a?(Hash)
206
+ end
207
+ groups.each(&:validate!)
208
+ max_diff_ratio
209
+ soft_check
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <g>
3
+ <path style="fill:#000000;stroke-width:1.26985" d="m 232.34261,415.99643 c -3.78909,0.0787 -9.04242,0.0141 -11.67409,-0.14366 -2.63165,-0.1578 -7.55543,-0.41574 -10.94171,-0.57319 -3.38629,-0.15745 -9.71293,-0.82153 -14.05923,-1.47573 -4.34629,-0.65419 -9.99952,-1.59744 -12.56272,-2.09608 -2.5632,-0.49866 -8.03405,-1.74195 -12.15746,-2.76286 -4.1234,-1.02092 -9.22955,-2.33315 -11.34697,-2.91606 -2.11742,-0.5829 -5.5823,-1.59142 -7.69973,-2.24113 -2.11742,-0.6497 -5.94702,-1.98356 -8.51022,-2.96412 -2.56319,-0.98055 -6.66635,-2.53296 -9.1181,-3.44977 -2.45176,-0.91681 -6.6658,-2.59238 -9.36455,-3.7235 -2.69874,-1.13111 -8.16961,-3.73448 -12.15747,-5.78527 -3.98786,-2.05078 -9.3478,-4.85437 -11.911,-6.2302 -2.563205,-1.37582 -5.936895,-3.22626 -7.497105,-4.11207 -1.56021,-0.88581 -5.38981,-3.21429 -8.51023,-5.1744 -3.120409,-1.96012 -6.950017,-4.48841 -8.510221,-5.61843 -1.560204,-1.13001 -4.516929,-3.2221 -6.570496,-4.64908 -2.053568,-1.42697 -5.609632,-4.06616 -7.902359,-5.86486 -2.292725,-1.79871 -4.64611,-3.686 -5.229742,-4.19401 -0.583633,-0.50799 -2.759916,-2.3391 -4.836186,-4.06912 -2.07627,-1.73002 -5.06363,-4.36111 -6.638578,-5.84686 -1.574949,-1.48575 -4.534574,-4.45028 -6.576946,-6.58782 -2.042372,-2.13756 -5.847267,-6.39511 -8.455321,-9.46124 -2.608056,-3.06612 -5.354167,-6.50801 -6.102468,-7.64864 -0.748301,-1.14062 -1.286727,-2.15026 -1.196502,-2.24364 0.09022,-0.0933 1.237225,0.7455 2.548885,1.86418 1.311661,1.11866 3.825536,3.26067 5.58639,4.76003 1.760854,1.49934 4.324091,3.76411 5.696081,5.0328 1.371989,1.26868 3.215142,2.90302 4.095894,3.63186 0.880752,0.72885 3.137737,2.35703 5.015521,3.61818 1.877784,1.26116 5.098374,3.6845 7.156864,5.38523 2.058491,1.70072 5.85794,4.60206 8.443217,6.44743 2.585277,1.84534 6.919008,4.8456 9.630498,6.66721 2.711504,1.82161 6.206531,4.06362 7.766736,4.98225 1.560217,0.91862 5.116273,3.12778 7.902353,4.90924 2.78608,1.78147 5.93985,3.6374 7.00837,4.12428 1.06852,0.48689 2.43624,1.24423 3.03937,1.68298 0.60314,0.43873 3.923225,2.30006 7.377965,4.13628 3.45474,1.83622 8.4697,4.33814 11.14433,5.55981 2.67464,1.22169 7.55951,3.53159 10.85526,5.13312 3.29576,1.60152 8.03717,3.6658 10.53648,4.58731 2.49931,0.9215 7.73553,2.80521 11.63605,4.18605 3.90051,1.38083 9.46256,3.26033 12.36008,4.17666 2.89754,0.91632 6.72713,2.12268 8.51024,2.6808 1.78308,0.5581 5.70386,1.60754 8.71284,2.33205 3.00898,0.72452 6.38267,1.56776 7.4971,1.87383 1.11444,0.30609 3.48515,0.78306 5.26824,1.05995 1.78309,0.27688 4.6097,0.86608 6.28135,1.30932 1.67166,0.44324 5.50126,1.16637 8.51023,1.60696 3.00897,0.44059 7.15578,1.1903 9.21513,1.66602 2.05933,0.47572 4.6124,0.8653 5.67348,0.86572 1.06107,4.4e-4 4.48228,0.2976 7.6027,0.66036 3.12041,0.36278 7.13238,0.83122 8.91547,1.04099 1.7831,0.20976 5.97742,0.56698 9.32073,0.7938 3.34331,0.22682 6.76259,0.41858 7.59842,0.42613 0.83582,0.008 1.51968,0.18232 1.51968,0.38834 0,0.20602 -1.13976,0.58775 -2.5328,0.84828 -1.39305,0.26054 -4.99469,0.6486 -8.00367,0.86238 -3.00896,0.21376 -8.57101,0.45313 -12.36008,0.53191 z m 23.90966,-24.68435 c -8.69258,0.0631 -18.99602,-0.031 -22.89655,-0.2091 -3.90052,-0.17816 -8.55074,-0.51598 -10.33384,-0.7507 -1.78309,-0.23473 -5.31528,-0.71071 -7.84928,-1.05776 -2.534,-0.34704 -7.36661,-1.20782 -10.73909,-1.91284 -3.3725,-0.70502 -8.13779,-1.75072 -10.58954,-2.32376 -2.45177,-0.57304 -5.09601,-1.16072 -5.87612,-1.30595 -0.78011,-0.14522 -4.24498,-1.06757 -7.69973,-2.04966 -3.45474,-0.98208 -10.38449,-3.02486 -15.39946,-4.53951 -5.01495,-1.51467 -11.12407,-3.58326 -13.57583,-4.59689 -2.45175,-1.0136 -5.18718,-2.08469 -6.07873,-2.38015 -0.89155,-0.29549 -3.17108,-1.18648 -5.0656,-1.97999 -1.89455,-0.79353 -4.90353,-2.10936 -6.68662,-2.9241 -1.7831,-0.81472 -3.55864,-1.64987 -3.94567,-1.85586 -0.38704,-0.20599 -0.87491,-0.37452 -1.0842,-0.37452 -0.20928,0 -3.26631,-1.47046 -6.79342,-3.2677 -3.5271,-1.79722 -10.06014,-5.22342 -14.51788,-7.61378 -4.45773,-2.39036 -8.378515,-4.57214 -8.712845,-4.84843 -0.33433,-0.27628 -2.22068,-1.47395 -4.1919,-2.66151 -1.9712,-1.18754 -3.68049,-2.15919 -3.79839,-2.15919 -0.11797,0 -1.33172,-0.82354 -2.69735,-1.83007 -1.36562,-1.00655 -6.950814,-4.91785 -12.411548,-8.69179 -5.460721,-3.77392 -10.110955,-7.00323 -10.333842,-7.17622 -0.222886,-0.173 -1.590601,-1.29788 -3.039365,-2.49972 -1.448765,-1.20183 -3.090024,-2.64636 -3.64724,-3.21006 -0.557217,-0.56369 -1.65139,-1.51918 -2.431493,-2.12331 -0.780105,-0.60412 -2.877268,-2.43495 -4.660361,-4.06853 -1.783096,-1.63357 -3.880258,-3.59451 -4.660363,-4.35766 -0.780104,-0.76316 -2.056637,-1.87344 -2.836741,-2.4673 -0.780105,-0.59387 -2.775442,-2.31369 -4.434083,-3.8218 -1.658643,-1.50811 -6.323338,-6.13916 -10.365992,-10.29123 -4.042653,-4.15204 -8.318494,-8.77593 -9.501868,-10.27529 -1.183374,-1.49935 -4.177884,-4.95822 -6.654463,-7.68638 -2.476578,-2.72817 -4.7092235,-5.37039 -4.9614335,-5.87162 -0.25221,-0.50121 -0.581278,-1.5032 -0.731263,-2.22664 -0.19845,-0.95721 -0.04768,-1.69945 0.553804,-2.72609 0.454571,-0.77593 2.9719805,-3.68016 5.5942395,-6.45384 2.622269,-2.7737 5.804393,-6.17084 7.071392,-7.5492 1.266999,-1.37837 3.623153,-3.73287 5.235899,-5.23223 1.612746,-1.49934 3.153544,-2.82045 3.423996,-2.93579 0.270453,-0.1153 2.290985,-1.9315 4.490074,-4.03594 2.199087,-2.10443 4.545426,-4.29394 5.214087,-4.86557 0.66866,-0.57163 2.218737,-1.83585 3.444615,-2.8094 1.225877,-0.97353 3.23186,-2.53676 4.457737,-3.47383 1.225878,-0.93707 2.411232,-1.92489 2.634118,-2.19519 0.222887,-0.27029 1.955326,-1.64897 3.849863,-3.06377 1.894539,-1.41478 3.991702,-3.02783 4.660362,-3.58456 0.668661,-0.55673 3.495272,-2.57108 6.281358,-4.47636 2.786087,-1.90526 5.795054,-3.9826 6.686604,-4.61629 0.891552,-0.6337 3.626979,-2.42229 6.078734,-3.97463 2.451755,-1.55234 5.096007,-3.24842 5.876103,-3.76907 0.780108,-0.52066 4.51853,-2.7543 8.3076,-4.96366 3.78908,-2.20935 7.43632,-4.26166 8.10499,-4.56068 0.66865,-0.29903 4.589445,-2.37424 8.712835,-4.6116 4.12341,-2.23734 10.8708,-5.51105 14.99421,-7.27491 4.1234,-1.76385 9.41191,-4.03755 11.75222,-5.05264 2.34031,-1.0151 4.98456,-2.11153 5.8761,-2.43651 0.89155,-0.32499 3.35344,-1.20282 5.47086,-1.95074 2.11742,-0.74793 5.21758,-1.87816 6.88924,-2.51163 1.67164,-0.63346 4.40707,-1.57305 6.07872,-2.08798 1.67166,-0.51491 4.49826,-1.37248 6.28137,-1.90569 1.78308,-0.53323 6.61568,-1.91724 10.73908,-3.07561 4.12342,-1.15836 9.5031,-2.51161 11.95486,-3.00724 2.45174,-0.49561 6.73725,-1.45347 9.52334,-2.12857 2.78608,-0.67511 5.72996,-1.22634 6.54193,-1.22498 0.812,8.9e-4 2.81797,-0.26514 4.45775,-0.59221 1.63977,-0.32709 4.34911,-0.79526 6.02077,-1.04038 1.67165,-0.24513 5.50124,-0.63392 8.51023,-0.86398 3.00897,-0.23006 8.20628,-0.60438 11.54959,-0.83185 3.3433,-0.22746 9.13329,-0.53481 12.86665,-0.68298 l 6.78792,-0.26942 v -3.87268 c -0.0463,-5.9927 -0.30901,-11.98096 -0.53718,-17.96877 l -8.1388,0.29634 c -4.47634,0.16297 -12.42498,0.57151 -17.66367,0.90786 -5.23869,0.33634 -10.98378,0.79278 -12.76687,1.01432 -1.7831,0.22152 -5.70388,0.77287 -8.71285,1.2252 -3.00897,0.45233 -7.29448,1.11925 -9.52335,1.48202 -2.22887,0.36279 -5.4202,0.9532 -7.09186,1.31205 -1.67165,0.35883 -5.13653,1.12116 -7.69972,1.69407 -2.5632,0.57289 -6.48398,1.52026 -8.71285,2.10524 -2.22887,0.58499 -7.69973,2.10071 -12.15747,3.36825 -4.45773,1.26753 -12.2993,3.77952 -17.42569,5.58217 -5.12641,1.80268 -11.50907,4.13398 -14.18372,5.18068 -2.67464,1.04671 -7.1425,2.8656 -9.92859,4.04196 -2.78609,1.17637 -10.26293,4.78708 -16.61519,8.02378 -6.352285,3.2367 -14.467395,7.60676 -18.033585,9.71122 -3.56619,2.10447 -10.040038,6.08833 -14.386324,8.85304 -4.346298,2.76472 -8.99653,5.81248 -10.333851,6.77285 -1.337321,0.96036 -4.024087,2.90143 -5.970588,4.3135 -1.946503,1.41206 -5.502562,4.09274 -7.902354,5.95708 -2.399791,1.86433 -5.001524,3.91299 -5.781626,4.55257 -0.780105,0.63959 -2.877268,2.32369 -4.660363,3.74245 -1.783095,1.41875 -4.836178,3.95404 -6.784631,5.63396 -1.948453,1.67993 -5.379582,4.63064 -7.624731,6.55713 -2.245149,1.92649 -4.163774,3.41818 -4.263615,3.31485 -0.09984,-0.10337 0.504677,-1.09909 1.343372,-2.21282 0.838696,-1.11372 3.276945,-3.92652 5.418334,-6.25064 2.141388,-2.32414 6.725095,-7.15102 10.186013,-10.7264 3.460919,-3.57539 7.925391,-8.05771 9.921048,-9.96072 1.995659,-1.90302 4.307129,-3.97904 5.136602,-4.6134 0.829474,-0.63435 2.896847,-2.28577 4.594162,-3.66977 1.697315,-1.38403 5.196772,-4.16085 7.776568,-6.17075 2.579798,-2.00989 7.243613,-5.42069 10.364022,-7.57954 3.120421,-2.15888 7.497107,-5.0244 9.725978,-6.36785 2.228874,-1.34342 4.700554,-2.88679 5.492634,-3.42968 0.79209,-0.5429 1.57075,-0.98708 1.73036,-0.98708 0.15962,0 1.01247,-0.47187 1.89522,-1.04858 0.88275,-0.57672 4.84549,-2.72078 8.80611,-4.76461 3.960615,-2.04383 9.495415,-4.85634 12.299585,-6.25004 2.80416,-1.3937 6.63376,-3.20846 8.51022,-4.03279 1.87646,-0.82436 6.51189,-2.75161 10.30097,-4.2828 3.78908,-1.53119 9.35111,-3.62868 12.36009,-4.66108 3.00897,-1.0324 9.30046,-3.06537 13.98109,-4.51768 4.68062,-1.45232 12.24864,-3.59758 16.81782,-4.76726 4.56919,-1.16967 11.04304,-2.68928 14.38634,-3.3769 3.34329,-0.68763 9.72597,-1.8218 14.18371,-2.52038 4.45774,-0.69857 8.9256,-1.36316 9.9286,-1.47684 1.00299,-0.11365 3.37369,-0.39554 5.26823,-0.62635 1.89453,-0.2308 6.08887,-0.622 9.32072,-0.86932 3.23187,-0.24731 12.80586,-0.6132 21.27556,-0.81306 9.42201,-0.22235 18.38819,-0.20795 23.09919,0.0371 4.23484,0.22028 8.52035,0.46041 9.52334,0.53364 1.003,0.0733 3.82961,0.26477 6.28136,0.42562 2.45175,0.16084 6.28136,0.55121 8.51023,0.86748 2.22886,0.31625 6.78791,0.98056 10.13121,1.47623 3.34331,0.49567 9.27008,1.55397 13.1706,2.35175 3.90051,0.7978 8.64193,1.82873 10.53646,2.29095 1.89454,0.46223 5.4506,1.326 7.90236,1.91949 2.45175,0.59348 6.5496,1.63286 9.10633,2.30971 2.55671,0.67684 4.83624,1.38607 5.0656,1.57604 0.22936,0.18999 1.0553,0.48259 1.83539,0.65022 0.78011,0.16764 3.97145,1.13746 7.09187,2.15517 3.1204,1.01771 6.03821,2.02114 6.48398,2.22984 0.44576,0.20871 2.08702,0.89416 3.64723,1.52324 1.56021,0.62907 4.93391,1.94632 7.4971,2.92722 2.5632,0.98091 5.84572,2.25309 7.29448,2.82707 1.44876,0.57398 4.27537,1.78978 6.28136,2.70181 2.00598,0.91201 4.83259,2.19095 6.28136,2.84207 1.44875,0.65111 6.64608,3.29117 11.5496,5.86678 4.9035,2.57561 10.28319,5.55196 11.95483,6.61408 1.67165,1.06215 4.68063,2.89398 6.68661,4.07074 2.00598,1.17677 4.2855,2.53165 5.06561,3.01082 0.7801,0.4792 2.60372,1.66385 4.05247,2.63259 1.44878,0.96873 4.18421,2.78271 6.07874,4.03108 1.89455,1.24836 4.87944,3.4053 6.6331,4.79319 1.75367,1.3879 3.85083,3.04029 4.66037,3.67198 0.80953,0.63167 2.01896,1.51335 2.68762,1.95927 0.66867,0.44591 1.65073,1.15584 2.18239,1.57759 0.53165,0.42175 1.71701,1.44971 2.63412,2.28436 0.91711,0.83464 3.4911,3.06916 5.71996,4.96559 2.22887,1.89644 6.85188,6.11494 10.27338,9.37446 3.42149,3.2595 6.58314,6.40379 7.02587,6.9873 0.44275,0.58349 2.2024,2.58503 3.91034,4.44784 1.70794,1.8628 4.3368,4.9606 5.84192,6.88396 1.5051,1.92338 2.64856,3.5881 2.54104,3.69941 -0.10756,0.11123 -0.8831,-0.28347 -1.72345,-0.87726 -0.84036,-0.59379 -2.89564,-2.22507 -4.56729,-3.62509 -1.67164,-1.40001 -4.05083,-3.25719 -5.28705,-4.12707 -1.23625,-0.86987 -3.76301,-2.80489 -5.61505,-4.30003 -1.85205,-1.49515 -4.30547,-3.49032 -5.45204,-4.43372 -1.14657,-0.9434 -4.45539,-3.47829 -7.35291,-5.63309 -2.89755,-2.15479 -7.3654,-5.41669 -9.9286,-7.24866 -2.5632,-1.83198 -5.20843,-3.63788 -5.87826,-4.01312 -0.66985,-0.37523 -3.58764,-2.30742 -6.48398,-4.29374 -2.89636,-1.98631 -7.27207,-4.93297 -9.72382,-6.54811 -2.45176,-1.61515 -6.19018,-3.93489 -8.30761,-5.155 -2.11743,-1.2201 -5.03521,-2.92323 -6.48398,-3.78473 -1.44875,-0.8615 -6.00781,-3.33712 -10.13123,-5.50138 -4.12339,-2.16426 -8.86481,-4.5757 -10.53646,-5.35876 -1.67165,-0.78305 -4.40708,-2.06648 -6.07873,-2.85207 -1.67166,-0.7856 -4.13354,-1.87407 -5.47086,-2.41884 -1.33733,-0.54477 -4.16393,-1.78141 -6.28135,-2.7481 -2.11743,-0.9667 -7.49711,-3.07016 -11.95485,-4.67438 -4.45773,-1.60421 -9.10797,-3.26012 -10.33385,-3.67979 -1.22587,-0.41966 -4.14366,-1.37501 -6.48398,-2.12299 -2.34031,-0.74798 -4.8022,-1.50079 -5.47085,-1.67292 -0.66867,-0.17211 -3.22173,-0.92464 -5.67348,-1.67227 -2.45176,-0.74762 -5.64309,-1.62424 -7.09187,-1.94802 -1.44876,-0.32379 -4.70235,-0.96023 -7.2302,-1.4143 -2.52785,-0.45409 -5.53682,-1.10526 -6.6866,-1.44704 -1.14979,-0.34179 -4.18768,-0.98869 -6.75088,-1.43755 -2.5632,-0.44885 -6.21045,-1.10197 -8.10498,-1.45137 -1.89454,-0.3494 -6.90949,-1.02368 -11.14434,-1.49841 -4.23486,-0.47473 -9.53408,-1.0463 -11.77608,-1.27018 -2.24199,-0.22386 -8.88053,-0.60219 -14.75541,-0.69776 l -15.04599,-0.24476 c 0.25797,7.75023 0.44739,14.42667 0.4897,21.94529 l 7.32946,-0.046 c 1.60411,-0.0102 4.93224,0.18351 7.39579,0.40781 2.46356,0.22429 6.9411,0.61078 9.95006,0.85886 3.00898,0.24809 7.11212,0.73413 9.1181,1.08009 2.00599,0.34595 6.56504,1.096 10.13123,1.66677 3.56618,0.57077 8.85468,1.51565 11.75221,2.09976 2.89752,0.58411 6.45359,1.31863 7.90236,1.63227 1.44876,0.31366 4.54892,1.16447 6.88923,1.89069 2.34031,0.72625 4.89338,1.48118 5.67348,1.67762 0.7801,0.19646 4.70089,1.22232 8.71284,2.27968 4.01197,1.05738 9.1181,2.64712 11.34697,3.53278 2.22887,0.88565 6.42319,2.49693 9.32073,3.58061 2.89753,1.08368 5.99768,2.32015 6.88923,2.74774 0.89155,0.42759 2.71516,1.20666 4.05248,1.73126 1.33733,0.52462 4.12347,1.77231 6.19141,2.77265 2.06795,1.00034 3.84997,1.8188 3.96005,1.8188 0.11009,0 1.79181,0.77688 3.7372,1.7264 1.94537,0.94951 7.73136,3.8831 12.85777,6.51908 5.1264,2.63598 11.50907,5.98305 14.18371,7.43794 2.67464,1.45491 6.04833,3.36708 7.49711,4.24929 1.44876,0.8822 3.91065,2.33391 5.47085,3.226 1.56021,0.89211 3.47978,2.03851 4.26572,2.5476 0.78593,0.50907 2.42719,1.54744 3.64724,2.3075 1.22004,0.76005 2.57607,1.60933 3.01341,1.88729 0.43733,0.27797 2.94128,1.92087 5.56436,3.65089 2.62307,1.73003 6.36839,4.14622 8.32295,5.3693 1.95455,1.2231 6.10681,4.08673 9.22722,6.36363 3.12042,2.27691 6.27983,4.57897 7.0209,5.11571 0.74107,0.53672 2.0176,1.59634 2.83673,2.35469 0.81913,0.75836 2.05779,1.66442 2.75255,2.0135 0.69476,0.34905 2.51104,1.71024 4.03616,3.02485 1.52512,1.31462 4.6951,3.99622 7.04439,5.9591 2.34929,1.96288 6.15021,5.14673 8.44645,7.07523 2.29626,1.92849 5.9671,5.22947 8.15738,7.33551 2.19031,2.10604 4.46122,4.42375 5.04646,5.15045 0.58525,0.72671 1.17246,1.882 1.30489,2.56733 0.13928,0.72076 0.0102,1.68565 -0.30598,2.2888 -0.30075,0.5735 -1.18507,1.85396 -1.96518,2.84546 -0.7801,0.99153 -2.51254,3.11373 -3.84986,4.716 -1.33732,1.60227 -3.27358,3.97912 -4.30281,5.28187 -1.02921,1.30277 -5.67132,6.23762 -10.31579,10.96636 -4.64448,4.72873 -10.87252,10.79598 -13.84009,13.48278 -2.96757,2.68681 -7.7171,6.85563 -10.55451,9.26408 -2.8374,2.40844 -6.52665,5.43722 -8.19829,6.73062 -1.67166,1.29341 -4.04236,3.12289 -5.26824,4.06552 -1.22587,0.94264 -4.03662,3.04467 -6.24609,4.67118 -2.20946,1.6265 -5.58897,4.02668 -7.51,5.3337 -1.92104,1.30703 -3.78221,2.56514 -4.13594,2.79581 -0.35373,0.23067 -2.17154,1.47287 -4.03958,2.76044 -1.86804,1.28756 -5.94951,3.86722 -9.06992,5.73256 -3.12042,1.86532 -6.67648,3.965 -7.90236,4.66593 -1.22587,0.70093 -3.0495,1.68972 -4.05249,2.19729 -1.00299,0.50759 -5.01495,2.51404 -8.91546,4.45881 -3.90052,1.94475 -7.74424,3.89043 -8.54158,4.32373 -0.79735,0.4333 -4.52529,2.12298 -8.28434,3.75484 -3.75904,1.63187 -8.94225,3.83262 -11.51825,4.89055 -2.576,1.05793 -5.77778,2.27187 -7.11511,2.69763 -1.33732,0.42577 -3.70803,1.23963 -5.26824,1.80859 -1.5602,0.56895 -7.3046,2.35461 -12.76534,3.96811 -5.46072,1.61351 -11.8434,3.39925 -14.1837,3.9683 -2.34031,0.56905 -5.98755,1.41783 -8.10498,1.88617 -2.11742,0.46835 -6.03821,1.31781 -8.71285,1.88772 -2.67464,0.56991 -6.50425,1.26233 -8.51022,1.5387 -2.00598,0.27639 -4.66963,0.67555 -5.91921,0.88705 -1.24959,0.21149 -5.17038,0.67354 -8.71286,1.02679 -3.54248,0.35323 -8.35568,0.83207 -10.69599,1.06405 -2.34031,0.232 -11.36722,0.47347 -20.05982,0.53662 z m -23.70705,-11.54534 c 2.07084,0.0325 2.84566,-0.08 2.43151,-0.35305 -0.33435,-0.2204 -1.42851,-0.51808 -2.43151,-0.66151 -1.00299,-0.14343 -4.74141,-1.11291 -8.3076,-2.15439 -3.56619,-1.04149 -7.57815,-2.32216 -8.91547,-2.84594 -1.33732,-0.52377 -4.97691,-2.20754 -8.08797,-3.7417 -3.11106,-1.53417 -6.57594,-3.37997 -7.69972,-4.10178 -1.1238,-0.72181 -4.41396,-2.92438 -7.3115,-4.8946 -2.89753,-1.97021 -6.72713,-4.78447 -8.51022,-6.25391 -1.78311,-1.46943 -4.42734,-3.86059 -5.87611,-5.31365 -1.44877,-1.45308 -3.91065,-4.10957 -5.47086,-5.90333 -1.5602,-1.79376 -4.78595,-5.78572 -7.1683,-8.87101 -2.38237,-3.08531 -4.93544,-6.65702 -5.67349,-7.93713 -0.73805,-1.28011 -1.95386,-3.39934 -2.70177,-4.70941 -0.74794,-1.31007 -2.54237,-5.02416 -3.98763,-8.25354 -1.44526,-3.22937 -3.07467,-7.1927 -3.62092,-8.8074 -0.54623,-1.61468 -1.72976,-5.4747 -2.63003,-8.57781 -0.9003,-3.10313 -1.88268,-7.34956 -2.18309,-9.4365 -0.30042,-2.08697 -0.74444,-6.15359 -0.98671,-9.03697 -0.24229,-2.88338 -0.62024,-7.22417 -0.83993,-9.64621 -0.26691,-2.94274 -0.26123,-6.27346 0.0171,-10.03965 0.22908,-3.09978 0.78296,-7.9998 1.2308,-10.88892 0.44784,-2.88914 1.18796,-6.67542 1.64467,-8.41395 0.45671,-1.73853 1.46848,-5.237 2.24838,-7.77436 0.77988,-2.53738 2.34337,-6.97253 3.47442,-9.85591 1.13102,-2.88337 3.68813,-8.2559 5.68242,-11.93897 1.9943,-3.68306 4.65276,-8.11821 5.90771,-9.8559 1.25493,-1.73767 4.0537,-5.34481 6.21947,-8.01586 2.16577,-2.67105 5.30815,-6.20364 6.98307,-7.85021 1.67491,-1.64655 4.92015,-4.54 7.21166,-6.42987 2.29149,-1.88987 6.7661,-5.22907 9.94355,-7.42047 3.17746,-2.19138 5.69051,-4.07406 5.58456,-4.1837 -0.10591,-0.10959 -1.33798,0.0286 -2.73783,0.30724 -1.39984,0.27865 -4.00408,0.8587 -5.78718,1.28901 -1.7831,0.43032 -5.61269,1.36529 -8.51022,2.07774 -2.89753,0.71246 -6.90949,1.84401 -8.91547,2.51455 -2.00598,0.67056 -4.8326,1.52949 -6.28135,1.90874 -1.44878,0.37926 -5.1872,1.54836 -8.30761,2.59802 -3.12042,1.04966 -9.77664,3.50322 -14.79159,5.45235 -5.01494,1.94915 -10.39463,4.1145 -11.95483,4.81191 -1.56022,0.69741 -5.38981,2.51995 -8.51023,4.05009 -3.12041,1.53015 -9.32072,4.69494 -13.77846,7.03289 -4.45774,2.33795 -9.655055,5.14377 -11.549585,6.23518 -1.89454,1.0914 -5.63296,3.31664 -8.3076,4.945 -2.67464,1.62838 -5.865986,3.61187 -7.091863,4.40779 -1.225878,0.79592 -5.237837,3.48706 -8.91547,5.98032 -3.677631,2.49325 -7.533387,5.16172 -8.568341,5.92992 -1.034956,0.76821 -5.046919,3.90821 -8.915475,6.97779 -3.868556,3.06957 -8.219092,6.53017 -9.667856,7.69019 -1.448764,1.16003 -4.625179,3.93299 -7.058698,6.16215 -2.43352,2.22914 -6.46873,6.03466 -8.967134,8.45671 -2.498406,2.42202 -5.851329,5.85736 -7.450944,7.63406 -2.905108,3.22671 -2.90762,3.23156 -2.225761,4.30855 0.375445,0.59302 0.887361,1.21538 1.13759,1.38304 0.250228,0.16765 2.021565,2.19213 3.936304,4.49883 1.914739,2.3067 6.655628,7.50922 10.53531,11.56114 3.879681,4.05194 9.059948,9.26694 11.511704,11.58892 2.451756,2.32196 5.096005,4.75976 5.876108,5.41733 0.780105,0.65755 2.330419,2.08239 3.445146,3.16627 1.114727,1.0839 2.716145,2.50263 3.558708,3.15273 0.842564,0.6501 3.21303,2.60902 5.267699,4.35315 2.054683,1.74412 6.288842,4.99131 9.409263,7.21598 3.120409,2.22467 8.044185,5.7625 10.94171,7.86186 2.89754,2.09936 5.9065,4.22184 6.68661,4.71664 0.78011,0.4948 2.39786,1.52212 3.59503,2.28295 1.19715,0.76082 2.23162,1.38332 2.29881,1.38332 0.0672,0 2.557795,1.38118 5.534685,3.0693 2.97688,1.68811 8.05154,4.44898 11.27702,6.13526 3.22547,1.68629 6.04686,3.21175 6.26974,3.38991 0.22289,0.17816 3.23186,1.68144 6.68661,3.3406 3.45475,1.65917 7.19317,3.3527 8.30759,3.76339 1.11444,0.41068 4.02837,1.63455 6.4754,2.71969 2.44702,1.08515 6.00309,2.54367 7.90234,3.24115 1.89927,0.69747 5.55038,2.00963 8.11358,2.91589 2.5632,0.90626 6.02807,2.12299 7.69973,2.70384 1.67165,0.58085 5.13653,1.69251 7.69973,2.47036 2.56319,0.77785 6.57515,1.91793 8.91546,2.53352 2.34031,0.6156 4.98456,1.29148 5.87612,1.50195 0.89155,0.21048 2.5328,0.56208 3.64723,0.7813 1.11444,0.21925 3.30279,0.68953 4.86299,1.04509 1.56021,0.35556 4.93391,1.03453 7.49711,1.50881 2.56319,0.47426 7.66934,1.3189 11.34697,1.87695 3.67763,0.55806 8.05432,1.18473 9.72597,1.3926 1.67166,0.20787 4.40708,0.3994 6.07873,0.42564 z m 47.11018,-0.13503 c 1.61593,-0.14786 6.12939,-0.73625 10.02991,-1.30754 3.90051,-0.57127 8.73312,-1.31879 10.73909,-1.66117 2.00599,-0.34237 4.46787,-0.80864 5.47087,-1.03616 1.00299,-0.2275 3.94865,-0.78461 6.54591,-1.23801 2.59727,-0.4534 5.97096,-1.10605 7.4971,-1.45033 1.52615,-0.34427 5.05433,-1.18408 7.84042,-1.86624 2.78609,-0.68216 8.07458,-2.22402 11.75222,-3.42633 3.67762,-1.20233 7.87195,-2.52083 9.32072,-2.93001 1.44877,-0.40919 5.82545,-1.95687 9.72597,-3.43931 3.90052,-1.48244 7.45657,-2.91279 7.90236,-3.17855 0.44576,-0.26576 2.81647,-1.31796 5.26823,-2.33822 2.45176,-1.02026 7.10198,-3.114 10.33385,-4.65273 3.23185,-1.53874 9.8948,-4.8471 14.80654,-7.35192 4.91175,-2.50483 11.20323,-5.94875 13.98109,-7.65316 2.77785,-1.70441 7.60371,-4.76514 10.72413,-6.80161 3.12041,-2.03646 6.31174,-4.20596 7.09185,-4.82108 0.78011,-0.61511 2.38964,-1.72603 3.57676,-2.46869 1.18713,-0.74265 4.01374,-2.90899 6.28137,-4.81408 2.26763,-1.9051 4.76122,-3.91906 5.54133,-4.47547 0.78009,-0.55642 2.87725,-2.2163 4.66036,-3.68863 1.78309,-1.47235 4.13001,-3.4618 5.21537,-4.42103 1.08536,-0.95922 3.13373,-2.6877 4.55195,-3.84104 1.41821,-1.15335 3.81271,-3.15598 5.3211,-4.45028 1.50838,-1.2943 7.48395,-7.2398 13.27903,-13.21224 5.79507,-5.97243 11.58047,-12.27392 12.85644,-14.00332 l 2.31994,-3.14436 -1.74035,-1.67759 c -0.95719,-0.92268 -3.46007,-3.23103 -5.56197,-5.12966 -2.10188,-1.89862 -5.37169,-4.67943 -7.26622,-6.17957 -1.89455,-1.50014 -4.85878,-3.85936 -6.58722,-5.24271 -1.72844,-1.38335 -4.2815,-3.41925 -5.67349,-4.52421 -1.39199,-1.10496 -2.98678,-2.3294 -3.54401,-2.72099 -0.5572,-0.39159 -1.74256,-1.28185 -2.63411,-1.97833 -0.89154,-0.6965 -2.62399,-1.95355 -3.84986,-2.79345 -1.22588,-0.83989 -2.77595,-2.05055 -3.44462,-2.69033 -0.66865,-0.63978 -4.13353,-3.13584 -7.69972,-5.54677 -3.56619,-2.41095 -7.12225,-4.72048 -7.90236,-5.13228 -0.7801,-0.41181 -2.15608,-1.2709 -3.05772,-1.90906 -0.90165,-0.63817 -4.36654,-2.803 -7.69973,-4.81076 -3.3332,-2.00773 -7.7928,-4.68908 -9.91023,-5.95854 -2.11743,-1.26944 -4.76167,-2.88932 -5.8761,-3.59973 -1.11444,-0.71039 -3.1646,-1.90889 -4.5559,-2.66334 -1.3913,-0.75444 -6.04152,-3.24223 -10.33384,-5.52841 -4.29231,-2.2862 -8.26011,-4.30156 -8.81734,-4.47861 -0.5572,-0.17705 -3.9309,-1.7801 -7.49709,-3.56234 -3.56619,-1.78223 -8.7635,-4.23077 -11.5496,-5.44119 -2.78608,-1.21042 -9.40728,-3.77955 -14.71378,-5.70919 -5.3065,-1.92963 -13.05688,-4.53963 -17.22307,-5.80003 -4.16619,-1.26038 -9.30734,-2.75728 -11.42477,-3.32643 -2.11742,-0.56915 -4.6705,-1.16252 -5.67348,-1.31858 l -1.82362,-0.28373 2.83674,1.78442 c 1.56021,0.98144 4.38682,3.0247 6.28136,4.5406 1.89453,1.5159 4.44761,3.57362 5.67349,4.57272 1.22587,0.99908 4.44959,4.16816 7.1638,7.0424 2.71423,2.87422 6.56112,7.43825 8.54866,10.14229 1.98756,2.70403 3.85168,5.38824 4.14252,5.96492 0.29083,0.57668 0.65195,1.14286 0.80247,1.2582 0.15053,0.11529 1.02738,1.36724 1.94858,2.78203 0.92118,1.41476 2.25498,3.86826 2.964,5.4522 0.70902,1.58392 2.37548,5.33336 3.70325,8.33207 1.32777,2.99871 3.02373,7.24513 3.76878,9.43649 0.74506,2.19137 1.82052,6.2935 2.38989,9.11586 0.56938,2.82235 1.38465,8.20115 1.81171,11.9529 0.42708,3.75174 0.98342,8.99173 1.23635,11.64444 0.34056,3.57208 0.34282,6.55372 0.009,11.49528 -0.24811,3.66971 -0.89613,9.0279 -1.44004,11.90708 -0.54392,2.8792 -1.27715,6.38791 -1.62941,7.79713 -0.35226,1.4092 -1.61608,5.6052 -2.80851,9.32442 -1.19242,3.71923 -3.06635,8.81494 -4.16428,11.32381 -1.09792,2.50886 -3.1828,6.66768 -4.63304,9.24181 -1.45025,2.57413 -3.52637,5.94827 -4.6136,7.49808 -1.08724,1.54982 -3.54723,4.82251 -5.46662,7.27266 -1.91941,2.45015 -6.02255,7.01133 -9.11809,10.13597 -3.09557,3.12464 -6.81364,6.51033 -8.26241,7.52375 -1.44875,1.01343 -4.00182,2.90565 -5.67348,4.20495 -1.67164,1.29929 -5.13652,3.67482 -7.69972,5.27897 -2.5632,1.60412 -6.30162,3.69939 -8.30761,4.65611 -2.00597,0.95673 -4.65224,2.20252 -5.88059,2.76842 -1.22835,0.5659 -4.78442,1.88987 -7.90236,2.94217 -3.11794,1.05231 -7.81174,2.41919 -10.43066,3.03751 -2.61893,0.61835 -4.76168,1.21251 -4.76168,1.3204 0,0.10781 1.32212,0.0752 2.93806,-0.0728 z m -15.76795,-4.97391 c 2.93304,-0.14445 7.06523,-0.55196 9.18266,-0.9056 2.11742,-0.35363 6.12939,-1.23093 8.91547,-1.94956 2.78608,-0.71862 6.79805,-1.96469 8.91548,-2.76902 2.11742,-0.80435 5.03521,-1.92988 6.48397,-2.5012 1.44878,-0.57133 4.45924,-1.96903 6.68994,-3.10601 2.23071,-1.13698 5.04744,-2.84396 6.2594,-3.79328 1.21195,-0.94934 2.83055,-1.99719 3.59686,-2.32855 0.76634,-0.33138 3.2381,-1.93875 5.49284,-3.57195 2.25474,-1.6332 5.64959,-4.37334 7.54413,-6.08923 1.89454,-1.71588 4.59959,-4.28268 6.0112,-5.704 1.41163,-1.42131 3.68053,-3.99969 5.04203,-5.72971 1.36149,-1.73003 3.62582,-4.60071 5.03183,-6.37929 1.40602,-1.77858 2.55639,-3.35278 2.55639,-3.49823 0,-0.14545 0.72125,-1.33247 1.60277,-2.63781 0.88153,-1.30535 3.00663,-5.48741 4.72245,-9.29347 1.71581,-3.80605 3.65912,-8.42994 4.31846,-10.27528 0.65934,-1.84537 1.80295,-5.24251 2.54135,-7.54921 0.73841,-2.30671 1.89695,-6.83622 2.57455,-10.06559 0.67759,-3.22939 1.43162,-7.7589 1.67561,-10.0656 0.31361,-2.96483 0.32422,-6.95964 0.0362,-13.6305 -0.30987,-7.17763 -0.69453,-11.093 -1.60695,-16.3566 -0.76512,-4.41388 -1.85678,-8.8947 -3.01427,-12.3723 -0.9981,-2.9987 -2.55888,-7.57707 -3.4684,-10.17413 -1.04434,-2.98205 -2.866,-6.84545 -4.94388,-10.485 -1.80962,-3.16969 -3.95876,-6.72782 -4.77587,-7.90697 -0.81713,-1.17912 -2.92018,-4.10445 -4.67348,-6.50069 -1.75329,-2.39623 -3.85045,-5.17991 -4.66036,-6.18595 -0.80991,-1.00603 -2.56674,-2.89332 -3.90406,-4.194 -1.33732,-1.30067 -3.43448,-3.36861 -4.66035,-4.59542 -1.22588,-1.22681 -3.32304,-2.97124 -4.66037,-3.87653 -1.33732,-0.90527 -3.25211,-2.2903 -4.25511,-3.07785 -1.00299,-0.78755 -4.37668,-2.93032 -7.4971,-4.76172 -3.12042,-1.83141 -6.75241,-3.9358 -8.0711,-4.67645 -1.31869,-0.74064 -4.51001,-2.1049 -7.09185,-3.03169 -2.58184,-0.92678 -5.97078,-1.85917 -7.53099,-2.07196 -1.56021,-0.2128 -4.9339,-0.56833 -7.4971,-0.7901 -2.56319,-0.22177 -5.57217,-0.49143 -6.68661,-0.59927 -1.11443,-0.10781 -5.08081,-0.32514 -8.81416,-0.48291 l -6.78792,-0.28686 -0.0578,4.34526 c -0.0318,2.38989 -0.12968,7.64802 -0.21753,11.68476 -0.0879,4.03673 -0.17294,8.6606 -0.18909,10.27529 -0.0162,1.6147 0.10553,8.06048 0.27045,14.32397 l 0.29982,11.38818 4.10088,0.51407 c 2.6694,0.33463 5.87528,1.13397 9.18417,2.28998 3.2916,1.14997 6.64825,2.713 9.52335,4.43458 2.44203,1.46227 5.25994,3.32814 6.26205,4.14635 1.00209,0.81822 2.36981,2.00579 3.03937,2.63907 0.66955,0.63328 1.35186,1.15229 1.51621,1.15336 0.16437,8.9e-4 0.7321,0.71218 1.2616,1.58027 0.5295,0.86807 1.44045,2.04763 2.02432,2.62124 0.58388,0.57362 1.06474,1.19003 1.06859,1.36981 0.004,0.17979 0.75562,1.30702 1.67059,2.50498 0.91498,1.19794 2.12757,3.12687 2.69466,4.28649 0.56708,1.15965 1.52621,3.14646 2.13138,4.41514 0.60517,1.26869 1.57521,4.09964 2.15562,6.291 0.58041,2.19136 1.23575,5.11667 1.4563,6.5007 0.22055,1.38402 0.47753,3.05166 0.57107,3.70586 0.0935,0.65421 0.0225,3.86263 -0.1579,7.1298 -0.24718,4.47741 -0.64036,7.17976 -1.59648,10.97313 -0.69769,2.76805 -2.01154,6.73137 -2.91964,8.8074 -0.90812,2.07603 -2.61303,5.28444 -3.7887,7.1298 -1.17567,1.84536 -3.17489,4.5379 -4.44269,5.98341 -1.26782,1.44551 -3.6092,3.72429 -5.20308,5.06395 -1.59388,1.33964 -4.06191,3.24783 -5.48449,4.2404 -1.42258,0.99258 -4.43155,2.69427 -6.6866,3.78153 -2.25504,1.08726 -5.65016,2.39817 -7.54471,2.9131 -1.89454,0.51494 -5.07252,1.1131 -7.06218,1.32923 -1.98967,0.21613 -3.68574,0.46351 -3.76905,0.54974 -0.0833,0.0863 -0.11974,3.0568 -0.0812,6.6013 0.0388,3.54449 0.0887,8.4262 0.11098,10.84823 0.0223,2.42203 -0.1172,11.01574 -0.3101,19.0971 -0.19288,8.08137 -0.21703,15.80045 -0.0537,17.15351 l 0.29699,2.4601 z m -13.96284,-0.33987 3.80357,0.0572 -0.20891,-16.04204 c -0.12215,-9.38404 -0.0387,-17.97637 0.20099,-20.70264 0.26052,-2.96235 0.27968,-7.24138 0.0526,-11.7432 l -0.3573,-7.08261 -2.43149,-0.29247 c -1.33734,-0.16085 -3.25213,-0.47936 -4.25513,-0.70778 -1.00297,-0.22841 -3.2825,-0.77921 -5.06561,-1.22399 -1.78308,-0.44478 -4.70087,-1.56026 -6.48397,-2.47884 -1.78309,-0.91858 -4.33617,-2.2967 -5.67349,-3.06245 -1.33731,-0.76577 -3.16094,-1.97286 -4.05248,-2.68244 -0.89155,-0.70958 -3.14011,-2.79836 -4.99679,-4.64174 -1.85669,-1.84338 -4.22521,-4.58111 -5.26338,-6.08384 -1.03817,-1.50274 -2.75338,-4.22055 -3.81158,-6.03957 -1.05821,-1.81904 -2.36878,-4.46126 -2.9124,-5.87161 -0.54361,-1.41035 -1.11009,-3.13045 -1.25883,-3.82246 -0.14875,-0.69201 -0.62747,-2.37131 -1.06383,-3.73178 -0.43636,-1.36046 -0.91939,-3.90833 -1.07341,-5.6619 -0.15402,-1.75358 -0.26273,-4.69816 -0.2416,-6.54353 0.0211,-1.84534 0.338,-5.43122 0.70415,-7.96858 0.36614,-2.53738 0.83009,-5.46269 1.03098,-6.50071 0.20088,-1.03801 0.56284,-2.53445 0.80432,-3.3254 0.24148,-0.79095 1.38509,-3.43318 2.54136,-5.87159 1.15625,-2.43843 3.117,-5.94335 4.35722,-7.7887 1.2402,-1.84536 3.71683,-4.83372 5.5036,-6.64078 1.78678,-1.80707 4.38673,-4.11145 5.77767,-5.12084 1.39094,-1.00939 4.25153,-2.79627 6.35687,-3.97086 2.10534,-1.17458 5.63323,-2.77098 7.83976,-3.54753 2.20654,-0.77655 5.6831,-1.71039 7.7257,-2.07521 2.04263,-0.3648 4.25268,-0.66328 4.91126,-0.66328 h 1.19741 l 0.0175,-25.79309 0.0175,-25.79309 -1.11378,-0.0555 c -0.61257,-0.0304 -3.8492,-0.0214 -7.19249,0.0204 -3.34331,0.0416 -9.70478,0.36371 -14.13661,0.7156 -5.44046,0.43198 -8.92881,0.9176 -10.7391,1.49503 -1.47466,0.47037 -4.18895,1.50026 -6.03175,2.28865 -1.84279,0.78837 -5.39886,2.53412 -7.90235,3.8794 -2.5035,1.34529 -5.55481,3.08762 -6.78068,3.87183 -1.22588,0.78421 -3.41423,2.26331 -4.863,3.28688 -1.44876,1.02359 -3.63711,2.6879 -4.86299,3.69848 -1.22587,1.01057 -4.01288,3.37912 -6.19335,5.26343 -2.18047,1.88433 -5.60948,5.33182 -7.62003,7.6611 -2.01054,2.32928 -4.38676,5.25459 -5.2805,6.50069 -0.89373,1.24611 -2.16054,2.92104 -2.81513,3.72207 -0.65459,0.80104 -1.19179,1.65031 -1.19377,1.88731 -0.003,0.23697 -0.9076,1.75198 -2.01248,3.36668 -1.10488,1.61468 -3.27285,5.57802 -4.8177,8.80738 -1.54485,3.22938 -3.01772,6.67655 -3.27302,7.66035 -0.2553,0.98382 -0.80347,2.39929 -1.21815,3.1455 -0.41469,0.74622 -0.88171,2.01731 -1.03786,2.82465 -0.15613,0.80735 -0.70153,2.88338 -1.21198,4.6134 -0.51046,1.73002 -1.39907,4.93844 -1.97472,7.1298 -0.57564,2.19136 -1.22098,5.30541 -1.43408,6.92011 -0.21311,1.61469 -0.5057,6.52166 -0.65018,10.90439 -0.22099,6.70315 -0.12572,9.16744 0.60023,15.51779 0.47463,4.15206 1.01846,8.39849 1.20852,9.4365 0.19006,1.03802 0.62866,3.30277 0.97469,5.03279 0.34602,1.73004 1.16214,4.84408 1.81359,6.92011 0.65146,2.07602 1.91659,5.47317 2.81142,7.54919 0.89481,2.07604 1.85152,4.52953 2.12601,5.4522 0.27449,0.92269 1.28837,3.37618 2.25306,5.4522 0.96469,2.07603 3.3288,5.945 5.25358,8.59771 1.92478,2.6527 3.69504,5.20055 3.93394,5.6619 0.23889,0.46134 1.47367,2.25427 2.74395,3.98429 1.27028,1.73003 3.66491,4.56098 5.32138,6.291 1.65648,1.73003 4.71055,4.7497 6.78683,6.7104 2.07627,1.9607 5.80747,5.04017 8.29157,6.84327 2.4841,1.8031 6.88726,4.64235 9.78478,6.30944 2.89752,1.6671 6.07665,3.45634 7.06472,3.97609 0.98806,0.51977 3.08522,1.48833 4.66036,2.15233 1.57514,0.66402 5.41695,2.06196 8.53736,3.10655 3.12042,1.04458 7.953,2.38953 10.73909,2.98877 2.78609,0.59923 6.61569,1.24268 8.51024,1.42988 1.89452,0.1872 5.1562,0.36609 7.24816,0.39752 z"/>
4
+ <path style="fill:#008080;stroke-width:0.735481" d="m 258.63061,346.76779 v -27.48562 l 6.43544,-1.17299 c 10.14959,-1.84993 18.74136,-6.47518 26.71398,-14.38106 5.43671,-5.39121 6.88273,-7.37088 9.72899,-13.31945 7.53519,-15.74824 7.94808,-32.62931 1.16677,-47.70395 -2.54683,-5.66151 -8.46664,-13.80406 -12.77116,-17.56639 -6.84143,-5.9797 -16.95525,-10.80623 -25.74776,-12.28739 l -5.15852,-0.869 -0.19353,-25.61938 -0.19352,-25.6194 9.4721,0.50005 c 21.91593,1.15699 27.47705,2.60891 41.39959,10.80872 9.0666,5.33986 17.40338,11.64205 22.42765,16.95419 5.29499,5.59834 14.70621,18.96895 18.51156,26.29947 4.45008,8.57258 9.25282,23.10904 10.87886,32.92712 1.87201,11.30329 1.75414,29.01122 -0.25263,37.9532 -6.92663,30.86438 -22.41007,54.25215 -46.50999,70.25347 -15.98593,10.61399 -36.83797,17.81403 -51.59128,17.81403 h -4.31655 z"/>
5
+ <path style="fill:#6c5d53;stroke-width:0.735481" d="m 242.66006,373.44484 c -16.38082,-1.90703 -34.84058,-9.49125 -49.48719,-20.33187 -14.41335,-10.66799 -28.56513,-29.0361 -34.99439,-45.42052 -6.717,-17.11767 -8.765,-29.17405 -8.18327,-48.17396 0.33852,-11.05635 0.60482,-12.96304 2.99493,-21.44313 6.16868,-21.88632 15.18988,-37.07831 30.64719,-51.61087 10.51354,-9.88456 24.51921,-18.68649 35.75881,-22.47285 4.00747,-1.35002 21.155,-3.02749 30.94776,-3.02749 h 3.13834 v 25.74182 25.74179 h -2.76037 c -4.18551,0 -11.86066,2.44348 -18.68976,5.95011 -8.6959,4.46519 -16.71645,12.60506 -21.31334,21.63041 -3.84255,7.54428 -4.69646,10.55897 -5.80383,20.48993 -1.84852,16.57767 3.75031,31.99729 15.97728,44.0027 7.45313,7.31805 15.24528,11.36208 26.44542,13.72479 l 5.77687,1.21866 0.19283,27.39453 0.19281,27.39452 -2.767,-0.0955 c -1.52185,-0.0524 -5.15473,-0.37338 -8.07309,-0.71313 z"/>
6
+ <path style="fill:#87aade;stroke-width:1.04013" d="m 259.05556,346.60858 v -27.00629 l 6.50079,-1.20895 c 18.56102,-3.45178 33.2898,-16.96531 39.89437,-36.60276 2.89708,-8.61398 2.7143,-25.41857 -0.37334,-34.32417 -6.04115,-17.42422 -19.47791,-29.55986 -37.91062,-34.23961 l -7.59114,-1.92726 -0.27866,-25.13756 -0.27864,-25.13757 9.11971,0.6389 c 21.7827,1.52603 29.464,3.71019 43.12347,12.26204 12.17895,7.62497 19.47782,14.12891 27.04849,24.10265 10.52299,13.8632 15.92052,25.09749 21.25409,44.23778 1.4857,5.33161 1.87867,10.49874 1.85897,24.44296 -0.0231,16.30998 -0.24776,18.40859 -2.89529,27.04329 -5.20857,16.98717 -11.21754,28.62862 -21.37503,41.41073 -15.68442,19.73721 -46.15216,36.34164 -69.51613,37.88521 l -8.58104,0.56692 z"/>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twinshot
4
+ # Adapter registry entry — see lib/twinshot/opener.rb.
5
+ module Opener
6
+ # macOS: the original `open`.
7
+ module Darwin
8
+ module_function
9
+
10
+ def open(path)
11
+ Opener.launch("open", path) ||
12
+ raise(Error, "`open #{path}` failed")
13
+ end
14
+ end
15
+
16
+ Opener.register "darwin", Darwin
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twinshot
4
+ # Adapter registry entry — see lib/twinshot/opener.rb.
5
+ module Opener
6
+ # Linux and the BSDs: `xdg-open`. Also the fallback family for any
7
+ # host_os the case above does not recognize.
8
+ module Linux
9
+ module_function
10
+
11
+ def open(path)
12
+ Opener.launch("xdg-open", path) ||
13
+ raise(Error, "`xdg-open #{path}` failed (is xdg-open installed?)")
14
+ end
15
+ end
16
+
17
+ Opener.register "linux", Linux
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twinshot
4
+ # Adapter registry entry — see lib/twinshot/opener.rb.
5
+ module Opener
6
+ # Windows: `start` is a cmd.exe builtin, so it needs the cmd wrapper.
7
+ # The empty-string arg is the window title — without it `start` would
8
+ # eat a quoted path as the title and open a console instead.
9
+ module Windows
10
+ module_function
11
+
12
+ def open(path)
13
+ Opener.launch("cmd", "/c", "start", "", path) ||
14
+ raise(Error, "`start #{path}` failed")
15
+ end
16
+ end
17
+
18
+ Opener.register "windows", Windows
19
+ end
20
+ end