sevgi 0.94.0 → 0.98.2

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.
@@ -4,31 +4,48 @@ module Sevgi
4
4
  class Executor
5
5
  # Describes Ruby source evaluated by the Sevgi executor.
6
6
  # @api private
7
- Source = Data.define(:string, :file, :line) do
8
- # @overload call(string:, file: nil, line: nil)
7
+ Source = Data.define(:string, :file, :line, :origin) do
8
+ # @overload call(string:, file: nil, line: nil, origin: nil)
9
9
  # Builds a source object.
10
10
  # @param string [String] Ruby source string
11
11
  # @param file [String, nil] source file name for diagnostics
12
12
  # @param line [Integer, nil] starting source line for diagnostics
13
+ # @param origin [String, nil] physical source path used for load-cycle identity
13
14
  # @return [Sevgi::Executor::Source] source object
14
15
  def self.call(...) = new(...)
15
16
 
16
17
  # Builds a source object from a file.
17
18
  # @param file [String] source file to read
19
+ # @param as [String, nil] logical source name used for evaluation and diagnostics
18
20
  # @return [Sevgi::Executor::Source] source object with file contents
19
21
  # @raise [Errno::ENOENT] when the file cannot be read
20
- def self.load(file) = new(string: ::File.read(file), file: file, line: 1)
22
+ def self.load(file, as: nil) = new(string: ::File.read(file), file: as || file, line: 1, origin: file)
21
23
 
22
24
  # Creates a source object.
23
25
  # @param string [String] Ruby source string
24
26
  # @param file [String, nil] source file name for diagnostics
25
27
  # @param line [Integer, nil] starting source line for diagnostics
28
+ # @param origin [String, nil] physical source path used for load-cycle identity
26
29
  # @return [void]
27
- def initialize(string:, file: nil, line: nil) = super(string:, file: file || "sevgi", line: line || 1)
30
+ def initialize(string:, file: nil, line: nil, origin: nil)
31
+ super(string:, file: file || "sevgi", line: line || 1, origin:)
32
+ end
33
+
34
+ private_class_method :[]
28
35
 
29
36
  # Returns the stack key used for this source.
30
37
  # @return [String] source file name
31
38
  def key = file
39
+
40
+ # Returns the canonical identity used for active-load cycle detection.
41
+ # @return [Integer, String] object identity for inline source, or canonical physical identity for loaded source
42
+ def identity
43
+ return object_id unless origin
44
+
45
+ ::File.realpath(origin)
46
+ rescue ::SystemCallError
47
+ ::File.expand_path(origin)
48
+ end
32
49
  end
33
50
  end
34
51
  end
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "singleton"
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
- # Executes Sevgi script source inside an isolated module scope.
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
@@ -16,156 +17,189 @@ module Sevgi
16
17
  # state. The process SIGINT handler is shared by Ruby, so executor runs guard it
17
18
  # with a reference-counted critical section and restore the previous handler
18
19
  # after the last active execution finishes.
20
+ #
21
+ # Consumers execute the full DSL through {Sevgi.execute} or {Sevgi.execute_file}, then inspect {Executor::Result},
22
+ # {Executor::Error}, and {Executor::CycleError}. The custom receiver and boot lifecycle is internal plumbing for the
23
+ # top-level API and Rake integration.
24
+ #
25
+ # @see https://sevgi.roktas.dev/execution/ Execution guide
19
26
  class Executor
20
- include Singleton
27
+ private_class_method :new
28
+ private_constant :Scope
21
29
 
22
30
  # Thread-current key used for the fiber-local executor scope stack.
23
31
  # @api private
24
32
  SCOPE_KEY = :sevgi_executor_scopes
