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.
@@ -1,175 +1,175 @@
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
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 process SIGINT handler is shared by Ruby, so executor runs guard it
17
- # with a reference-counted critical section and restore the previous handler
18
- # after the last active execution finishes.
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
- include Singleton
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
- 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 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.("box stack empty; create a box first") unless instance.current
64
+ PanicError.("No active executor scope") unless STATE.current
37
65
 
38
- instance.current.load(file, ...)
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::Scope, nil] execution scope, or nil for empty source
50
- # @note Required-library load failures are captured as {Sevgi::Executor::Error} on the returned scope.
51
- # @note Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains
52
- # process-global while any execution is active.
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::Scope, nil] execution scope, or nil for an empty file
64
- # @note File-read and required-library load failures are captured as {Sevgi::Executor::Error} on the returned scope.
65
- # @note Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains
66
- # process-global while any execution is active.
67
- def self.execute_file(file, require: nil, receiver: nil, &block)
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:, line: 1), e)
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
- # Removes the current executor scope.
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
- def initialize
86
- @signal_count = 0
87
- @signal_mutex = Mutex.new
88
- @signal_previous = nil
89
- end
128
+ class << self
129
+ private
90
130
 
91
- # Creates and pushes a new executor scope.
92
- # @param scope [Module, nil] existing module scope to reuse
93
- # @return [Sevgi::Executor::Scope] created scope
94
- # @api private
95
- def create(scope = nil) = Scope.new(scope).tap { scopes << it }
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
- # Removes the active executor scope.
118
- # @param scope [Sevgi::Executor::Scope, nil] exact scope to remove, or nil to pop the current scope
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
- # Installs the process SIGINT handler while one or more executions are active.
129
- # @return [void]
130
- # @api private
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
- @signal_count += 1
145
+ ensure
146
+ STATE.shutdown(scope) if scope
136
147
  end
137
- end
138
148
 
139
- def self.capture_error(source, error)
140
- acquired = instance.trap
141
- scope = instance.create
142
- scope.capture(source, error)
143
- ensure
144
- instance.restore if acquired
145
- instance.shutdown(scope) if scope
146
- end
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
- def self.execute_source(source, require:, receiver:, &block)
149
- acquired = false
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
- acquired = instance.trap
153
- scope = instance.create
154
- catch(:result) { run_source(scope, source, require, receiver, &block) }
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
- ensure
157
- instance.restore if acquired
158
- instance.shutdown(scope) if scope
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
- def self.run_source(scope, source, library, receiver, &block)
162
- ::Kernel.require(library) if library
163
- scope.call(source, receiver, &block)
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
@@ -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 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:)
14
29
 
15
- # Converts an SVG/XML file into Sevgi DSL Ruby source.
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 cannot be found or the id is absent
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 Derender(file, id = nil) = Derender.derender_file(file, id:)
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