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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2dc7c21825c07ed66a185187025a0fd58969df58105d0d55ba4da7ec3d1f78f
4
- data.tar.gz: 53a825990707906e4386cf104b8d83d13d33bd41752496044fd65b1493c1b9cf
3
+ metadata.gz: 50251e3e573b65b4e4fdaf8731f03af5da849ee270a0765df20a3987c0227d9b
4
+ data.tar.gz: 68bdb206d1e170ff485784a7ab6caadf1c20f7e2883307cf799b1ea2b205e0c3
5
5
  SHA512:
6
- metadata.gz: bc0bd992d47b00eac1a4bd65f5835759c04e0b657bcb459109362ee0dab2bad0ec35090ce03223c64a45740f860352c26d56f286945793fb80377886246201f7
7
- data.tar.gz: bd1f18ae77d395b4905b233bea49e80e5c2c6c68545847cc0a4b0a4a6668f2a1bcd3edcf9248b45e6613e80a5029399197428b9a801bbc95e7be997195fdf918
6
+ metadata.gz: bb652c2b35a40a038cb59d5fa19980381c10d497d5c01639d906b0a4704f3ac79ab21db919e117cf82423b3107b21e45055b345da737bcfa8c30cdd19bbb4544
7
+ data.tar.gz: 13b69afa718368211e58442d31f495cc73b3792e6d9fd294b4d99c884a01feedf7ad996fc30fbeeec7c9bc3ccc858938f8d3b173df9daf27e23a8189d44fb566
data/README.md CHANGED
@@ -0,0 +1,5 @@
1
+ # Sevgi Showcase
2
+
3
+ Provides executable examples, rendered outputs, and documentation-site support.
4
+
5
+ See the root [README](../README.md) and the documentation site for usage.
@@ -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
- # A gross hack to avoid touching filesystem during testing. Intercept the Save methods to display output in stdout
31
- # instead of saving an actual file.
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/kludge", file, *)
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
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sevgi
4
+ # Showcase component namespace.
4
5
  module Showcase
5
- VERSION = "0.73.2"
6
+ # Current showcase component version.
7
+ VERSION = "0.93.1"
6
8
  end
7
9
  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,6 @@
1
+ dark:
2
+ "#d1bfa7": "#705f4b"
3
+ "#d4a76f": "#b88a55"
4
+ "#6a3f2b": "#d3a778"
5
+ "#333": "#161616"
6
+ "#f5f5f5": "#e8e2dc"
@@ -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
@@ -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>
@@ -0,0 +1,3 @@
1
+ dark:
2
+ "#333": "#e5c39c"
3
+ lightgreen: "#1f3522"
@@ -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
@@ -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>
@@ -0,0 +1,4 @@
1
+ dark:
2
+ "#FF6961": "#f27b72"
3
+ "#77DD77": "#7fdc9c"
4
+ "#FFD700": "#f1c95b"
@@ -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
@@ -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>
@@ -0,0 +1,3 @@
1
+ dark:
2
+ orange: "#f2b35d"
3
+ red: "#e8505b"
@@ -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
@@ -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>