25
- private_constant :SCOPE_KEY, :Source
33
+ SOURCE_LINE_MAX = (2 ** 31) - 1
34
+ private_constant :SCOPE_KEY, :SOURCE_LINE_MAX, :Source
35
+
36
+ # Owns mutable execution and process-signal state outside the public executor surface.
37
+ # @api private
38
+ class State
39
+ def initialize
40
+ @signal_count = 0
41
+ @signal_mutex = Mutex.new
42
+ @signal_previous = nil
43
+ end
44
+
45
+ def create(scope = nil) = Scope.new(scope).tap { scopes << it }
46
+ def current = scopes.last
47
+
48
+ def restore
49
+ @signal_mutex.synchronize do
50
+ next if @signal_count.zero?
51
+
52
+ @signal_count -= 1
53
+ next unless @signal_count.zero?
54
+
55
+ Signal.trap("INT", @signal_previous)
56
+ @signal_previous = nil
57
+ end
58
+ end
59
+
60
+ def shutdown(scope = nil)
61
+ return scopes.pop unless scope
62
+ return scopes.pop if scopes.last.equal?(scope)
63
+
64
+ scopes.delete(scope)
65
+ end
66
+
67
+ def trap
68
+ @signal_mutex.synchronize do
69
+ @signal_previous = Signal.trap("INT") { Kernel.abort("") } if @signal_count.zero?
70
+ @signal_count += 1
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def scopes = Thread.current[SCOPE_KEY] ||= []
77
+ end
78
+
79
+ STATE = State.new
80
+ private_constant :STATE, :State
26
81
 
27
82
  # Loads a script file inside the current executor scope.
28
83
  # @param file [String] path to a Sevgi script file
29
- # @return [Sevgi::Executor::Scope] current execution scope
84
+ # @return [Sevgi::Executor::Scope] current internal execution scope
30
85
  # @raise [Sevgi::PanicError] when there is no active executor scope
31
86
  # @note Uses the active executor scope from the current fiber.
32
87
  # @api private
33
88
  def self.load(file, ...)
34
- PanicError.("box stack empty; create a box first") unless instance.current
89
+ PanicError.("box stack empty; create a box first") unless STATE.current
35
90
 
36
- instance.current.load(file, ...)
91
+ STATE.current.load(file, ...)
37
92
  end
38
93
 
94
+ private_class_method :load
95
+
39
96
  # Executes Ruby source inside a managed Sevgi script scope.
40
97
  # @param string [String] source to evaluate
41
98
  # @param file [String, nil] source file name used for errors and backtraces
42
99
  # @param line [Integer, nil] starting source line used for errors and backtraces
43
100
  # @param require [String, nil] optional Ruby library to require before execution
44
- # @param receiver [Object, nil] receiver used while booting the DSL
101
+ # @param receiver [Object, nil] receiver used verbatim while booting the DSL; nil selects the isolated execution
102
+ # module, while false and other executable objects remain explicit receivers
45
103
  # @yield optional boot block that installs DSL methods before evaluation
46
104
  # @yieldreturn [void]
47
- # @return [Sevgi::Executor::Scope, nil] execution scope, or nil for empty source
48
- # @note Required-library load failures are captured as {Sevgi::Executor::Error} on the returned scope.
105
+ # @return [Sevgi::Executor::Result] immutable execution result
106
+ # @raise [Sevgi::ArgumentError] when source, file, line, required library, or receiver is invalid
107
+ # @note Script and required-library failures are captured in {Sevgi::Executor::Result#error}; inspect
108
+ # {Sevgi::Executor::Error#cause} for the original exception.
109
+ # @note Empty source without `require:` is a strict no-op: no scope is created, the receiver and boot block are
110
+ # unused, and the result stack is empty. Supplying `require:` uses the normal boot and evaluation lifecycle.
49
111
  # @note Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains
50
112
  # process-global while any execution is active.
113
+ # @api private
51
114
  def self.execute(string, file: nil, line: nil, require: nil, receiver: nil, &block)
115
+ validate_source!(string, file, line)
116
+ validate_context!(require, receiver)
117
+
52
118
  execute_source(Source.new(string:, file:, line:), require:, receiver:, &block)
53
119
  end
54
120
 
55
121
  # Executes a file inside a managed Sevgi script scope.
56
122
  # @param file [String] source file to read and execute
123
+ # @param as [String, nil] logical source name used for evaluation and diagnostics
57
124
  # @param require [String, nil] optional Ruby library to require before execution
58
- # @param receiver [Object, nil] receiver used while booting the DSL
125
+ # @param receiver [Object, nil] receiver used verbatim while booting the DSL; nil selects the isolated execution
126
+ # module, while false and other executable objects remain explicit receivers
59
127
  # @yield optional boot block that installs DSL methods before evaluation
60
128
  # @yieldreturn [void]
