sevgi 0.95.0 → 1.0.0
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/CHANGELOG.md +221 -2
- data/README.md +27 -9
- data/bin/igsev +6 -0
- data/bin/sevgi +1 -1
- data/lib/sevgi/binaries/igsev.rb +161 -0
- data/lib/sevgi/binaries/rake.rb +8 -3
- data/lib/sevgi/binaries/sevgi.rb +74 -31
- data/lib/sevgi/executor/error.rb +24 -24
- data/lib/sevgi/executor/result.rb +54 -0
- data/lib/sevgi/executor/scope.rb +27 -9
- data/lib/sevgi/executor/source.rb +16 -7
- data/lib/sevgi/executor.rb +106 -106
- data/lib/sevgi/skill.rb +42 -0
- data/lib/sevgi/svg.rb +166 -0
- data/lib/sevgi/toplevel/derender.rb +95 -7
- data/lib/sevgi/toplevel/executor.rb +59 -15
- data/lib/sevgi/toplevel/graphics.rb +93 -6
- data/lib/sevgi/toplevel/sundries.rb +24 -5
- data/lib/sevgi/toplevel.rb +7 -8
- data/lib/sevgi/version.rb +1 -1
- data/lib/sevgi.rb +105 -14
- metadata +36 -17
data/lib/sevgi/executor.rb
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "sevgi/function"
|
|
4
4
|
|
|
5
5
|
require_relative "executor/error"
|
|
6
|
+
require_relative "executor/result"
|
|
6
7
|
require_relative "executor/scope"
|
|
7
8
|
require_relative "executor/source"
|
|
8
9
|
|
|
9
10
|
module Sevgi
|
|
10
|
-
#
|
|
11
|
+
# Internal Sevgi script runtime and namespace for public execution result types.
|
|
11
12
|
#
|
|
12
13
|
# The executor is used by script mode and by the `Load` DSL word to preserve a
|
|
13
14
|
# useful load stack while keeping DSL methods out of the caller's global object
|
|
14
15
|
# whenever possible. Active scope stacks are isolated per Ruby fiber, so
|
|
15
16
|
# concurrent executions can perform nested `Load` calls without sharing scope
|
|
16
|
-
# state. The
|
|
17
|
-
#
|
|
18
|
-
#
|
|
17
|
+
# state. The host owns process signal handlers. Execution captures an Interrupt
|
|
18
|
+
# only when Ruby delivers it inside the executing scope; worker executions do
|
|
19
|
+
# not take ownership of the main thread's signal policy.
|
|
20
|
+
#
|
|
21
|
+
# Consumers execute the full DSL through {Sevgi.execute} or {Sevgi.execute_file}, then inspect {Executor::Result},
|
|
22
|
+
# {Executor::Error}, {Executor::CycleError}, and {Executor::LoadDepthError}. The custom receiver and boot lifecycle is
|
|
23
|
+
# internal plumbing for the top-level API and Rake integration.
|
|
24
|
+
#
|
|
25
|
+
# @see https://sevgi.roktas.dev/usage/#execute Execute source guide
|
|
19
26
|
class Executor
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
private_class_method :new
|
|
22
28
|
private_constant :Scope
|
|
23
29
|
|
|
24
30
|
# Thread-current key used for the fiber-local executor scope stack.
|
|
25
31
|
# @api private
|
|
26
32
|
SCOPE_KEY = :sevgi_executor_scopes
|
|
27
|
-
|
|
33
|
+
SOURCE_LINE_MAX = (2 ** 31) - 1
|
|
34
|
+
private_constant :SCOPE_KEY, :SOURCE_LINE_MAX, :Source
|
|
35
|
+
|
|
36
|
+
# Owns mutable execution state outside the public executor surface.
|
|
37
|
+
# @api private
|
|
38
|
+
class State
|
|
39
|
+
def create(scope = nil) = Scope.new(scope).tap { scopes << it }
|
|
40
|
+
def current = scopes.last
|
|
41
|
+
|
|
42
|
+
def shutdown(scope = nil)
|
|
43
|
+
return scopes.pop unless scope
|
|
44
|
+
return scopes.pop if scopes.last.equal?(scope)
|
|
45
|
+
|
|
46
|
+
scopes.delete(scope)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def scopes = Thread.current[SCOPE_KEY] ||= []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
STATE = State.new
|
|
55
|
+
private_constant :STATE, :State
|
|
28
56
|
|
|
29
57
|
# Loads a script file inside the current executor scope.
|
|
30
58
|
# @param file [String] path to a Sevgi script file
|
|
31
|
-
# @return [Sevgi::Executor::Scope] current execution scope
|
|
59
|
+
# @return [Sevgi::Executor::Scope] current internal execution scope
|
|
32
60
|
# @raise [Sevgi::PanicError] when there is no active executor scope
|
|
33
61
|
# @note Uses the active executor scope from the current fiber.
|
|
34
62
|
# @api private
|
|
35
63
|
def self.load(file, ...)
|
|
36
|
-
PanicError.("
|
|
64
|
+
PanicError.("No active executor scope") unless STATE.current
|
|
37
65
|
|
|
38
|
-
|
|
66
|
+
STATE.current.load(file, ...)
|
|
39
67
|
end
|
|
40
68
|
|
|
69
|
+
private_class_method :load
|
|
70
|
+
|
|
41
71
|
# Executes Ruby source inside a managed Sevgi script scope.
|
|
42
72
|
# @param string [String] source to evaluate
|
|
43
73
|
# @param file [String, nil] source file name used for errors and backtraces
|
|
44
74
|
# @param line [Integer, nil] starting source line used for errors and backtraces
|
|
45
75
|
# @param require [String, nil] optional Ruby library to require before execution
|
|
46
|
-
# @param receiver [Object, nil] receiver used while booting the DSL
|
|
76
|
+
# @param receiver [Object, nil] receiver used verbatim while booting the DSL. Nil selects the isolated execution
|
|
77
|
+
# module, while false and other executable objects remain explicit receivers
|
|
47
78
|
# @yield optional boot block that installs DSL methods before evaluation
|
|
48
79
|
# @yieldreturn [void]
|
|
49
|
-
# @return [Sevgi::Executor::
|
|
50
|
-
# @
|
|
51
|
-
# @note
|
|
52
|
-
#
|
|
80
|
+
# @return [Sevgi::Executor::Result] immutable execution result
|
|
81
|
+
# @raise [Sevgi::ArgumentError] when source, file, line, required library, or receiver is invalid
|
|
82
|
+
# @note Script and required-library failures are captured in {Sevgi::Executor::Result#error}. Inspect
|
|
83
|
+
# {Sevgi::Executor::Error#cause} for the original exception.
|
|
84
|
+
# @note Empty source without `require:` is a strict no-op: no scope is created, the receiver and boot block are
|
|
85
|
+
# unused, and the result stack is empty. Supplying `require:` uses the normal boot and evaluation lifecycle.
|
|
86
|
+
# @note Reentrant and concurrent calls keep independent scope stacks per fiber and preserve the host's signal policy.
|
|
87
|
+
# @api private
|
|
53
88
|
def self.execute(string, file: nil, line: nil, require: nil, receiver: nil, &block)
|
|
89
|
+
validate_source!(string, file, line)
|
|
90
|
+
validate_context!(require, receiver)
|
|
91
|
+
|
|
54
92
|
execute_source(Source.new(string:, file:, line:), require:, receiver:, &block)
|
|
55
93
|
end
|
|
56
94
|
|
|
57
95
|
# Executes a file inside a managed Sevgi script scope.
|
|
58
96
|
# @param file [String] source file to read and execute
|
|
97
|
+
# @param as [String, nil] logical source name used for evaluation and diagnostics
|
|
59
98
|
# @param require [String, nil] optional Ruby library to require before execution
|
|
60
|
-
# @param receiver [Object, nil] receiver used while booting the DSL
|
|
99
|
+
# @param receiver [Object, nil] receiver used verbatim while booting the DSL. Nil selects the isolated execution
|
|
100
|
+
# module, while false and other executable objects remain explicit receivers
|
|
61
101
|
# @yield optional boot block that installs DSL methods before evaluation
|
|
62
102
|
# @yieldreturn [void]
|
|
63
|
-
# @return [Sevgi::Executor::
|
|
64
|
-
# @
|
|
65
|
-
# @note
|
|
66
|
-
#
|
|
67
|
-
|
|
103
|
+
# @return [Sevgi::Executor::Result] immutable execution result
|
|
104
|
+
# @raise [Sevgi::ArgumentError] when file, logical source name, required library, or receiver is invalid
|
|
105
|
+
# @note File-read, script, and required-library failures are captured in {Sevgi::Executor::Result#error}. Inspect
|
|
106
|
+
# {Sevgi::Executor::Result#stack} for nested loads.
|
|
107
|
+
# @note An empty file without `require:` is a strict no-op: no scope is created, the receiver and boot block are
|
|
108
|
+
# unused, and the result stack is empty. Supplying `require:` uses the normal boot and evaluation lifecycle.
|
|
109
|
+
# @note Reentrant and concurrent calls keep independent scope stacks per fiber and preserve the host's signal policy.
|
|
110
|
+
# @api private
|
|
111
|
+
def self.execute_file(file, as: nil, require: nil, receiver: nil, &block)
|
|
112
|
+
ArgumentError.("Executor file must be a String") unless file.is_a?(::String)
|
|
113
|
+
ArgumentError.("Executor logical file must be a String or nil") unless as.nil? || as.is_a?(::String)
|
|
114
|
+
validate_context!(require, receiver)
|
|
115
|
+
|
|
68
116
|
source = nil
|
|
69
117
|
begin
|
|
70
|
-
source = Source.load(file)
|
|
118
|
+
source = Source.load(file, as:)
|
|
71
119
|
rescue ::SystemCallError => e
|
|
72
|
-
return capture_error(Source.new(string: "", file
|
|
120
|
+
return capture_error(Source.new(string: "", file: as || file, line: 1, origin: file), e)
|
|
73
121
|
end
|
|
74
122
|
|
|
75
123
|
execute_source(source, require:, receiver:, &block)
|
|
76
124
|
end
|
|
77
125
|
|
|
78
|
-
|
|
79
|
-
# @return [Sevgi::Executor::Scope, nil] removed scope
|
|
80
|
-
# @api private
|
|
81
|
-
def self.shutdown
|
|
82
|
-
instance.shutdown
|
|
83
|
-
end
|
|
126
|
+
private_class_method :execute, :execute_file
|
|
84
127
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@signal_mutex = Mutex.new
|
|
88
|
-
@signal_previous = nil
|
|
89
|
-
end
|
|
128
|
+
class << self
|
|
129
|
+
private
|
|
90
130
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# Returns the active executor scope.
|
|
98
|
-
# @return [Sevgi::Executor::Scope, nil] active scope, if any
|
|
99
|
-
# @api private
|
|
100
|
-
def current = scopes.last
|
|
101
|
-
|
|
102
|
-
# Restores the process SIGINT handler when the last active execution ends.
|
|
103
|
-
# @return [void]
|
|
104
|
-
# @api private
|
|
105
|
-
def restore
|
|
106
|
-
@signal_mutex.synchronize do
|
|
107
|
-
next if @signal_count.zero?
|
|
108
|
-
|
|
109
|
-
@signal_count -= 1
|
|
110
|
-
next unless @signal_count.zero?
|
|
111
|
-
|
|
112
|
-
Signal.trap("INT", @signal_previous)
|
|
113
|
-
@signal_previous = nil
|
|
131
|
+
def capture_error(source, error)
|
|
132
|
+
scope = STATE.create
|
|
133
|
+
scope.capture(source, error).result
|
|
134
|
+
ensure
|
|
135
|
+
STATE.shutdown(scope) if scope
|
|
114
136
|
end
|
|
115
|
-
end
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# @return [Sevgi::Executor::Scope, nil] removed scope
|
|
120
|
-
# @api private
|
|
121
|
-
def shutdown(scope = nil)
|
|
122
|
-
return scopes.pop unless scope
|
|
123
|
-
return scopes.pop if scopes.last.equal?(scope)
|
|
124
|
-
|
|
125
|
-
scopes.delete(scope)
|
|
126
|
-
end
|
|
138
|
+
def execute_source(source, require:, receiver:, &block)
|
|
139
|
+
return Result.new(value: nil, error: nil, stack: []) if source.string.empty? && require.nil?
|
|
127
140
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def trap
|
|
132
|
-
@signal_mutex.synchronize do
|
|
133
|
-
@signal_previous = Signal.trap("INT") { Kernel.abort("") } if @signal_count.zero?
|
|
141
|
+
scope = STATE.create
|
|
142
|
+
catch(:result) { run_source(scope, source, require, receiver, &block) }
|
|
143
|
+
scope.result
|
|
134
144
|
|
|
135
|
-
|
|
145
|
+
ensure
|
|
146
|
+
STATE.shutdown(scope) if scope
|
|
136
147
|
end
|
|
137
|
-
end
|
|
138
148
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
def run_source(scope, source, library, receiver, &block)
|
|
150
|
+
::Kernel.require(library) if library
|
|
151
|
+
scope.call(source, receiver, &block)
|
|
152
|
+
# Required libraries use the same failure policy as executable script source.
|
|
153
|
+
# rubocop:disable-next Lint/RescueException
|
|
154
|
+
rescue ::Exception => e
|
|
155
|
+
scope.capture(source, e)
|
|
156
|
+
end
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return if source.string.empty? && require.nil?
|
|
158
|
+
def validate_context!(library, receiver)
|
|
159
|
+
ArgumentError.("Executor library must be a String or nil") unless library.nil? || library.is_a?(::String)
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
valid = (receiver in ::Object) && receiver.respond_to?(:public_send) && receiver.respond_to?(:instance_exec)
|
|
162
|
+
ArgumentError.("Executor receiver must be an executable Object or nil") unless valid
|
|
163
|
+
end
|
|
155
164
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
end
|
|
165
|
+
def validate_source!(string, file, line)
|
|
166
|
+
ArgumentError.("Executor source must be a String") unless string.is_a?(::String)
|
|
167
|
+
ArgumentError.("Executor file must be a String or nil") unless file.nil? || file.is_a?(::String)
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
rescue ::LoadError => e
|
|
165
|
-
scope.capture(source, e)
|
|
169
|
+
valid = line.nil? || (line.is_a?(::Integer) && line.between?(1, SOURCE_LINE_MAX))
|
|
170
|
+
ArgumentError.("Executor line must be between 1 and #{SOURCE_LINE_MAX}") unless valid
|
|
171
|
+
end
|
|
166
172
|
end
|
|
167
|
-
|
|
168
|
-
private_class_method :capture_error, :execute_source, :run_source
|
|
169
|
-
|
|
170
|
-
private
|
|
171
|
-
|
|
172
|
-
def scopes = Thread.current[SCOPE_KEY] ||= []
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
end
|
data/lib/sevgi/skill.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems"
|
|
4
|
+
require "sevgi"
|
|
5
|
+
|
|
6
|
+
module Sevgi
|
|
7
|
+
# Locates the agent skill packaged for the installed Sevgi version.
|
|
8
|
+
# @api private
|
|
9
|
+
module Skill
|
|
10
|
+
extend self
|
|
11
|
+
|
|
12
|
+
Error = Class.new(::Sevgi::Error)
|
|
13
|
+
|
|
14
|
+
def path
|
|
15
|
+
spec = ::Gem::Specification.find_by_name("sevgi-appendix", "= #{::Sevgi::VERSION}")
|
|
16
|
+
packaged = packaged_path(spec)
|
|
17
|
+
# Package managers can replace a versioned gem path with their stable prefix.
|
|
18
|
+
path = ::File.expand_path(ENV.fetch("SEVGI_SKILL", packaged))
|
|
19
|
+
|
|
20
|
+
Error.("Sevgi skill is unavailable at #{path}.") unless ::File.file?(::File.join(path, "SKILL.md"))
|
|
21
|
+
|
|
22
|
+
path
|
|
23
|
+
rescue ::Gem::MissingSpecError
|
|
24
|
+
Error.("sevgi-appendix #{::Sevgi::VERSION} is not installed.")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def packaged_path(spec)
|
|
30
|
+
relative = spec.metadata["sevgi_skill_path"]
|
|
31
|
+
root = ::File.expand_path(spec.full_gem_path)
|
|
32
|
+
path = ::File.expand_path(relative, root) if relative.is_a?(String) && !relative.empty?
|
|
33
|
+
inside = path&.start_with?("#{root}#{::File::SEPARATOR}")
|
|
34
|
+
|
|
35
|
+
Error.("sevgi-appendix #{::Sevgi::VERSION} does not declare a valid skill path.") unless inside
|
|
36
|
+
|
|
37
|
+
path
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private_constant :Skill
|
|
42
|
+
end
|
data/lib/sevgi/svg.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevgi
|
|
4
|
+
# Public SVG facade installed by `require "sevgi"`.
|
|
5
|
+
#
|
|
6
|
+
# Capitalized methods are SVG DSL operations and constructors: {SVG.Canvas}
|
|
7
|
+
# creates a canvas, {SVG.Document} defines a document profile, {SVG.Module}
|
|
8
|
+
# builds a callable drawing module, and {SVG.Paper} registers a paper size.
|
|
9
|
+
# Double-colon names are Ruby constants and types, such as {SVG::Canvas},
|
|
10
|
+
# {SVG::Document}, and {SVG::Module}. The global `SVG(...)` method builds a
|
|
11
|
+
# drawing. There is intentionally no stuttering `SVG.SVG(...)` form.
|
|
12
|
+
#
|
|
13
|
+
# Lowercase constructors remain on {Sevgi::Graphics} for focused component
|
|
14
|
+
# use. Script execution belongs to {Sevgi.execute} and is not part of this
|
|
15
|
+
# SVG-domain facade.
|
|
16
|
+
#
|
|
17
|
+
# @example Compose a drawing through the facade
|
|
18
|
+
# SVG.Paper 85, 55, :card
|
|
19
|
+
# canvas = SVG.Canvas :card, margins: 4
|
|
20
|
+
# profile = SVG.Document attributes: {viewBox: "0 0 85 55"}
|
|
21
|
+
#
|
|
22
|
+
# drawing = SVG profile, canvas do
|
|
23
|
+
# rect width: 85, height: 55, rx: 3
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# drawing.Render
|
|
27
|
+
# @example Distinguish an operation from its result type
|
|
28
|
+
# canvas = SVG.Canvas width: 24, height: 24, unit: :px
|
|
29
|
+
# canvas.is_a?(SVG::Canvas) #=> true
|
|
30
|
+
# @see https://sevgi.roktas.dev/start/ Getting started
|
|
31
|
+
# @see https://sevgi.roktas.dev/usage/ Usage guide
|
|
32
|
+
# @see https://sevgi.roktas.dev/compose/ Composition guide
|
|
33
|
+
module SVG
|
|
34
|
+
# SVG attribute collection and normalization API.
|
|
35
|
+
Attributes = Graphics::Attributes
|
|
36
|
+
# Canvas value type returned by {SVG.Canvas}.
|
|
37
|
+
Canvas = Graphics::Canvas
|
|
38
|
+
# Explicit SVG content serialization types.
|
|
39
|
+
Content = Graphics::Content
|
|
40
|
+
# Document profiles and document classes.
|
|
41
|
+
Document = Graphics::Document
|
|
42
|
+
# Base SVG/XML element type.
|
|
43
|
+
Element = Graphics::Element
|
|
44
|
+
# Duplicate-id and structural lint failure.
|
|
45
|
+
LintError = Graphics::LintError
|
|
46
|
+
# CSS-like canvas margin value.
|
|
47
|
+
Margin = Graphics::Margin
|
|
48
|
+
# Graphics mixtures available for document composition.
|
|
49
|
+
Mixtures = Graphics::Mixtures
|
|
50
|
+
# Contract for one callable drawing module.
|
|
51
|
+
Module = Graphics::Module
|
|
52
|
+
# Recursive contract for namespaces of callable drawing modules.
|
|
53
|
+
Modules = Graphics::Modules
|
|
54
|
+
# Immutable paper-size value and profile registry.
|
|
55
|
+
Paper = Graphics::Paper
|
|
56
|
+
# Current Sevgi toolkit version.
|
|
57
|
+
VERSION = Sevgi::VERSION
|
|
58
|
+
|
|
59
|
+
# Builds a canvas from a paper profile or explicit dimensions.
|
|
60
|
+
# @return [Sevgi::Graphics::Canvas] canvas value
|
|
61
|
+
# @see Sevgi::Toplevel#Canvas
|
|
62
|
+
def self.Canvas(...) = Sevgi.Canvas(...)
|
|
63
|
+
|
|
64
|
+
# Converts inline SVG/XML into an immutable Derender node.
|
|
65
|
+
# @return [Sevgi::Derender::Node] selected node
|
|
66
|
+
# @see Sevgi::Toplevel#Decompile
|
|
67
|
+
def self.Decompile(...) = Sevgi.Decompile(...)
|
|
68
|
+
|
|
69
|
+
# Converts an SVG/XML file into an immutable Derender node.
|
|
70
|
+
# @return [Sevgi::Derender::Node] selected node
|
|
71
|
+
# @see Sevgi::Toplevel#DecompileFile
|
|
72
|
+
def self.DecompileFile(...) = Sevgi.DecompileFile(...)
|
|
73
|
+
|
|
74
|
+
# Converts inline SVG/XML into formatted Sevgi DSL source.
|
|
75
|
+
# @return [String] formatted Sevgi DSL source
|
|
76
|
+
# @see Sevgi::Toplevel#Derender
|
|
77
|
+
def self.Derender(...) = Sevgi.Derender(...)
|
|
78
|
+
|
|
79
|
+
# Converts an SVG/XML file into formatted Sevgi DSL source.
|
|
80
|
+
# @return [String] formatted Sevgi DSL source
|
|
81
|
+
# @see Sevgi::Toplevel#DerenderFile
|
|
82
|
+
def self.DerenderFile(...) = Sevgi.DerenderFile(...)
|
|
83
|
+
|
|
84
|
+
# Defines, validates, or looks up a document profile.
|
|
85
|
+
# @return [Class] document class
|
|
86
|
+
# @see Sevgi::Toplevel#Document
|
|
87
|
+
def self.Document(...) = Sevgi.Document(...)
|
|
88
|
+
|
|
89
|
+
# Defines or replaces a document profile.
|
|
90
|
+
# @return [Class] document class
|
|
91
|
+
# @see Sevgi::Toplevel#Document!
|
|
92
|
+
def self.Document!(...) = Sevgi.Document!(...)
|
|
93
|
+
|
|
94
|
+
# Includes an inline SVG/XML node under a graphics element.
|
|
95
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
|
|
96
|
+
# @see Sevgi::Toplevel#Evaluate
|
|
97
|
+
def self.Evaluate(...) = Sevgi.Evaluate(...)
|
|
98
|
+
|
|
99
|
+
# Includes only an inline SVG/XML node's children under a graphics element.
|
|
100
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
101
|
+
# @see Sevgi::Toplevel#EvaluateChildren
|
|
102
|
+
def self.EvaluateChildren(...) = Sevgi.EvaluateChildren(...)
|
|
103
|
+
|
|
104
|
+
# Includes only an SVG/XML file node's children under a graphics element.
|
|
105
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
106
|
+
# @see Sevgi::Toplevel#EvaluateChildrenFile
|
|
107
|
+
def self.EvaluateChildrenFile(...) = Sevgi.EvaluateChildrenFile(...)
|
|
108
|
+
|
|
109
|
+
# Includes an SVG/XML file node under a graphics element.
|
|
110
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
|
|
111
|
+
# @see Sevgi::Toplevel#EvaluateFile
|
|
112
|
+
def self.EvaluateFile(...) = Sevgi.EvaluateFile(...)
|
|
113
|
+
|
|
114
|
+
# Fits a drawable grid to a graphics canvas.
|
|
115
|
+
# @return [Sevgi::Sundries::Grid] fitted grid
|
|
116
|
+
# @see Sevgi::Toplevel#Grid
|
|
117
|
+
def self.Grid(...) = Sevgi.Grid(...)
|
|
118
|
+
|
|
119
|
+
# Fits major and minor intervals into a span.
|
|
120
|
+
# @return [Sevgi::Sundries::Ruler] fitted ruler
|
|
121
|
+
# @see Sevgi::Toplevel#Ruler
|
|
122
|
+
def self.Ruler(...) = Sevgi.Ruler(...)
|
|
123
|
+
|
|
124
|
+
# Loads nested `.sevgi` files through the active executor scope.
|
|
125
|
+
# @return [Array<String>] requested file names
|
|
126
|
+
# @see Sevgi::Toplevel#Load
|
|
127
|
+
def self.Load(...) = Sevgi.Load(...)
|
|
128
|
+
|
|
129
|
+
# Extends a document profile with a named or anonymous graphics mixture.
|
|
130
|
+
# @return [Module, nil] anonymous mixture when supplied, otherwise nil
|
|
131
|
+
# @see Sevgi::Toplevel#Mixin
|
|
132
|
+
def self.Mixin(...) = Sevgi.Mixin(...)
|
|
133
|
+
|
|
134
|
+
# Builds an anonymous callable drawing module.
|
|
135
|
+
# The callable contract is installed before the optional definition runs, so `base` and drawing-method tracking
|
|
136
|
+
# are available throughout the block. The definition keeps its ordinary Ruby lexical constant scope.
|
|
137
|
+
# @yield [mod] optionally defines the callable module
|
|
138
|
+
# @yieldparam mod [Module] module being defined
|
|
139
|
+
# @yieldreturn [Object] ignored definition result
|
|
140
|
+
# @return [Module] new anonymous module extended with {SVG::Module}
|
|
141
|
+
# @example Build a callable drawing module
|
|
142
|
+
# label = SVG.Module do
|
|
143
|
+
# base { circle r: 8, fill: "seagreen" }
|
|
144
|
+
# def call(value) = text value
|
|
145
|
+
# end
|
|
146
|
+
#
|
|
147
|
+
# SVG(:minimal) { Call label, "OK" }
|
|
148
|
+
# @see SVG::Module
|
|
149
|
+
def self.Module(&definition)
|
|
150
|
+
mod = ::Module.new
|
|
151
|
+
mod.extend(Graphics::Module)
|
|
152
|
+
mod.module_eval(&definition) if definition
|
|
153
|
+
mod
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Defines or validates a named paper profile.
|
|
157
|
+
# @return [Symbol, String] original paper profile name
|
|
158
|
+
# @see Sevgi::Toplevel#Paper
|
|
159
|
+
def self.Paper(...) = Sevgi.Paper(...)
|
|
160
|
+
|
|
161
|
+
# Defines or overwrites a named paper profile.
|
|
162
|
+
# @return [Symbol, String] original paper profile name
|
|
163
|
+
# @see Sevgi::Toplevel#Paper!
|
|
164
|
+
def self.Paper!(...) = Sevgi.Paper!(...)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -4,20 +4,108 @@ require "sevgi/derender"
|
|
|
4
4
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Toplevel
|
|
7
|
+
# Converts inline SVG/XML content into a derender node.
|
|
8
|
+
# @param content [String] SVG/XML source content
|
|
9
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
10
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
11
|
+
# subtree after id selection
|
|
12
|
+
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
13
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
|
|
14
|
+
# @see Sevgi.Decompile
|
|
15
|
+
# @see Sevgi::Derender.decompile
|
|
16
|
+
def Decompile(content, id: nil, omit: nil) = Derender.decompile(content, id:, omit:)
|
|
17
|
+
|
|
7
18
|
# Converts an SVG/XML file into a derender node.
|
|
8
19
|
# @param file [String] path to the source SVG/XML file
|
|
9
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
20
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
21
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
22
|
+
# subtree after id selection
|
|
10
23
|
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
11
|
-
# @raise [Sevgi::ArgumentError] when the file
|
|
24
|
+
# @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
|
|
25
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
26
|
+
# @see Sevgi.DecompileFile
|
|
12
27
|
# @see Sevgi::Derender.decompile_file
|
|
13
|
-
def
|
|
28
|
+
def DecompileFile(file, id: nil, omit: nil) = Derender.decompile_file(file, id:, omit:)
|
|
14
29
|
|
|
15
|
-
# Converts
|
|
30
|
+
# Converts inline SVG/XML content into Sevgi DSL Ruby source. The returned String is ordinary Ruby source. Review
|
|
31
|
+
# and integrate it statically rather than using Ruby's raw dynamic evaluation methods.
|
|
32
|
+
# @param content [String] SVG/XML source content
|
|
33
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
34
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
35
|
+
# subtree after id selection
|
|
36
|
+
# @return [String] formatted Sevgi DSL source
|
|
37
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
|
|
38
|
+
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
39
|
+
# @see Sevgi.Derender
|
|
40
|
+
# @see Sevgi::Derender.derender
|
|
41
|
+
def Derender(content, id: nil, omit: nil) = Derender.derender(content, id:, omit:)
|
|
42
|
+
|
|
43
|
+
# Converts an SVG/XML file into Sevgi DSL Ruby source. The returned String is ordinary Ruby source. Review and
|
|
44
|
+
# integrate it statically rather than using Ruby's raw dynamic evaluation methods.
|
|
16
45
|
# @param file [String] path to the source SVG/XML file
|
|
17
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
46
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
47
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
48
|
+
# subtree after id selection
|
|
18
49
|
# @return [String] formatted Sevgi DSL source
|
|
19
|
-
# @raise [Sevgi::ArgumentError] when the file
|
|
50
|
+
# @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
|
|
51
|
+
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
52
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
53
|
+
# @see Sevgi.DerenderFile
|
|
20
54
|
# @see Sevgi::Derender.derender_file
|
|
21
|
-
def
|
|
55
|
+
def DerenderFile(file, id: nil, omit: nil) = Derender.derender_file(file, id:, omit:)
|
|
56
|
+
|
|
57
|
+
# Evaluates inline SVG/XML content under a graphics element, including the selected node.
|
|
58
|
+
# @param content [String] SVG/XML source content
|
|
59
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
60
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
61
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
62
|
+
# subtree after id selection
|
|
63
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root element, or nil when it produces no output
|
|
64
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
|
|
65
|
+
# @see Sevgi.Evaluate
|
|
66
|
+
# @see Sevgi::Derender.evaluate
|
|
67
|
+
def Evaluate(content, element, id: nil, omit: nil) = Derender.evaluate(content, element, id:, omit:)
|
|
68
|
+
|
|
69
|
+
# Evaluates only the selected node's children from inline SVG/XML content under a graphics element.
|
|
70
|
+
# @param content [String] SVG/XML source content
|
|
71
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
72
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
73
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
74
|
+
# subtree after id selection
|
|
75
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
76
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
|
|
77
|
+
# @see Sevgi.EvaluateChildren
|
|
78
|
+
# @see Sevgi::Derender.evaluate_children
|
|
79
|
+
def EvaluateChildren(content, element, id: nil, omit: nil)
|
|
80
|
+
Derender.evaluate_children(content, element, id:, omit:)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Evaluates only the selected node's children from an SVG/XML file under a graphics element.
|
|
84
|
+
# @param file [String] path to the source SVG/XML file
|
|
85
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
86
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
87
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
88
|
+
# subtree after id selection
|
|
89
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
90
|
+
# @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
|
|
91
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
92
|
+
# @see Sevgi.EvaluateChildrenFile
|
|
93
|
+
# @see Sevgi::Derender.evaluate_children_file
|
|
94
|
+
def EvaluateChildrenFile(file, element, id: nil, omit: nil)
|
|
95
|
+
Derender.evaluate_children_file(file, element, id:, omit:)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Evaluates an SVG/XML file under a graphics element, including the selected node.
|
|
99
|
+
# @param file [String] path to the source SVG/XML file
|
|
100
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
101
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
102
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
103
|
+
# subtree after id selection
|
|
104
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root element, or nil when it produces no output
|
|
105
|
+
# @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
|
|
106
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
107
|
+
# @see Sevgi.EvaluateFile
|
|
108
|
+
# @see Sevgi::Derender.evaluate_file
|
|
109
|
+
def EvaluateFile(file, element, id: nil, omit: nil) = Derender.evaluate_file(file, element, id:, omit:)
|
|
22
110
|
end
|
|
23
111
|
end
|