sevgi-showcase 0.73.2 → 0.93.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 +4 -4
- data/README.md +5 -0
- data/lib/sevgi/showcase/minitest/script.rb +36 -4
- data/lib/sevgi/showcase/minitest/shell.rb +33 -4
- data/lib/sevgi/showcase/minitest/suite.rb +17 -0
- data/lib/sevgi/showcase/passive.rb +25 -0
- data/lib/sevgi/showcase/version.rb +3 -1
- data/srv/checker-board.sevgi +96 -0
- data/srv/checker-board.svg +117 -0
- data/srv/checker-board.yml +6 -0
- data/srv/gear-wheel.sevgi +22 -0
- data/srv/gear-wheel.svg +11 -0
- data/srv/gear-wheel.yml +3 -0
- data/srv/grid-cells.sevgi +20 -0
- data/srv/grid-cells.svg +34 -0
- data/srv/grid-cells.yml +4 -0
- data/srv/heart-mask.sevgi +14 -0
- data/srv/heart-mask.svg +8 -0
- data/srv/heart-mask.yml +3 -0
- data/srv/meter-face.sevgi +20 -0
- data/srv/meter-face.svg +13 -0
- data/srv/meter-face.yml +3 -0
- data/srv/pacman-pokey.sevgi +17 -0
- data/srv/pacman-pokey.svg +9 -0
- data/srv/pacman-pokey.yml +3 -0
- data/srv/pacman-single.sevgi +17 -0
- data/srv/pacman-single.svg +14 -0
- data/srv/pacman-single.yml +3 -0
- data/srv/ruler-hline.sevgi +52 -0
- data/srv/ruler-hline.svg +443 -0
- data/srv/ruler-hline.yml +3 -0
- data/srv/ruler-line.sevgi +52 -0
- data/srv/ruler-line.svg +443 -0
- data/srv/ruler-line.yml +3 -0
- data/srv/snow-flake.sevgi +17 -0
- data/srv/snow-flake.svg +20 -0
- data/srv/snow-flake.yml +3 -0
- metadata +34 -4
- data/lib/sevgi/showcase/kludge.rb +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50251e3e573b65b4e4fdaf8731f03af5da849ee270a0765df20a3987c0227d9b
|
|
4
|
+
data.tar.gz: 68bdb206d1e170ff485784a7ab6caadf1c20f7e2883307cf799b1ea2b205e0c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb652c2b35a40a038cb59d5fa19980381c10d497d5c01639d906b0a4704f3ac79ab21db919e117cf82423b3107b21e45055b345da737bcfa8c30cdd19bbb4544
|
|
7
|
+
data.tar.gz: 13b69afa718368211e58442d31f495cc73b3792e6d9fd294b4d99c884a01feedf7ad996fc30fbeeec7c9bc3ccc858938f8d3b173df9daf27e23a8189d44fb566
|
data/README.md
CHANGED
|
@@ -1,38 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
|
+
# Minitest helpers used by the showcase component.
|
|
5
|
+
# @api private
|
|
4
6
|
module Test
|
|
7
|
+
# Executable showcase script descriptor.
|
|
8
|
+
# @api private
|
|
5
9
|
class Script
|
|
10
|
+
# @return [String] absolute script path
|
|
6
11
|
attr_reader :file
|
|
7
12
|
|
|
13
|
+
# Creates a script descriptor.
|
|
14
|
+
# @param file [String] executable .sevgi file
|
|
15
|
+
# @return [void]
|
|
16
|
+
# @raise [Sevgi::ArgumentError] when the file is missing or not executable
|
|
8
17
|
def initialize(file) = @file = ::File.expand_path(sanitize(file))
|
|
9
18
|
|
|
19
|
+
# Returns the script directory.
|
|
20
|
+
# @return [String]
|
|
10
21
|
def dir = @dir ||= ::File.dirname(file)
|
|
11
22
|
|
|
23
|
+
# Returns the script basename.
|
|
24
|
+
# @return [String]
|
|
12
25
|
def file! = ::File.basename(file)
|
|
13
26
|
|
|
27
|
+
# Returns the script name without extension.
|
|
28
|
+
# @return [String]
|
|
14
29
|
def name = @name ||= ::File.basename(file, ".*")
|
|
15
30
|
|
|
31
|
+
# @overload run(*args)
|
|
32
|
+
# Runs the script directly.
|
|
33
|
+
# @param args [Array<String>] extra command arguments
|
|
34
|
+
# @return [Sevgi::Test::Shell::Result]
|
|
16
35
|
def run(*) = Shell.run(file, *)
|
|
17
36
|
|
|
37
|
+
# Returns the script suite name.
|
|
38
|
+
# @return [String]
|
|
18
39
|
def suite = @suite ||= ::File.basename(dir)
|
|
19
40
|
|
|
41
|
+
# Reports whether the expected SVG output exists.
|
|
42
|
+
# @return [Boolean]
|
|
20
43
|
def svg? = ::File.exist?(svg)
|
|
21
44
|
|
|
45
|
+
# Returns the expected SVG output path.
|
|
46
|
+
# @return [String]
|
|
22
47
|
def svg = @svg ||= ::File.expand_path("#{dir}/#{name}.svg")
|
|
23
48
|
|
|
49
|
+
# Returns the expected SVG output basename.
|
|
50
|
+
# @return [String]
|
|
24
51
|
def svg! = ::File.basename(svg)
|
|
25
52
|
|
|
53
|
+
# Returns the optional YAML metadata path.
|
|
54
|
+
# @return [String]
|
|
26
55
|
def yml = @yml ||= ::File.expand_path("#{dir}/#{name}.yml")
|
|
27
56
|
|
|
57
|
+
# Returns the optional YAML metadata basename.
|
|
58
|
+
# @return [String]
|
|
28
59
|
def yml! = ::File.basename(yml)
|
|
29
60
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
61
|
+
# @overload run_passive(*args)
|
|
62
|
+
# Runs the script without writing Save output to files.
|
|
63
|
+
# @param args [Array<String>] extra command arguments
|
|
64
|
+
# @return [Sevgi::Test::Shell::Result]
|
|
33
65
|
def run_passive(*)
|
|
34
66
|
warn("...#{name}")
|
|
35
|
-
Shell.run("sevgi", "-r", "sevgi/showcase/
|
|
67
|
+
Shell.run("sevgi", "-r", "sevgi/showcase/passive", file, *)
|
|
36
68
|
end
|
|
37
69
|
|
|
38
70
|
private
|
|
@@ -3,26 +3,49 @@
|
|
|
3
3
|
require "open3"
|
|
4
4
|
|
|
5
5
|
module Sevgi
|
|
6
|
+
# Minitest helpers used by the showcase component.
|
|
7
|
+
# @api private
|
|
6
8
|
module Test
|
|
9
|
+
# Shell runner helpers for showcase tests.
|
|
10
|
+
# @api private
|
|
7
11
|
module Shell
|
|
12
|
+
# Shell command result.
|
|
13
|
+
# @api private
|
|
8
14
|
Result = Data.define(:args, :out, :err, :exit_code) do
|
|
15
|
+
# Returns the command string.
|
|
16
|
+
# @return [String]
|
|
9
17
|
def cmd = args.join(" ")
|
|
10
18
|
|
|
19
|
+
# Reports whether the command failed.
|
|
20
|
+
# @return [Boolean]
|
|
11
21
|
def notok? = !ok?
|
|
12
22
|
|
|
23
|
+
# Reports whether the command exited successfully.
|
|
24
|
+
# @return [Boolean]
|
|
13
25
|
def ok? = exit_code&.zero?
|
|
14
26
|
|
|
27
|
+
# Returns the first stdout line.
|
|
28
|
+
# @return [String, nil]
|
|
15
29
|
def outline = out.first
|
|
16
30
|
|
|
31
|
+
# Returns stdout as a newline-joined string.
|
|
32
|
+
# @return [String]
|
|
17
33
|
def to_s = out.join("\n")
|
|
18
34
|
end
|
|
19
35
|
|
|
20
36
|
# Adapted to popen3 from github.com/mina-deploy/mina
|
|
37
|
+
# Shell command runner.
|
|
38
|
+
# @api private
|
|
21
39
|
class Runner
|
|
40
|
+
# Creates a runner.
|
|
41
|
+
# @return [void]
|
|
22
42
|
def initialize
|
|
23
43
|
@coathooks = 0
|
|
24
44
|
end
|
|
25
45
|
|
|
46
|
+
# Runs a command and captures stdout, stderr, and exit status.
|
|
47
|
+
# @param args [Array<String>] command and arguments
|
|
48
|
+
# @return [Sevgi::Test::Shell::Result]
|
|
26
49
|
def run(*args, &block)
|
|
27
50
|
out, err, status = Open3.popen3(*args) do |stdin, stdout, stderr, thread|
|
|
28
51
|
inputs(stdin, thread, &block) if block
|
|
@@ -41,12 +64,14 @@ module Sevgi
|
|
|
41
64
|
|
|
42
65
|
def outputs(stdout, stderr, thread)
|
|
43
66
|
# handle `^C`
|
|
44
|
-
trap("INT") { handle_sigint(thread.pid) }
|
|
67
|
+
trap = trap("INT") { handle_sigint(thread.pid) }
|
|
45
68
|
|
|
46
|
-
out = stdout.readlines.map(&:chomp)
|
|
47
|
-
err = stderr.readlines.map(&:chomp)
|
|
69
|
+
out = Thread.new { stdout.readlines.map(&:chomp) }
|
|
70
|
+
err = Thread.new { stderr.readlines.map(&:chomp) }
|
|
48
71
|
|
|
49
|
-
[out, err, thread.value]
|
|
72
|
+
[out.value, err.value, thread.value]
|
|
73
|
+
ensure
|
|
74
|
+
trap("INT", trap) if trap
|
|
50
75
|
end
|
|
51
76
|
|
|
52
77
|
def handle_sigint(pid)
|
|
@@ -66,6 +91,10 @@ module Sevgi
|
|
|
66
91
|
|
|
67
92
|
extend self
|
|
68
93
|
|
|
94
|
+
# @overload run(*args)
|
|
95
|
+
# Runs a command through a fresh runner.
|
|
96
|
+
# @param args [Array<String>] command and arguments
|
|
97
|
+
# @return [Sevgi::Test::Shell::Result]
|
|
69
98
|
def run(...) = Runner.new.run(...)
|
|
70
99
|
end
|
|
71
100
|
end
|
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
|
+
# Minitest helpers used by the showcase component.
|
|
5
|
+
# @api private
|
|
4
6
|
module Test
|
|
7
|
+
# Showcase script suite collection.
|
|
8
|
+
# @api private
|
|
5
9
|
class Suite
|
|
10
|
+
# Creates a suite from a showcase root directory.
|
|
11
|
+
# @param rootdir [String] showcase root directory
|
|
12
|
+
# @return [void]
|
|
6
13
|
def initialize(rootdir)
|
|
7
14
|
@scripts = load(rootdir)
|
|
8
15
|
end
|
|
9
16
|
|
|
17
|
+
# Returns scripts belonging to a suite.
|
|
18
|
+
# @param suite [String, Symbol] suite name
|
|
19
|
+
# @return [Array<Sevgi::Test::Script>]
|
|
10
20
|
def [](suite)
|
|
11
21
|
(@cache ||= {})[suite] ||= @scripts.select { |script| script.suite == suite.to_s }
|
|
12
22
|
end
|
|
13
23
|
|
|
24
|
+
# Returns all suite names.
|
|
25
|
+
# @return [Array<String>]
|
|
14
26
|
def suites
|
|
15
27
|
@suites ||= @scripts.map(&:suite).uniq
|
|
16
28
|
end
|
|
17
29
|
|
|
30
|
+
# Suite names that are expected to be invalid examples.
|
|
18
31
|
NON_VALIDS = ["gotcha"].freeze
|
|
19
32
|
|
|
33
|
+
# Returns scripts expected to be invalid.
|
|
34
|
+
# @return [Array<Sevgi::Test::Script>]
|
|
20
35
|
def non_valids
|
|
21
36
|
NON_VALIDS.map { self[it] }.flatten
|
|
22
37
|
end
|
|
23
38
|
|
|
39
|
+
# Returns scripts expected to render successfully.
|
|
40
|
+
# @return [Array<Sevgi::Test::Script>]
|
|
24
41
|
def valids
|
|
25
42
|
suites.reject { NON_VALIDS.include?(it) }.map { self[it] }.flatten
|
|
26
43
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevgi
|
|
4
|
+
module Graphics
|
|
5
|
+
module Document
|
|
6
|
+
class Base
|
|
7
|
+
# @overload Save(*args, **kwargs)
|
|
8
|
+
# Redirects showcase saves to stdout during passive integration tests.
|
|
9
|
+
# @param args [Array<Object>] ignored save arguments
|
|
10
|
+
# @param kwargs [Hash] render options
|
|
11
|
+
# @return [Object] F.out return value
|
|
12
|
+
# @api private
|
|
13
|
+
def Save(*, **) = Out(**)
|
|
14
|
+
|
|
15
|
+
# @overload Save!(*args, **kwargs)
|
|
16
|
+
# Redirects forced showcase saves to stdout during passive integration tests.
|
|
17
|
+
# @param args [Array<Object>] ignored save arguments
|
|
18
|
+
# @param kwargs [Hash] render options
|
|
19
|
+
# @return [Object] F.out return value
|
|
20
|
+
# @api private
|
|
21
|
+
def Save!(...) = Save(...)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class CheckerBoard
|
|
5
|
+
BOARD_MARGIN = 50
|
|
6
|
+
CELL_SIZE = 100
|
|
7
|
+
PIECE_SIZE = 80
|
|
8
|
+
|
|
9
|
+
STYLE = {
|
|
10
|
+
".board-frame" => { fill: "#d1bfa7", stroke: "#6a3f2b" },
|
|
11
|
+
".inner-frame" => {
|
|
12
|
+
fill: "#6a3f2b",
|
|
13
|
+
stroke: "#6a3f2b",
|
|
14
|
+
"stroke-width": (BOARD_MARGIN * 0.7).round
|
|
15
|
+
},
|
|
16
|
+
".cell" => { stroke: "white", "stroke-width": 1 },
|
|
17
|
+
".light.cell" => { fill: "#d4a76f" },
|
|
18
|
+
".dark.cell" => { fill: "#6a3f2b" },
|
|
19
|
+
".piece" => {
|
|
20
|
+
filter: "drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5))",
|
|
21
|
+
stroke: "none"
|
|
22
|
+
},
|
|
23
|
+
".light.piece" => { fill: "#f5f5f5" },
|
|
24
|
+
".dark.piece" => { fill: "#333" }
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@pieces = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def width = (CELL_SIZE * 8) + (BOARD_MARGIN * 2)
|
|
32
|
+
|
|
33
|
+
def height = width
|
|
34
|
+
|
|
35
|
+
def view_box = "0 0 #{width} #{height}"
|
|
36
|
+
|
|
37
|
+
def add_piece(row, col, dark: false)
|
|
38
|
+
@pieces << [row, col, dark]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def render_on(svg)
|
|
42
|
+
svg.css STYLE
|
|
43
|
+
add_frame(svg)
|
|
44
|
+
add_cells(svg)
|
|
45
|
+
add_pieces(svg)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def add_frame(svg)
|
|
51
|
+
svg.rect class: "board-frame", x: 0, y: 0, width: width, height: height
|
|
52
|
+
svg.rect class: "inner-frame", x: BOARD_MARGIN, y: BOARD_MARGIN,
|
|
53
|
+
width: width - (BOARD_MARGIN * 2),
|
|
54
|
+
height: height - (BOARD_MARGIN * 2)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_cells(svg)
|
|
58
|
+
8.times do |row|
|
|
59
|
+
8.times do |col|
|
|
60
|
+
svg.rect class: "#{(row + col).even? ? "dark" : "light"} cell",
|
|
61
|
+
x: (col * CELL_SIZE) + BOARD_MARGIN,
|
|
62
|
+
y: (row * CELL_SIZE) + BOARD_MARGIN,
|
|
63
|
+
width: CELL_SIZE,
|
|
64
|
+
height: CELL_SIZE
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def add_pieces(svg)
|
|
70
|
+
@pieces.each do |row, col, dark|
|
|
71
|
+
x = (col * CELL_SIZE) + BOARD_MARGIN + 10
|
|
72
|
+
y = (row * CELL_SIZE) + BOARD_MARGIN + 10
|
|
73
|
+
|
|
74
|
+
svg.circle class: "#{dark ? "dark" : "light"} piece",
|
|
75
|
+
cx: x + (PIECE_SIZE / 2),
|
|
76
|
+
cy: y + (PIECE_SIZE / 2),
|
|
77
|
+
r: PIECE_SIZE / 2
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
board = CheckerBoard.new
|
|
83
|
+
|
|
84
|
+
[1, 3, 5, 7].each do |col|
|
|
85
|
+
board.add_piece 0, col, dark: true
|
|
86
|
+
board.add_piece 6, col, dark: false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
[0, 2, 4, 6].each do |col|
|
|
90
|
+
board.add_piece 1, col, dark: true
|
|
91
|
+
board.add_piece 7, col, dark: false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
SVG :minimal, viewBox: board.view_box do
|
|
95
|
+
board.render_on(self)
|
|
96
|
+
end.Save
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<svg viewBox="0 0 900 900">
|
|
2
|
+
<style type="text/css">
|
|
3
|
+
<![CDATA[
|
|
4
|
+
.board-frame {
|
|
5
|
+
fill: #d1bfa7;
|
|
6
|
+
stroke: #6a3f2b;
|
|
7
|
+
}
|
|
8
|
+
.inner-frame {
|
|
9
|
+
fill: #6a3f2b;
|
|
10
|
+
stroke: #6a3f2b;
|
|
11
|
+
stroke-width: 35;
|
|
12
|
+
}
|
|
13
|
+
.cell {
|
|
14
|
+
stroke: white;
|
|
15
|
+
stroke-width: 1;
|
|
16
|
+
}
|
|
17
|
+
.light.cell {
|
|
18
|
+
fill: #d4a76f;
|
|
19
|
+
}
|
|
20
|
+
.dark.cell {
|
|
21
|
+
fill: #6a3f2b;
|
|
22
|
+
}
|
|
23
|
+
.piece {
|
|
24
|
+
filter: drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5));
|
|
25
|
+
stroke: none;
|
|
26
|
+
}
|
|
27
|
+
.light.piece {
|
|
28
|
+
fill: #f5f5f5;
|
|
29
|
+
}
|
|
30
|
+
.dark.piece {
|
|
31
|
+
fill: #333;
|
|
32
|
+
}
|
|
33
|
+
]]>
|
|
34
|
+
</style>
|
|
35
|
+
<rect class="board-frame" x="0" y="0" width="900" height="900"/>
|
|
36
|
+
<rect class="inner-frame" x="50" y="50" width="800" height="800"/>
|
|
37
|
+
<rect class="dark cell" x="50" y="50" width="100" height="100"/>
|
|
38
|
+
<rect class="light cell" x="150" y="50" width="100" height="100"/>
|
|
39
|
+
<rect class="dark cell" x="250" y="50" width="100" height="100"/>
|
|
40
|
+
<rect class="light cell" x="350" y="50" width="100" height="100"/>
|
|
41
|
+
<rect class="dark cell" x="450" y="50" width="100" height="100"/>
|
|
42
|
+
<rect class="light cell" x="550" y="50" width="100" height="100"/>
|
|
43
|
+
<rect class="dark cell" x="650" y="50" width="100" height="100"/>
|
|
44
|
+
<rect class="light cell" x="750" y="50" width="100" height="100"/>
|
|
45
|
+
<rect class="light cell" x="50" y="150" width="100" height="100"/>
|
|
46
|
+
<rect class="dark cell" x="150" y="150" width="100" height="100"/>
|
|
47
|
+
<rect class="light cell" x="250" y="150" width="100" height="100"/>
|
|
48
|
+
<rect class="dark cell" x="350" y="150" width="100" height="100"/>
|
|
49
|
+
<rect class="light cell" x="450" y="150" width="100" height="100"/>
|
|
50
|
+
<rect class="dark cell" x="550" y="150" width="100" height="100"/>
|
|
51
|
+
<rect class="light cell" x="650" y="150" width="100" height="100"/>
|
|
52
|
+
<rect class="dark cell" x="750" y="150" width="100" height="100"/>
|
|
53
|
+
<rect class="dark cell" x="50" y="250" width="100" height="100"/>
|
|
54
|
+
<rect class="light cell" x="150" y="250" width="100" height="100"/>
|
|
55
|
+
<rect class="dark cell" x="250" y="250" width="100" height="100"/>
|
|
56
|
+
<rect class="light cell" x="350" y="250" width="100" height="100"/>
|
|
57
|
+
<rect class="dark cell" x="450" y="250" width="100" height="100"/>
|
|
58
|
+
<rect class="light cell" x="550" y="250" width="100" height="100"/>
|
|
59
|
+
<rect class="dark cell" x="650" y="250" width="100" height="100"/>
|
|
60
|
+
<rect class="light cell" x="750" y="250" width="100" height="100"/>
|
|
61
|
+
<rect class="light cell" x="50" y="350" width="100" height="100"/>
|
|
62
|
+
<rect class="dark cell" x="150" y="350" width="100" height="100"/>
|
|
63
|
+
<rect class="light cell" x="250" y="350" width="100" height="100"/>
|
|
64
|
+
<rect class="dark cell" x="350" y="350" width="100" height="100"/>
|
|
65
|
+
<rect class="light cell" x="450" y="350" width="100" height="100"/>
|
|
66
|
+
<rect class="dark cell" x="550" y="350" width="100" height="100"/>
|
|
67
|
+
<rect class="light cell" x="650" y="350" width="100" height="100"/>
|
|
68
|
+
<rect class="dark cell" x="750" y="350" width="100" height="100"/>
|
|
69
|
+
<rect class="dark cell" x="50" y="450" width="100" height="100"/>
|
|
70
|
+
<rect class="light cell" x="150" y="450" width="100" height="100"/>
|
|
71
|
+
<rect class="dark cell" x="250" y="450" width="100" height="100"/>
|
|
72
|
+
<rect class="light cell" x="350" y="450" width="100" height="100"/>
|
|
73
|
+
<rect class="dark cell" x="450" y="450" width="100" height="100"/>
|
|
74
|
+
<rect class="light cell" x="550" y="450" width="100" height="100"/>
|
|
75
|
+
<rect class="dark cell" x="650" y="450" width="100" height="100"/>
|
|
76
|
+
<rect class="light cell" x="750" y="450" width="100" height="100"/>
|
|
77
|
+
<rect class="light cell" x="50" y="550" width="100" height="100"/>
|
|
78
|
+
<rect class="dark cell" x="150" y="550" width="100" height="100"/>
|
|
79
|
+
<rect class="light cell" x="250" y="550" width="100" height="100"/>
|
|
80
|
+
<rect class="dark cell" x="350" y="550" width="100" height="100"/>
|
|
81
|
+
<rect class="light cell" x="450" y="550" width="100" height="100"/>
|
|
82
|
+
<rect class="dark cell" x="550" y="550" width="100" height="100"/>
|
|
83
|
+
<rect class="light cell" x="650" y="550" width="100" height="100"/>
|
|
84
|
+
<rect class="dark cell" x="750" y="550" width="100" height="100"/>
|
|
85
|
+
<rect class="dark cell" x="50" y="650" width="100" height="100"/>
|
|
86
|
+
<rect class="light cell" x="150" y="650" width="100" height="100"/>
|
|
87
|
+
<rect class="dark cell" x="250" y="650" width="100" height="100"/>
|
|
88
|
+
<rect class="light cell" x="350" y="650" width="100" height="100"/>
|
|
89
|
+
<rect class="dark cell" x="450" y="650" width="100" height="100"/>
|
|
90
|
+
<rect class="light cell" x="550" y="650" width="100" height="100"/>
|
|
91
|
+
<rect class="dark cell" x="650" y="650" width="100" height="100"/>
|
|
92
|
+
<rect class="light cell" x="750" y="650" width="100" height="100"/>
|
|
93
|
+
<rect class="light cell" x="50" y="750" width="100" height="100"/>
|
|
94
|
+
<rect class="dark cell" x="150" y="750" width="100" height="100"/>
|
|
95
|
+
<rect class="light cell" x="250" y="750" width="100" height="100"/>
|
|
96
|
+
<rect class="dark cell" x="350" y="750" width="100" height="100"/>
|
|
97
|
+
<rect class="light cell" x="450" y="750" width="100" height="100"/>
|
|
98
|
+
<rect class="dark cell" x="550" y="750" width="100" height="100"/>
|
|
99
|
+
<rect class="light cell" x="650" y="750" width="100" height="100"/>
|
|
100
|
+
<rect class="dark cell" x="750" y="750" width="100" height="100"/>
|
|
101
|
+
<circle class="dark piece" cx="200" cy="100" r="40"/>
|
|
102
|
+
<circle class="light piece" cx="200" cy="700" r="40"/>
|
|
103
|
+
<circle class="dark piece" cx="400" cy="100" r="40"/>
|
|
104
|
+
<circle class="light piece" cx="400" cy="700" r="40"/>
|
|
105
|
+
<circle class="dark piece" cx="600" cy="100" r="40"/>
|
|
106
|
+
<circle class="light piece" cx="600" cy="700" r="40"/>
|
|
107
|
+
<circle class="dark piece" cx="800" cy="100" r="40"/>
|
|
108
|
+
<circle class="light piece" cx="800" cy="700" r="40"/>
|
|
109
|
+
<circle class="dark piece" cx="100" cy="200" r="40"/>
|
|
110
|
+
<circle class="light piece" cx="100" cy="800" r="40"/>
|
|
111
|
+
<circle class="dark piece" cx="300" cy="200" r="40"/>
|
|
112
|
+
<circle class="light piece" cx="300" cy="800" r="40"/>
|
|
113
|
+
<circle class="dark piece" cx="500" cy="200" r="40"/>
|
|
114
|
+
<circle class="light piece" cx="500" cy="800" r="40"/>
|
|
115
|
+
<circle class="dark piece" cx="700" cy="200" r="40"/>
|
|
116
|
+
<circle class="light piece" cx="700" cy="800" r="40"/>
|
|
117
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
GEAR_STYLE = {
|
|
5
|
+
fill: "#333",
|
|
6
|
+
stroke: "none"
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
SVG :minimal, viewBox: "0 0 100 100", style: { background: "lightgreen" } do
|
|
10
|
+
mask id: "hole" do
|
|
11
|
+
rect x: 0, y: 0, width: 100, height: 100, fill: "white"
|
|
12
|
+
circle cx: 50, cy: 50, r: 15, fill: "black"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
circle cx: 50, cy: 50, r: 30, mask: "url(#hole)", **GEAR_STYLE
|
|
16
|
+
|
|
17
|
+
[0, 45, 90, 135].each do |angle|
|
|
18
|
+
rect x: 45, y: 10, width: 10, height: 80,
|
|
19
|
+
mask: "url(#hole)", transform: "rotate(#{angle} 50 50)",
|
|
20
|
+
**GEAR_STYLE
|
|
21
|
+
end
|
|
22
|
+
end.Save
|
data/srv/gear-wheel.svg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg viewBox="0 0 100 100" style="background:lightgreen">
|
|
2
|
+
<mask id="hole">
|
|
3
|
+
<rect x="0" y="0" width="100" height="100" fill="white"/>
|
|
4
|
+
<circle cx="50" cy="50" r="15" fill="black"/>
|
|
5
|
+
</mask>
|
|
6
|
+
<circle cx="50" cy="50" r="30" mask="url(#hole)" fill="#333" stroke="none"/>
|
|
7
|
+
<rect x="45" y="10" width="10" height="80" mask="url(#hole)" transform="rotate(0 50 50)" fill="#333" stroke="none"/>
|
|
8
|
+
<rect x="45" y="10" width="10" height="80" mask="url(#hole)" transform="rotate(45 50 50)" fill="#333" stroke="none"/>
|
|
9
|
+
<rect x="45" y="10" width="10" height="80" mask="url(#hole)" transform="rotate(90 50 50)" fill="#333" stroke="none"/>
|
|
10
|
+
<rect x="45" y="10" width="10" height="80" mask="url(#hole)" transform="rotate(135 50 50)" fill="#333" stroke="none"/>
|
|
11
|
+
</svg>
|
data/srv/gear-wheel.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
CELL_SIZE = 20
|
|
5
|
+
COLORS = %w[#FF6961 #77DD77 #FFD700].freeze
|
|
6
|
+
COLS = 8
|
|
7
|
+
ROWS = 3
|
|
8
|
+
|
|
9
|
+
SVG :minimal, viewBox: "0 0 #{COLS * CELL_SIZE} #{ROWS * CELL_SIZE}" do
|
|
10
|
+
css ".cell" => { stroke: "white", rx: (CELL_SIZE * 0.1).round }
|
|
11
|
+
|
|
12
|
+
COLS.times do |col|
|
|
13
|
+
ROWS.times do |row|
|
|
14
|
+
rect class: "cell",
|
|
15
|
+
x: col * CELL_SIZE, y: row * CELL_SIZE,
|
|
16
|
+
width: CELL_SIZE, height: CELL_SIZE,
|
|
17
|
+
fill: COLORS[(col + row) % COLORS.length]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end.Save
|
data/srv/grid-cells.svg
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<svg viewBox="0 0 160 60">
|
|
2
|
+
<style type="text/css">
|
|
3
|
+
<![CDATA[
|
|
4
|
+
.cell {
|
|
5
|
+
stroke: white;
|
|
6
|
+
rx: 2;
|
|
7
|
+
}
|
|
8
|
+
]]>
|
|
9
|
+
</style>
|
|
10
|
+
<rect class="cell" x="0" y="0" width="20" height="20" fill="#FF6961"/>
|
|
11
|
+
<rect class="cell" x="0" y="20" width="20" height="20" fill="#77DD77"/>
|
|
12
|
+
<rect class="cell" x="0" y="40" width="20" height="20" fill="#FFD700"/>
|
|
13
|
+
<rect class="cell" x="20" y="0" width="20" height="20" fill="#77DD77"/>
|
|
14
|
+
<rect class="cell" x="20" y="20" width="20" height="20" fill="#FFD700"/>
|
|
15
|
+
<rect class="cell" x="20" y="40" width="20" height="20" fill="#FF6961"/>
|
|
16
|
+
<rect class="cell" x="40" y="0" width="20" height="20" fill="#FFD700"/>
|
|
17
|
+
<rect class="cell" x="40" y="20" width="20" height="20" fill="#FF6961"/>
|
|
18
|
+
<rect class="cell" x="40" y="40" width="20" height="20" fill="#77DD77"/>
|
|
19
|
+
<rect class="cell" x="60" y="0" width="20" height="20" fill="#FF6961"/>
|
|
20
|
+
<rect class="cell" x="60" y="20" width="20" height="20" fill="#77DD77"/>
|
|
21
|
+
<rect class="cell" x="60" y="40" width="20" height="20" fill="#FFD700"/>
|
|
22
|
+
<rect class="cell" x="80" y="0" width="20" height="20" fill="#77DD77"/>
|
|
23
|
+
<rect class="cell" x="80" y="20" width="20" height="20" fill="#FFD700"/>
|
|
24
|
+
<rect class="cell" x="80" y="40" width="20" height="20" fill="#FF6961"/>
|
|
25
|
+
<rect class="cell" x="100" y="0" width="20" height="20" fill="#FFD700"/>
|
|
26
|
+
<rect class="cell" x="100" y="20" width="20" height="20" fill="#FF6961"/>
|
|
27
|
+
<rect class="cell" x="100" y="40" width="20" height="20" fill="#77DD77"/>
|
|
28
|
+
<rect class="cell" x="120" y="0" width="20" height="20" fill="#FF6961"/>
|
|
29
|
+
<rect class="cell" x="120" y="20" width="20" height="20" fill="#77DD77"/>
|
|
30
|
+
<rect class="cell" x="120" y="40" width="20" height="20" fill="#FFD700"/>
|
|
31
|
+
<rect class="cell" x="140" y="0" width="20" height="20" fill="#77DD77"/>
|
|
32
|
+
<rect class="cell" x="140" y="20" width="20" height="20" fill="#FFD700"/>
|
|
33
|
+
<rect class="cell" x="140" y="40" width="20" height="20" fill="#FF6961"/>
|
|
34
|
+
</svg>
|
data/srv/grid-cells.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
HEART_PATH = "M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
|
|
5
|
+
|
|
6
|
+
SVG :minimal, viewBox: "-10 -10 120 120" do
|
|
7
|
+
mask id: "heart" do
|
|
8
|
+
rect x: 0, y: 0, width: 100, height: 100, fill: "white"
|
|
9
|
+
path d: HEART_PATH, fill: "black"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
polygon points: %w[-10,120 120,120 120,-10], fill: "orange"
|
|
13
|
+
circle cx: 50, cy: 50, r: 50, mask: "url(#heart)", fill: "red"
|
|
14
|
+
end.Save
|
data/srv/heart-mask.svg
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg viewBox="-10 -10 120 120">
|
|
2
|
+
<mask id="heart">
|
|
3
|
+
<rect x="0" y="0" width="100" height="100" fill="white"/>
|
|
4
|
+
<path d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z" fill="black"/>
|
|
5
|
+
</mask>
|
|
6
|
+
<polygon points="-10,120 120,120 120,-10" fill="orange"/>
|
|
7
|
+
<circle cx="50" cy="50" r="50" mask="url(#heart)" fill="red"/>
|
|
8
|
+
</svg>
|
data/srv/heart-mask.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
LED_COUNT = 10
|
|
5
|
+
LED_GAP = 2
|
|
6
|
+
LED_WIDTH = 10
|
|
7
|
+
TOTAL_WIDTH = (LED_COUNT * (LED_WIDTH + LED_GAP)) + 8
|
|
8
|
+
|
|
9
|
+
SVG :minimal, viewBox: "0 0 #{TOTAL_WIDTH} 30" do
|
|
10
|
+
rect x: 2, y: 2, width: TOTAL_WIDTH - 4, height: 26,
|
|
11
|
+
rx: 4, fill: "none", stroke: "#666"
|
|
12
|
+
|
|
13
|
+
x = 5
|
|
14
|
+
LED_COUNT.times do |i|
|
|
15
|
+
rect x:, y: 5, width: LED_WIDTH, height: 20,
|
|
16
|
+
rx: 3, fill: "green", "fill-opacity": (i < 7 ? 1 : 0.3)
|
|
17
|
+
|
|
18
|
+
x += LED_WIDTH + LED_GAP
|
|
19
|
+
end
|
|
20
|
+
end.Save
|
data/srv/meter-face.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg viewBox="0 0 128 30">
|
|
2
|
+
<rect x="2" y="2" width="124" height="26" rx="4" fill="none" stroke="#666"/>
|
|
3
|
+
<rect x="5" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
4
|
+
<rect x="17" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
5
|
+
<rect x="29" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
6
|
+
<rect x="41" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
7
|
+
<rect x="53" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
8
|
+
<rect x="65" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
9
|
+
<rect x="77" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="1"/>
|
|
10
|
+
<rect x="89" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="0.3"/>
|
|
11
|
+
<rect x="101" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="0.3"/>
|
|
12
|
+
<rect x="113" y="5" width="10" height="20" rx="3" fill="green" fill-opacity="0.3"/>
|
|
13
|
+
</svg>
|