61
- # @return [Sevgi::Executor::Scope, nil] execution scope, or nil for an empty file
62
- # @note File-read and required-library load failures are captured as {Sevgi::Executor::Error} on the returned scope.
129
+ # @return [Sevgi::Executor::Result] immutable execution result
130
+ # @raise [Sevgi::ArgumentError] when file, logical source name, required library, or receiver is invalid
131
+ # @note File-read, script, and required-library failures are captured in {Sevgi::Executor::Result#error}; inspect
132
+ # {Sevgi::Executor::Result#stack} for nested loads.
133
+ # @note An empty file without `require:` is a strict no-op: no scope is created, the receiver and boot block are
134
+ # unused, and the result stack is empty. Supplying `require:` uses the normal boot and evaluation lifecycle.
63
135
  # @note Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains
64
136
  # process-global while any execution is active.
65
- def self.execute_file(file, require: nil, receiver: nil, &block)
137
+ # @api private
138
+ def self.execute_file(file, as: nil, require: nil, receiver: nil, &block)
139
+ ArgumentError.("Executor file must be a String") unless file.is_a?(::String)
140
+ ArgumentError.("Executor logical file must be a String or nil") unless as.nil? || as.is_a?(::String)
141
+ validate_context!(require, receiver)
142
+
66
143
  source = nil
67
144
  begin
68
- source = Source.load(file)
145
+ source = Source.load(file, as:)
69
146
  rescue ::SystemCallError => e
70
- return capture_error(Source.new(string: "", file:, line: 1), e)
147
+ return capture_error(Source.new(string: "", file: as || file, line: 1, origin: file), e)
71
148
  end
72
149
 
73
150
  execute_source(source, require:, receiver:, &block)
74
151
  end
75
152
 
76
- # Removes the current executor scope.
77
- # @return [Sevgi::Executor::Scope, nil] removed scope
78
- # @api private
79
- def self.shutdown
80
- instance.shutdown
81
- end
82
-
83
- def initialize
84
- @signal_count = 0
85
- @signal_mutex = Mutex.new
86
- @signal_previous = nil
87
- end
153
+ private_class_method :execute, :execute_file
88
154
 
89
- # Creates and pushes a new executor scope.
90
- # @param scope [Module, nil] existing module scope to reuse
91
- # @return [Sevgi::Executor::Scope] created scope
92
- # @api private
93
- def create(scope = nil) = Scope.new(scope).tap { scopes << it }
155
+ class << self
156
+ private
94
157
 
95
- # Returns the active executor scope.
96
- # @return [Sevgi::Executor::Scope, nil] active scope, if any
97
- # @api private
98
- def current = scopes.last
99
-
100
- # Restores the process SIGINT handler when the last active execution ends.
101
- # @return [void]
102
- # @api private
103
- def restore
104
- @signal_mutex.synchronize do
105
- next if @signal_count.zero?
106
-
107
- @signal_count -= 1
108
- next unless @signal_count.zero?
109
-
110
- Signal.trap("INT", @signal_previous)
111
- @signal_previous = nil
158
+ def capture_error(source, error)
159
+ acquired = STATE.trap
160
+ scope = STATE.create
161
+ scope.capture(source, error).result
162
+ ensure
163
+ STATE.restore if acquired
164
+ STATE.shutdown(scope) if scope
112
165
  end
113
- end
114
166
 
115
- # Removes the active executor scope.
116
- # @param scope [Sevgi::Executor::Scope, nil] exact scope to remove, or nil to pop the current scope
117
- # @return [Sevgi::Executor::Scope, nil] removed scope
118
- # @api private
119
- def shutdown(scope = nil)
120
- return scopes.pop unless scope
121
- return scopes.pop if scopes.last.equal?(scope)
167
+ def execute_source(source, require:, receiver:, &block)
168
+ acquired = false
169
+ return Result.new(value: nil, error: nil, stack: []) if source.string.empty? && require.nil?
122
170
 
123
- scopes.delete(scope)
124
- end
171
+ acquired = STATE.trap
172
+ scope = STATE.create
173
+ catch(:result) { run_source(scope, source, require, receiver, &block) }
174
+ scope.result
125
175
 
126
- # Installs the process SIGINT handler while one or more executions are active.
127
- # @return [void]
128
- # @api private
129
- def trap
130
- @signal_mutex.synchronize do
131
- @signal_previous = Signal.trap("INT") { Kernel.abort("") } if @signal_count.zero?
132
-
133
- @signal_count += 1
176
+ ensure
177
+ STATE.restore if acquired
178
+ STATE.shutdown(scope) if scope
134
179
  end
135
- end
136
180
 
