Script Mode
A .sevgi file is ordinary Ruby run by the sevgi command. Before evaluating the file, the runner installs Sevgi's DSL
words in the script's top-level scope.
Applications embedding the same runner should use the result-oriented Execution API.
Script shape
A typical script has:
- a shebang that runs
ruby -S sevgi - Ruby constants, helper classes, or calculations
- an
SVGblock followed bySave,Write, orOut
In the checkers example, a Ruby hash stores the piece positions. A callable module holds the drawing steps, and the
SVG block invokes them with Call.
Callable modules can put argument-independent SVG in base blocks. Sevgi runs inherited bases from parent to child,
then local bases in registration order. Name a single drawing method call; give multiple methods names that describe
their drawing steps.
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
module CheckerBoard
extend SVG::Module
BOARD_MARGIN = 50
CELL_SIZE = 100
PIECE_SIZE = 80
SIZE = (CELL_SIZE * 8) + (BOARD_MARGIN * 2)
STYLE = {
".board-frame" => { fill: "#d1bfa7", stroke: "#6a3f2b" },
".inner-frame" => {
fill: "#6a3f2b",
stroke: "#6a3f2b",
"stroke-width": (BOARD_MARGIN * 0.7).round
},
".cell" => { stroke: "white", "stroke-width": 1 },
".light.cell" => { fill: "#d4a76f" },
".dark.cell" => { fill: "#6a3f2b" },
".piece" => {
filter: "drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5))",
stroke: "none"
},
".light.piece" => { fill: "#f5f5f5" },
".dark.piece" => { fill: "#333" }
}.freeze
base do # Draw the board.
css STYLE
square class: "board-frame", length: SIZE
square class: "inner-frame", x: BOARD_MARGIN, y: BOARD_MARGIN,
length: SIZE - (BOARD_MARGIN * 2)
8.times do |row|
8.times do |col|
square class: "#{(row + col).even? ? "dark" : "light"} cell",
x: (col * CELL_SIZE) + BOARD_MARGIN,
y: (row * CELL_SIZE) + BOARD_MARGIN,
length: CELL_SIZE
end
end
end
def call(placement)
center = BOARD_MARGIN + (CELL_SIZE / 2)
placement.each do |tone, rows|
rows.each do |row, cols|
cols.each do |col|
circle class: "#{tone} piece",
cx: (col * CELL_SIZE) + center,
cy: (row * CELL_SIZE) + center,
r: PIECE_SIZE / 2
end
end
end
end
end
placement = {
dark: {
0 => [1, 3, 5, 7],
1 => [0, 2, 4, 6]
},
light: {
6 => [1, 3, 5, 7],
7 => [0, 2, 4, 6]
}
}
SVG :minimal,
width: CheckerBoard::SIZE,
height: CheckerBoard::SIZE,
viewBox: "0 0 #{CheckerBoard::SIZE} #{CheckerBoard::SIZE}" do
Call CheckerBoard, placement
end.Save
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
module CheckerBoard
extend SVG::Module
BOARD_MARGIN = 50
CELL_SIZE = 100
PIECE_SIZE = 80
SIZE = (CELL_SIZE * 8) + (BOARD_MARGIN * 2)
STYLE = {
".board-frame" => { fill: "#705f4b", stroke: "#d3a778" },
".inner-frame" => {
fill: "#d3a778",
stroke: "#d3a778",
"stroke-width": (BOARD_MARGIN * 0.7).round
},
".cell" => { stroke: "white", "stroke-width": 1 },
".light.cell" => { fill: "#b88a55" },
".dark.cell" => { fill: "#d3a778" },
".piece" => {
filter: "drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5))",
stroke: "none"
},
".light.piece" => { fill: "#e8e2dc" },
".dark.piece" => { fill: "#161616" }
}.freeze
base do # Draw the board.
css STYLE
square class: "board-frame", length: SIZE
square class: "inner-frame", x: BOARD_MARGIN, y: BOARD_MARGIN,
length: SIZE - (BOARD_MARGIN * 2)
8.times do |row|
8.times do |col|
square class: "#{(row + col).even? ? "dark" : "light"} cell",
x: (col * CELL_SIZE) + BOARD_MARGIN,
y: (row * CELL_SIZE) + BOARD_MARGIN,
length: CELL_SIZE
end
end
end
def call(placement)
center = BOARD_MARGIN + (CELL_SIZE / 2)
placement.each do |tone, rows|
rows.each do |row, cols|
cols.each do |col|
circle class: "#{tone} piece",
cx: (col * CELL_SIZE) + center,
cy: (row * CELL_SIZE) + center,
r: PIECE_SIZE / 2
end
end
end
end
end
placement = {
dark: {
0 => [1, 3, 5, 7],
1 => [0, 2, 4, 6]
},
light: {
6 => [1, 3, 5, 7],
7 => [0, 2, 4, 6]
}
}
SVG :minimal,
width: CheckerBoard::SIZE,
height: CheckerBoard::SIZE,
viewBox: "0 0 #{CheckerBoard::SIZE} #{CheckerBoard::SIZE}" do
Call CheckerBoard, placement
end.Save
<svg width="900" height="900" viewBox="0 0 900 900">
<style type="text/css">
<![CDATA[
.board-frame {
fill: #d1bfa7;
stroke: #6a3f2b;
}
.inner-frame {
fill: #6a3f2b;
stroke: #6a3f2b;
stroke-width: 35;
}
.cell {
stroke: white;
stroke-width: 1;
}
.light.cell {
fill: #d4a76f;
}
.dark.cell {
fill: #6a3f2b;
}
.piece {
filter: drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5));
stroke: none;
}
.light.piece {
fill: #f5f5f5;
}
.dark.piece {
fill: #333;
}
]]>
</style>
<rect width="900" height="900" class="board-frame"/>
<rect width="800" height="800" class="inner-frame" x="50" y="50"/>
<rect width="100" height="100" class="dark cell" x="50" y="50"/>
<rect width="100" height="100" class="light cell" x="150" y="50"/>
<rect width="100" height="100" class="dark cell" x="250" y="50"/>
<rect width="100" height="100" class="light cell" x="350" y="50"/>
<rect width="100" height="100" class="dark cell" x="450" y="50"/>
<rect width="100" height="100" class="light cell" x="550" y="50"/>
<rect width="100" height="100" class="dark cell" x="650" y="50"/>
<rect width="100" height="100" class="light cell" x="750" y="50"/>
<rect width="100" height="100" class="light cell" x="50" y="150"/>
<rect width="100" height="100" class="dark cell" x="150" y="150"/>
<rect width="100" height="100" class="light cell" x="250" y="150"/>
<rect width="100" height="100" class="dark cell" x="350" y="150"/>
<rect width="100" height="100" class="light cell" x="450" y="150"/>
<rect width="100" height="100" class="dark cell" x="550" y="150"/>
<rect width="100" height="100" class="light cell" x="650" y="150"/>
<rect width="100" height="100" class="dark cell" x="750" y="150"/>
<rect width="100" height="100" class="dark cell" x="50" y="250"/>
<rect width="100" height="100" class="light cell" x="150" y="250"/>
<rect width="100" height="100" class="dark cell" x="250" y="250"/>
<rect width="100" height="100" class="light cell" x="350" y="250"/>
<rect width="100" height="100" class="dark cell" x="450" y="250"/>
<rect width="100" height="100" class="light cell" x="550" y="250"/>
<rect width="100" height="100" class="dark cell" x="650" y="250"/>
<rect width="100" height="100" class="light cell" x="750" y="250"/>
<rect width="100" height="100" class="light cell" x="50" y="350"/>
<rect width="100" height="100" class="dark cell" x="150" y="350"/>
<rect width="100" height="100" class="light cell" x="250" y="350"/>
<rect width="100" height="100" class="dark cell" x="350" y="350"/>
<rect width="100" height="100" class="light cell" x="450" y="350"/>
<rect width="100" height="100" class="dark cell" x="550" y="350"/>
<rect width="100" height="100" class="light cell" x="650" y="350"/>
<rect width="100" height="100" class="dark cell" x="750" y="350"/>
<rect width="100" height="100" class="dark cell" x="50" y="450"/>
<rect width="100" height="100" class="light cell" x="150" y="450"/>
<rect width="100" height="100" class="dark cell" x="250" y="450"/>
<rect width="100" height="100" class="light cell" x="350" y="450"/>
<rect width="100" height="100" class="dark cell" x="450" y="450"/>
<rect width="100" height="100" class="light cell" x="550" y="450"/>
<rect width="100" height="100" class="dark cell" x="650" y="450"/>
<rect width="100" height="100" class="light cell" x="750" y="450"/>
<rect width="100" height="100" class="light cell" x="50" y="550"/>
<rect width="100" height="100" class="dark cell" x="150" y="550"/>
<rect width="100" height="100" class="light cell" x="250" y="550"/>
<rect width="100" height="100" class="dark cell" x="350" y="550"/>
<rect width="100" height="100" class="light cell" x="450" y="550"/>
<rect width="100" height="100" class="dark cell" x="550" y="550"/>
<rect width="100" height="100" class="light cell" x="650" y="550"/>
<rect width="100" height="100" class="dark cell" x="750" y="550"/>
<rect width="100" height="100" class="dark cell" x="50" y="650"/>
<rect width="100" height="100" class="light cell" x="150" y="650"/>
<rect width="100" height="100" class="dark cell" x="250" y="650"/>
<rect width="100" height="100" class="light cell" x="350" y="650"/>
<rect width="100" height="100" class="dark cell" x="450" y="650"/>
<rect width="100" height="100" class="light cell" x="550" y="650"/>
<rect width="100" height="100" class="dark cell" x="650" y="650"/>
<rect width="100" height="100" class="light cell" x="750" y="650"/>
<rect width="100" height="100" class="light cell" x="50" y="750"/>
<rect width="100" height="100" class="dark cell" x="150" y="750"/>
<rect width="100" height="100" class="light cell" x="250" y="750"/>
<rect width="100" height="100" class="dark cell" x="350" y="750"/>
<rect width="100" height="100" class="light cell" x="450" y="750"/>
<rect width="100" height="100" class="dark cell" x="550" y="750"/>
<rect width="100" height="100" class="light cell" x="650" y="750"/>
<rect width="100" height="100" class="dark cell" x="750" y="750"/>
<circle class="dark piece" cx="200" cy="100" r="40"/>
<circle class="dark piece" cx="400" cy="100" r="40"/>
<circle class="dark piece" cx="600" cy="100" r="40"/>
<circle class="dark piece" cx="800" cy="100" r="40"/>
<circle class="dark piece" cx="100" cy="200" r="40"/>
<circle class="dark piece" cx="300" cy="200" r="40"/>
<circle class="dark piece" cx="500" cy="200" r="40"/>
<circle class="dark piece" cx="700" cy="200" r="40"/>
<circle class="light piece" cx="200" cy="700" r="40"/>
<circle class="light piece" cx="400" cy="700" r="40"/>
<circle class="light piece" cx="600" cy="700" r="40"/>
<circle class="light piece" cx="800" cy="700" r="40"/>
<circle class="light piece" cx="100" cy="800" r="40"/>
<circle class="light piece" cx="300" cy="800" r="40"/>
<circle class="light piece" cx="500" cy="800" r="40"/>
<circle class="light piece" cx="700" cy="800" r="40"/>
</svg>
<svg width="900" height="900" viewBox="0 0 900 900">
<style type="text/css">
<![CDATA[
.board-frame {
fill: #705f4b;
stroke: #d3a778;
}
.inner-frame {
fill: #d3a778;
stroke: #d3a778;
stroke-width: 35;
}
.cell {
stroke: white;
stroke-width: 1;
}
.light.cell {
fill: #b88a55;
}
.dark.cell {
fill: #d3a778;
}
.piece {
filter: drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.5));
stroke: none;
}
.light.piece {
fill: #e8e2dc;
}
.dark.piece {
fill: #161616;
}
]]>
</style>
<rect width="900" height="900" class="board-frame"/>
<rect width="800" height="800" class="inner-frame" x="50" y="50"/>
<rect width="100" height="100" class="dark cell" x="50" y="50"/>
<rect width="100" height="100" class="light cell" x="150" y="50"/>
<rect width="100" height="100" class="dark cell" x="250" y="50"/>
<rect width="100" height="100" class="light cell" x="350" y="50"/>
<rect width="100" height="100" class="dark cell" x="450" y="50"/>
<rect width="100" height="100" class="light cell" x="550" y="50"/>
<rect width="100" height="100" class="dark cell" x="650" y="50"/>
<rect width="100" height="100" class="light cell" x="750" y="50"/>
<rect width="100" height="100" class="light cell" x="50" y="150"/>
<rect width="100" height="100" class="dark cell" x="150" y="150"/>
<rect width="100" height="100" class="light cell" x="250" y="150"/>
<rect width="100" height="100" class="dark cell" x="350" y="150"/>
<rect width="100" height="100" class="light cell" x="450" y="150"/>
<rect width="100" height="100" class="dark cell" x="550" y="150"/>
<rect width="100" height="100" class="light cell" x="650" y="150"/>
<rect width="100" height="100" class="dark cell" x="750" y="150"/>
<rect width="100" height="100" class="dark cell" x="50" y="250"/>
<rect width="100" height="100" class="light cell" x="150" y="250"/>
<rect width="100" height="100" class="dark cell" x="250" y="250"/>
<rect width="100" height="100" class="light cell" x="350" y="250"/>
<rect width="100" height="100" class="dark cell" x="450" y="250"/>
<rect width="100" height="100" class="light cell" x="550" y="250"/>
<rect width="100" height="100" class="dark cell" x="650" y="250"/>
<rect width="100" height="100" class="light cell" x="750" y="250"/>
<rect width="100" height="100" class="light cell" x="50" y="350"/>
<rect width="100" height="100" class="dark cell" x="150" y="350"/>
<rect width="100" height="100" class="light cell" x="250" y="350"/>
<rect width="100" height="100" class="dark cell" x="350" y="350"/>
<rect width="100" height="100" class="light cell" x="450" y="350"/>
<rect width="100" height="100" class="dark cell" x="550" y="350"/>
<rect width="100" height="100" class="light cell" x="650" y="350"/>
<rect width="100" height="100" class="dark cell" x="750" y="350"/>
<rect width="100" height="100" class="dark cell" x="50" y="450"/>
<rect width="100" height="100" class="light cell" x="150" y="450"/>
<rect width="100" height="100" class="dark cell" x="250" y="450"/>
<rect width="100" height="100" class="light cell" x="350" y="450"/>
<rect width="100" height="100" class="dark cell" x="450" y="450"/>
<rect width="100" height="100" class="light cell" x="550" y="450"/>
<rect width="100" height="100" class="dark cell" x="650" y="450"/>
<rect width="100" height="100" class="light cell" x="750" y="450"/>
<rect width="100" height="100" class="light cell" x="50" y="550"/>
<rect width="100" height="100" class="dark cell" x="150" y="550"/>
<rect width="100" height="100" class="light cell" x="250" y="550"/>
<rect width="100" height="100" class="dark cell" x="350" y="550"/>
<rect width="100" height="100" class="light cell" x="450" y="550"/>
<rect width="100" height="100" class="dark cell" x="550" y="550"/>
<rect width="100" height="100" class="light cell" x="650" y="550"/>
<rect width="100" height="100" class="dark cell" x="750" y="550"/>
<rect width="100" height="100" class="dark cell" x="50" y="650"/>
<rect width="100" height="100" class="light cell" x="150" y="650"/>
<rect width="100" height="100" class="dark cell" x="250" y="650"/>
<rect width="100" height="100" class="light cell" x="350" y="650"/>
<rect width="100" height="100" class="dark cell" x="450" y="650"/>
<rect width="100" height="100" class="light cell" x="550" y="650"/>
<rect width="100" height="100" class="dark cell" x="650" y="650"/>
<rect width="100" height="100" class="light cell" x="750" y="650"/>
<rect width="100" height="100" class="light cell" x="50" y="750"/>
<rect width="100" height="100" class="dark cell" x="150" y="750"/>
<rect width="100" height="100" class="light cell" x="250" y="750"/>
<rect width="100" height="100" class="dark cell" x="350" y="750"/>
<rect width="100" height="100" class="light cell" x="450" y="750"/>
<rect width="100" height="100" class="dark cell" x="550" y="750"/>
<rect width="100" height="100" class="light cell" x="650" y="750"/>
<rect width="100" height="100" class="dark cell" x="750" y="750"/>
<circle class="dark piece" cx="200" cy="100" r="40"/>
<circle class="dark piece" cx="400" cy="100" r="40"/>
<circle class="dark piece" cx="600" cy="100" r="40"/>
<circle class="dark piece" cx="800" cy="100" r="40"/>
<circle class="dark piece" cx="100" cy="200" r="40"/>
<circle class="dark piece" cx="300" cy="200" r="40"/>
<circle class="dark piece" cx="500" cy="200" r="40"/>
<circle class="dark piece" cx="700" cy="200" r="40"/>
<circle class="light piece" cx="200" cy="700" r="40"/>
<circle class="light piece" cx="400" cy="700" r="40"/>
<circle class="light piece" cx="600" cy="700" r="40"/>
<circle class="light piece" cx="800" cy="700" r="40"/>
<circle class="light piece" cx="100" cy="800" r="40"/>
<circle class="light piece" cx="300" cy="800" r="40"/>
<circle class="light piece" cx="500" cy="800" r="40"/>
<circle class="light piece" cx="700" cy="800" r="40"/>
</svg>
Output methods
Save writes next to the script with the same base name and an .svg extension. The showcase files use this form.
Write(path) writes to a specific destination.
Out prints SVG to standard output, which suits shell pipelines and tests.
The runner reads a file operand, -, or standard input when no file is given. Input from standard input has the
logical name output.sevgi, so an implicit Save, PDF, or PNG writes output.svg, output.pdf, or output.png.
Use --as NAME when the pipeline has a more useful identity. NAME is a basename, not a path:
sevgi --as badge < drawing.sevgi
That command evaluates the input as badge.sevgi, making an implicit Save write badge.svg. The option also works
with a file operand and keeps the file's directory, so relative Load calls still resolve beside the source:
sevgi --as proof drawings/card.sevgi
Here an implicit Save writes drawings/proof.svg. Explicit destinations such as Save "build/card.svg" and an
explicit default: always take precedence over the logical input name.
Top-level DSL scope
In a script, call SVG, SVG elements such as rect and circle, and capitalized operations such as Canvas, Paper,
and TileX as plain DSL words. There is little reason to spell out Sevgi's object graph in a file whose job is to draw.
It is still Ruby. Use hashes, loops, calculations, and helper objects wherever they make the drawing easier to read.
The runner installs the full top-level API in a managed scope. The script needs neither require "sevgi" nor an
SVG. facade receiver:
Paper 85, 55, :card
SVG :minimal, :card do
rect width: "100%", height: "100%", rx: 3
end.Save
Library code uses the same words through the facade: the example above becomes SVG.Paper(...) followed by the same
SVG(...) block. Types and callable-module contracts keep their double-colon spelling in both modes, such as
SVG::Canvas and SVG::Module.
Load
Load "palette" evaluates palette.sevgi relative to the active script, not the process working directory. You can
move a drawing split across several files as one directory. If loading fails, the executor error keeps the source stack
and points back to the file that caused it.
Top-level API
Script mode exposes the document entry points SVG, Canvas, Document, Document!, Paper, Paper!, Mixin,
Grid, and Load. Its Derender entry points are Decompile, Derender, Evaluate, and EvaluateChildren; append
File to any of those names when the input is a file path. Drawing words such as Rotate live inside an SVG block.
The DSL Catalog records the context for every word, including script-only helpers.
Rake
Require sevgi/binaries/rake in a Rakefile to run a script without spawning a shell:
require "sevgi/binaries/rake"
file "card.svg" => "card.sevgi" do
sevgi "card", "front", theme: :dark
end
Positional arguments arrive as ARGA; keyword arguments arrive as ARGH.