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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/PLAN.md +107 -0
- data/README.md +179 -0
- data/Rakefile +12 -0
- data/exe/twinshot +6 -0
- data/lib/twinshot/adapters/binary.rb +21 -0
- data/lib/twinshot/adapters/html.rb +27 -0
- data/lib/twinshot/adapters/png.rb +128 -0
- data/lib/twinshot/adapters/txt.rb +27 -0
- data/lib/twinshot/adapters/xml.rb +28 -0
- data/lib/twinshot/adapters.rb +105 -0
- data/lib/twinshot/cli.rb +503 -0
- data/lib/twinshot/compare.rb +249 -0
- data/lib/twinshot/compare_worker.rb +47 -0
- data/lib/twinshot/config.rb +212 -0
- data/lib/twinshot/logo.svg +8 -0
- data/lib/twinshot/opener/darwin.rb +18 -0
- data/lib/twinshot/opener/linux.rb +19 -0
- data/lib/twinshot/opener/windows.rb +20 -0
- data/lib/twinshot/opener.rb +59 -0
- data/lib/twinshot/progress.rb +104 -0
- data/lib/twinshot/report.rb +600 -0
- data/lib/twinshot/soft_check.rb +19 -0
- data/lib/twinshot/tree.rb +31 -0
- data/lib/twinshot/version.rb +5 -0
- data/lib/twinshot.rb +18 -0
- data/logo.svg +100 -0
- data/sig/twinshot.rbs +4 -0
- metadata +130 -0
|
@@ -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
|