137
- def self.capture_error(source, error)
138
- instance.trap
139
- scope = instance.create
140
- scope.capture(source, error)
141
- ensure
142
- instance.restore
143
- instance.shutdown(scope) if scope
144
- end
181
+ def run_source(scope, source, library, receiver, &block)
182
+ ::Kernel.require(library) if library
183
+ scope.call(source, receiver, &block)
184
+ rescue ::LoadError => e
185
+ scope.capture(source, e)
186
+ end
145
187
 
146
- def self.execute_source(source, require:, receiver:, &block)
147
- return if source.string.empty?
188
+ def validate_context!(library, receiver)
189
+ ArgumentError.("Executor library must be a String or nil") unless library.nil? || library.is_a?(::String)
148
190
 
149
- instance.trap
150
- scope = instance.create
151
- catch(:result) { run_source(scope, source, require, receiver, &block) }
191
+ valid = (receiver in ::Object) && receiver.respond_to?(:public_send) && receiver.respond_to?(:instance_exec)
192
+ ArgumentError.("Executor receiver must be an executable Object or nil") unless valid
193
+ end
152
194
 
153
- ensure
154
- instance.restore
155
- instance.shutdown(scope) if scope
156
- end
195
+ def validate_source!(string, file, line)
196
+ ArgumentError.("Executor source must be a String") unless string.is_a?(::String)
197
+ ArgumentError.("Executor file must be a String or nil") unless file.nil? || file.is_a?(::String)
157
198
 
158
- def self.run_source(scope, source, library, receiver, &block)
159
- ::Kernel.require(library) if library
160
- scope.call(source, receiver, &block)
161
- rescue ::LoadError => e
162
- scope.capture(source, e)
199
+ valid = line.nil? || (line.is_a?(::Integer) && line.between?(1, SOURCE_LINE_MAX))
200
+ ArgumentError.("Executor line must be between 1 and #{SOURCE_LINE_MAX}") unless valid
201
+ end
163
202
  end
164
-
165
- private_class_method :capture_error, :execute_source, :run_source
166
-
167
- private
168
-
169
- def scopes = Thread.current[SCOPE_KEY] ||= []
170
203
  end
204
+
171
205
  end
@@ -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 may 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,137 @@
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: {SVG.Canvas} creates a canvas,
7
+ # {SVG.Document} defines a document profile, and {SVG.Paper} registers a
8
+ # paper size. Double-colon names are Ruby constants and types, such as
9
+ # {SVG::Canvas} and {SVG::Document}. The global `SVG(...)` method builds a
10
+ # drawing; there is intentionally no stuttering `SVG.SVG(...)` form.
11
+ #
12
+ # Lowercase constructors remain on {Sevgi::Graphics} for focused component
13
+ # use. Script execution belongs to {Sevgi.execute} and is not part of this
14
+ # SVG-domain facade.
15
+ #
16
+ # @example Compose a drawing through the facade
17
+ # SVG.Paper 85, 55, :card
18
+ # canvas = SVG.Canvas :card, margins: 4
19
+ # profile = SVG.Document attributes: {viewBox: "0 0 85 55"}
20
+ #
21
+ # drawing = SVG profile, canvas do
22
+ # rect width: 85, height: 55, rx: 3
23
+ # end
24
+ #
25
+ # drawing.Render
26
+ # @example Distinguish an operation from its result type
27
+ # canvas = SVG.Canvas width: 24, height: 24, unit: :px
28
+ # canvas.is_a?(SVG::Canvas) #=> true
29
+ # @see https://sevgi.roktas.dev/getting-started/ Getting started
30
+ # @see https://sevgi.roktas.dev/library-mode/ Library mode guide
31
+ module SVG
32
+ # SVG attribute collection and normalization API.
33
+ Attributes = Graphics::Attributes
34
+ # Canvas value type returned by {SVG.Canvas}.
35
+ Canvas = Graphics::Canvas
36
+ # Explicit SVG content serialization types.
37
+ Content = Graphics::Content
38
+ # Document profiles and document classes.
39
+ Document = Graphics::Document
40
+ # Base SVG/XML element type.
41
+ Element = Graphics::Element
42
+ # Duplicate-id and structural lint failure.
43
+ LintError = Graphics::LintError
44
+ # CSS-like canvas margin value.
45
+ Margin = Graphics::Margin
46
+ # Graphics mixtures available for document composition.
47
+ Mixtures = Graphics::Mixtures
48
+ # Contract for one callable drawing module.
49
+ Module = Graphics::Module
50
+ # Recursive contract for namespaces of callable drawing modules.
51
+ Modules = Graphics::Modules
52
+ # Immutable paper-size value and profile registry.
53
+ Paper = Graphics::Paper
54
+ # Current Sevgi toolkit version.
55
+ VERSION = Sevgi::VERSION
56
+
57
+ # Builds a canvas from a paper profile or explicit dimensions.
58
+ # @return [Sevgi::Graphics::Canvas] canvas value
59
+ # @see Sevgi::Toplevel#Canvas
60
+ def self.Canvas(...) = Sevgi.Canvas(...)
61
+
62
+ # Converts inline SVG/XML into an immutable Derender node.
63
+ # @return [Sevgi::Derender::Node] selected node
64
+ # @see Sevgi::Toplevel#Decompile
65
+ def self.Decompile(...) = Sevgi.Decompile(...)
66
+
67
+ # Converts an SVG/XML file into an immutable Derender node.
68
+ # @return [Sevgi::Derender::Node] selected node
69
+ # @see Sevgi::Toplevel#DecompileFile
70
+ def self.DecompileFile(...) = Sevgi.DecompileFile(...)
71
+
72
+ # Converts inline SVG/XML into formatted Sevgi DSL source.
73
+ # @return [String] formatted Sevgi DSL source
74
+ # @see Sevgi::Toplevel#Derender
75
+ def self.Derender(...) = Sevgi.Derender(...)
76
+
77
+ # Converts an SVG/XML file into formatted Sevgi DSL source.
78
+ # @return [String] formatted Sevgi DSL source
79
+ # @see Sevgi::Toplevel#DerenderFile
80
+ def self.DerenderFile(...) = Sevgi.DerenderFile(...)
81
+
82
+ # Defines, validates, or looks up a document profile.
83
+ # @return [Class] document class
84
+ # @see Sevgi::Toplevel#Document
85
+ def self.Document(...) = Sevgi.Document(...)
86
+
87
+ # Defines or replaces a document profile.
88
+ # @return [Class] document class
89
+ # @see Sevgi::Toplevel#Document!
90
+ def self.Document!(...) = Sevgi.Document!(...)
91
+
92
+ # Includes an inline SVG/XML node under a graphics element.
93
+ # @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
94
+ # @see Sevgi::Toplevel#Evaluate
95
+ def self.Evaluate(...) = Sevgi.Evaluate(...)
96
+
97
+ # Includes only an inline SVG/XML node's children under a graphics element.
98
+ # @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
99
+ # @see Sevgi::Toplevel#EvaluateChildren
100
+ def self.EvaluateChildren(...) = Sevgi.EvaluateChildren(...)
101
+
102
+ # Includes only an SVG/XML file node's children under a graphics element.
103
+ # @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
104
+ # @see Sevgi::Toplevel#EvaluateChildrenFile
105
+ def self.EvaluateChildrenFile(...) = Sevgi.EvaluateChildrenFile(...)
106
+
107
+ # Includes an SVG/XML file node under a graphics element.
108
+ # @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
109
+ # @see Sevgi::Toplevel#EvaluateFile
110
+ def self.EvaluateFile(...) = Sevgi.EvaluateFile(...)
111
+
112
+ # Fits a drawable grid to a graphics canvas.
113
+ # @return [Sevgi::Sundries::Grid] fitted grid
114
+ # @see Sevgi::Toplevel#Grid
115
+ def self.Grid(...) = Sevgi.Grid(...)
116
+
117
+ # Loads nested `.sevgi` files through the active executor scope.
118
+ # @return [Array<String>] requested file names
119
+ # @see Sevgi::Toplevel#Load
120
+ def self.Load(...) = Sevgi.Load(...)
121
+
122
+ # Extends a document profile with a named or anonymous graphics mixture.
123
+ # @return [Module, nil] anonymous mixture when supplied, otherwise nil
124
+ # @see Sevgi::Toplevel#Mixin
125
+ def self.Mixin(...) = Sevgi.Mixin(...)
126
+
127
+ # Defines or validates a named paper profile.
128
+ # @return [Symbol, String] original paper profile name
129
+ # @see Sevgi::Toplevel#Paper
130
+ def self.Paper(...) = Sevgi.Paper(...)
131
+
132
+ # Defines or overwrites a named paper profile.
133
+ # @return [Symbol, String] original paper profile name
134
+ # @see Sevgi::Toplevel#Paper!
135
+ def self.Paper!(...) = Sevgi.Paper!(...)
136
+ end
137
+ end
@@ -4,20 +4,106 @@ 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 cannot be found or the id is absent
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 Decompile(file, id = nil) = Derender.decompile_file(file, id:)
28
+ def DecompileFile(file, id: nil, omit: nil) = Derender.decompile_file(file, id:, omit:)
29
+
30
+ # Converts inline SVG/XML content into Sevgi DSL Ruby source.
31
+ # @param content [String] SVG/XML source content
32
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
33
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
34
+ # subtree after id selection
35
+ # @return [String] formatted Sevgi DSL source
36
+ # @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
37
+ # @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
38
+ # @see Sevgi.Derender
39
+ # @see Sevgi::Derender.derender
40
+ def Derender(content, id: nil, omit: nil) = Derender.derender(content, id:, omit:)
14
41
 
15
42
  # Converts an SVG/XML file into Sevgi DSL Ruby source.
16
43
  # @param file [String] path to the source SVG/XML file
17
- # @param id [String, nil] optional SVG id selecting a node inside the source
44
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
45
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
46
+ # subtree after id selection
18
47
  # @return [String] formatted Sevgi DSL source
19
- # @raise [Sevgi::ArgumentError] when the file cannot be found or the id is absent
48
+ # @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
49
+ # @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
50
+ # @raise [SystemCallError] when the file cannot be read
51
+ # @see Sevgi.DerenderFile
20
52
  # @see Sevgi::Derender.derender_file
21
- def Derender(file, id = nil) = Derender.derender_file(file, id:)
53
+ def DerenderFile(file, id: nil, omit: nil) = Derender.derender_file(file, id:, omit:)
54
+
55
+ # Evaluates inline SVG/XML content under a graphics element, including the selected node.
56
+ # @param content [String] SVG/XML source content
57
+ # @param element [Sevgi::Graphics::Element] target graphics element
58
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
59
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
60
+ # subtree after id selection
61
+ # @return [Sevgi::Graphics::Element, nil] included selected/root element, or nil when it produces no output
62
+ # @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
63
+ # @see Sevgi.Evaluate
64
+ # @see Sevgi::Derender.evaluate
65
+ def Evaluate(content, element, id: nil, omit: nil) = Derender.evaluate(content, element, id:, omit:)
66
+
67
+ # Evaluates only the selected node's children from inline SVG/XML content under a graphics element.
68
+ # @param content [String] SVG/XML source content
69
+ # @param element [Sevgi::Graphics::Element] target graphics element
70
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
71
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
72
+ # subtree after id selection
73
+ # @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
74
+ # @raise [Sevgi::ArgumentError] when content is malformed or rootless, or the id is absent
75
+ # @see Sevgi.EvaluateChildren
76
+ # @see Sevgi::Derender.evaluate_children
77
+ def EvaluateChildren(content, element, id: nil, omit: nil)
78
+ Derender.evaluate_children(content, element, id:, omit:)
79
+ end
80
+
81
+ # Evaluates only the selected node's children from an SVG/XML file under a graphics element.
82
+ # @param file [String] path to the source SVG/XML file
83
+ # @param element [Sevgi::Graphics::Element] target graphics element
84
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
85
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
86
+ # subtree after id selection
87
+ # @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
88
+ # @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
89
+ # @raise [SystemCallError] when the file cannot be read
90
+ # @see Sevgi.EvaluateChildrenFile
91
+ # @see Sevgi::Derender.evaluate_children_file
92
+ def EvaluateChildrenFile(file, element, id: nil, omit: nil)
93
+ Derender.evaluate_children_file(file, element, id:, omit:)
94
+ end
95
+
96
+ # Evaluates an SVG/XML file under a graphics element, including the selected node.
97
+ # @param file [String] path to the source SVG/XML file
98
+ # @param element [Sevgi::Graphics::Element] target graphics element
99
+ # @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
100
+ # @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
101
+ # subtree after id selection
102
+ # @return [Sevgi::Graphics::Element, nil] included selected/root element, or nil when it produces no output
103
+ # @raise [Sevgi::ArgumentError] when the file is absent, malformed, or rootless, or the id is absent
104
+ # @raise [SystemCallError] when the file cannot be read
105
+ # @see Sevgi.EvaluateFile
106
+ # @see Sevgi::Derender.evaluate_file
107
+ def EvaluateFile(file, element, id: nil, omit: nil) = Derender.evaluate_file(file, element, id:, omit:)
22
108
  end
23
109
  end