sevgi 0.95.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -0
- 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 +3 -3
- data/lib/sevgi/binaries/sevgi.rb +69 -27
- data/lib/sevgi/executor/error.rb +16 -24
- data/lib/sevgi/executor/result.rb +54 -0
- data/lib/sevgi/executor/scope.rb +16 -5
- data/lib/sevgi/executor/source.rb +16 -7
- data/lib/sevgi/executor.rb +129 -99
- data/lib/sevgi/skill.rb +42 -0
- data/lib/sevgi/svg.rb +137 -0
- data/lib/sevgi/toplevel/derender.rb +92 -6
- data/lib/sevgi/toplevel/executor.rb +55 -13
- data/lib/sevgi/toplevel/graphics.rb +92 -6
- data/lib/sevgi/toplevel/sundries.rb +13 -5
- data/lib/sevgi/toplevel.rb +7 -7
- data/lib/sevgi/version.rb +1 -1
- data/lib/sevgi.rb +100 -13
- metadata +36 -17
|
@@ -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
|
|
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:)
|
|
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
|
|
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
|
|
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
|
|
@@ -8,22 +8,61 @@ module Sevgi
|
|
|
8
8
|
private_constant :BootBlock
|
|
9
9
|
|
|
10
10
|
# Executes Sevgi script source with the full top-level DSL installed.
|
|
11
|
-
#
|
|
12
|
-
# @
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
#
|
|
12
|
+
# @example Execute source in an isolated scope
|
|
13
|
+
# result = Sevgi.execute("F.pluralize('cat')")
|
|
14
|
+
# result.value # => "cats"
|
|
15
|
+
# @param string [String] source to evaluate
|
|
16
|
+
# @param file [String, nil] source file name used for errors and backtraces
|
|
17
|
+
# @param line [Integer, nil] starting source line used for errors and backtraces
|
|
18
|
+
# @param require [String, nil] optional Ruby library to require before execution
|
|
19
|
+
# @param main [Boolean] whether to install the DSL through Ruby's top-level main object
|
|
20
|
+
# @return [Sevgi::Executor::Result] immutable execution result
|
|
21
|
+
# @raise [Sevgi::ArgumentError] when source, file, line, required library, or main mode is invalid
|
|
22
|
+
# @note Script and required-library failures are captured in {Sevgi::Executor::Result#error}.
|
|
23
|
+
# @note The default isolated mode does not modify Ruby's top-level main object. `main: true` preserves the command-line
|
|
24
|
+
# default by installing Sevgi through main before evaluating source in the managed script scope.
|
|
25
|
+
# @note Empty source without `require:` is a strict no-op; the DSL boot block is unused.
|
|
15
26
|
# @note Reentrant and concurrent calls keep independent executor scope stacks per fiber.
|
|
16
|
-
# @see
|
|
17
|
-
def self.execute(
|
|
27
|
+
# @see https://sevgi.roktas.dev/execution/ Execution guide
|
|
28
|
+
def self.execute(string, file: nil, line: nil, require: nil, main: false)
|
|
29
|
+
Executor.__send__(:execute, string, file:, line:, require:, receiver: execution_receiver(main), &BootBlock)
|
|
30
|
+
end
|
|
18
31
|
|
|
19
32
|
# Executes a Sevgi script file with the full top-level DSL installed.
|
|
20
|
-
# @param
|
|
21
|
-
# @param
|
|
22
|
-
#
|
|
23
|
-
# @
|
|
33
|
+
# @param file [String] source file to read and execute
|
|
34
|
+
# @param as [String, nil] source basename used for evaluation, diagnostics, and caller-derived output defaults;
|
|
35
|
+
# its extension is replaced with `.sevgi` and the input file's directory is retained
|
|
36
|
+
# @param require [String, nil] optional Ruby library to require before execution
|
|
37
|
+
# @param main [Boolean] whether to install the DSL through Ruby's top-level main object
|
|
38
|
+
# @return [Sevgi::Executor::Result] immutable execution result
|
|
39
|
+
# @raise [Sevgi::ArgumentError] when file, logical source name, required library, or main mode is invalid
|
|
40
|
+
# @note File-read, script, and required-library failures are captured in {Sevgi::Executor::Result#error}.
|
|
41
|
+
# @note The default isolated mode does not modify Ruby's top-level main object. `main: true` preserves the command-line
|
|
42
|
+
# default by installing Sevgi through main before evaluating source in the managed script scope.
|
|
43
|
+
# @note An empty file without `require:` is a strict no-op; the DSL boot block is unused.
|
|
24
44
|
# @note Reentrant and concurrent calls keep independent executor scope stacks per fiber.
|
|
25
|
-
# @see
|
|
26
|
-
def self.execute_file(
|
|
45
|
+
# @see https://sevgi.roktas.dev/execution/ Execution guide
|
|
46
|
+
def self.execute_file(file, as: nil, require: nil, main: false)
|
|
47
|
+
as = source_name(file, as) if as
|
|
48
|
+
Executor.__send__(:execute_file, file, as:, require:, receiver: execution_receiver(main), &BootBlock)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.execution_receiver(main)
|
|
52
|
+
ArgumentError.("Sevgi main mode must be true or false") unless [true, false].include?(main)
|
|
53
|
+
|
|
54
|
+
TOPLEVEL_BINDING.receiver if main
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.source_name(file, name)
|
|
58
|
+
ArgumentError.("Sevgi file must be a String") unless file.is_a?(::String)
|
|
59
|
+
ArgumentError.("Sevgi file name must be a non-empty String") unless name.is_a?(::String) && !name.empty?
|
|
60
|
+
ArgumentError.("Sevgi file name must not include a directory") unless ::File.basename(name) == name
|
|
61
|
+
|
|
62
|
+
::File.join(::File.dirname(file), F.subext(".sevgi", name))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private_class_method :execution_receiver, :source_name
|
|
27
66
|
|
|
28
67
|
module Toplevel
|
|
29
68
|
# Loads one or more Sevgi files relative to the caller's source file.
|
|
@@ -32,13 +71,16 @@ module Sevgi
|
|
|
32
71
|
# @raise [Sevgi::PanicError] when called without an active executor scope
|
|
33
72
|
# @raise [Sevgi::Error] when a file cannot be located
|
|
34
73
|
# @note `Load` resolves against the active executor scope in the current fiber.
|
|
74
|
+
# @note Ordinary library code should use Ruby `require`; `Load` is available only during Sevgi script execution.
|
|
75
|
+
# @see Sevgi.Load
|
|
76
|
+
# @see Sevgi.execute_file
|
|
35
77
|
def Load(*files)
|
|
36
78
|
start = ::File.dirname(caller_locations(1..1).first.path)
|
|
37
79
|
|
|
38
80
|
files.each do |file|
|
|
39
81
|
location = F.locate(file, start)
|
|
40
82
|
|
|
41
|
-
::Sevgi::Executor.load
|
|
83
|
+
::Sevgi::Executor.__send__(:load, location.file)
|
|
42
84
|
end
|
|
43
85
|
end
|
|
44
86
|
end
|
|
@@ -4,14 +4,97 @@ require "sevgi/graphics"
|
|
|
4
4
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Toplevel
|
|
7
|
+
# @overload Canvas(paper, **overrides)
|
|
8
|
+
# Builds a canvas from a paper profile with optional field overrides.
|
|
9
|
+
# @param paper [Sevgi::Graphics::Paper, Symbol, String] paper object or registered profile
|
|
10
|
+
# @param overrides [Hash] canvas field overrides
|
|
11
|
+
# @return [Sevgi::Graphics::Canvas]
|
|
12
|
+
# @raise [Sevgi::ArgumentError] when the paper or an override is invalid
|
|
13
|
+
# @overload Canvas(width:, height:, unit: "mm", name: :custom, margins: [])
|
|
14
|
+
# Builds a canvas from an explicit size.
|
|
15
|
+
# @param width [Numeric] canvas width
|
|
16
|
+
# @param height [Numeric] canvas height
|
|
17
|
+
# @param unit [Symbol, String] SVG unit
|
|
18
|
+
# @param name [Symbol, String] paper name
|
|
19
|
+
# @param margins [Array<Numeric>] margin shorthand values
|
|
20
|
+
# @return [Sevgi::Graphics::Canvas]
|
|
21
|
+
# @raise [Sevgi::ArgumentError] when a required field is omitted or a value is invalid
|
|
22
|
+
# @example Build a paper-backed canvas
|
|
23
|
+
# canvas = Sevgi.Canvas :a4, margins: [12, 10]
|
|
24
|
+
# @see Sevgi.Canvas
|
|
25
|
+
# @see Sevgi::Graphics.canvas
|
|
26
|
+
def Canvas(...) = Graphics.canvas(...)
|
|
27
|
+
|
|
28
|
+
# @overload Document(name)
|
|
29
|
+
# Looks up an existing document profile by name.
|
|
30
|
+
# @param name [Symbol, String] profile name
|
|
31
|
+
# @return [Class] document class
|
|
32
|
+
# @raise [Sevgi::ArgumentError] when the profile is unknown
|
|
33
|
+
# @overload Document(name, preambles: Undefined, attributes: Undefined)
|
|
34
|
+
# Defines or validates a named document profile.
|
|
35
|
+
# @param name [Symbol, String] profile name
|
|
36
|
+
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
37
|
+
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes
|
|
38
|
+
# @return [Class] document class
|
|
39
|
+
# @raise [Sevgi::ArgumentError] when a profile conflicts or metadata is invalid
|
|
40
|
+
# @overload Document(preambles: Undefined, attributes: Undefined)
|
|
41
|
+
# Defines an anonymous document profile without registering it globally.
|
|
42
|
+
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
43
|
+
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes
|
|
44
|
+
# @return [Class] anonymous document class
|
|
45
|
+
# @raise [Sevgi::ArgumentError] when metadata is invalid
|
|
46
|
+
# @return [Class] document class
|
|
47
|
+
# @example Define and use a document profile
|
|
48
|
+
# Sevgi.Document :icon, attributes: {viewBox: "0 0 24 24"}
|
|
49
|
+
# drawing = Sevgi.SVG(:icon) { circle cx: 12, cy: 12, r: 10 }
|
|
50
|
+
# @see Sevgi.Document
|
|
51
|
+
# @see Sevgi::Graphics.document
|
|
52
|
+
def Document(...) = Graphics.document(...)
|
|
53
|
+
|
|
54
|
+
# Defines or replaces a document profile.
|
|
55
|
+
# @param name [Symbol, String] profile name
|
|
56
|
+
# @param preambles [Array<String>, nil] document preamble lines
|
|
57
|
+
# @param attributes [Hash, nil] default root attributes
|
|
58
|
+
# @return [Class] document class
|
|
59
|
+
# @raise [Sevgi::ArgumentError] when the name or metadata is invalid
|
|
60
|
+
# @see Sevgi.Document!
|
|
61
|
+
# @see Sevgi::Graphics.document!
|
|
62
|
+
def Document!(name, preambles: [], attributes: {})
|
|
63
|
+
Graphics.document!(name, preambles:, attributes:)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Builds an SVG document through the full Sevgi top-level DSL.
|
|
67
|
+
# @param document [Symbol, String, Class] document profile name or document class
|
|
68
|
+
# @param canvas [Sevgi::Graphics::Canvas, Sevgi::Graphics::Paper, Symbol, String, Sevgi::Undefined, nil] optional
|
|
69
|
+
# canvas or paper profile
|
|
70
|
+
# @param attributes [Hash] root SVG attributes
|
|
71
|
+
# @yield the document block evaluated in the SVG document context
|
|
72
|
+
# @yieldreturn [void]
|
|
73
|
+
# @return [Sevgi::Graphics::Document::Proto] SVG document object
|
|
74
|
+
# @raise [Sevgi::ArgumentError] when the document, paper, or canvas arguments are invalid
|
|
75
|
+
# @see Sevgi.SVG
|
|
76
|
+
# @see Sevgi::Graphics.SVG
|
|
77
|
+
def SVG(document = :default, canvas = Undefined, **attributes, &block)
|
|
78
|
+
Graphics.SVG(document, canvas, **attributes, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @overload Mixin(mod, document = Sevgi::Graphics::Document::Base)
|
|
82
|
+
# Adds a named graphics mixture to a document class.
|
|
83
|
+
# @param mod [Symbol, String] named mixture to mix into the document
|
|
84
|
+
# @param document [Class] document class receiving the mixture
|
|
85
|
+
# @return [nil]
|
|
86
|
+
# @raise [NameError] when a named mixture cannot be resolved
|
|
87
|
+
# @see Sevgi.Mixin
|
|
88
|
+
# @see Sevgi::Graphics::Mixtures.mixin
|
|
7
89
|
# @overload Mixin(mod, document = Sevgi::Graphics::Document::Base, &block)
|
|
8
|
-
# Adds graphics mixture
|
|
90
|
+
# Adds a named graphics mixture and an anonymous extension to a document class.
|
|
9
91
|
# @param mod [Symbol, String] named mixture to mix into the document
|
|
10
92
|
# @param document [Class] document class receiving the mixture
|
|
11
93
|
# @yield optional anonymous mixture module body
|
|
12
94
|
# @yieldreturn [void]
|
|
13
|
-
# @return [
|
|
95
|
+
# @return [Module] anonymous mixture
|
|
14
96
|
# @raise [NameError] when a named mixture cannot be resolved
|
|
97
|
+
# @see Sevgi.Mixin
|
|
15
98
|
# @see Sevgi::Graphics::Mixtures.mixin
|
|
16
99
|
# @overload Mixin(document = Sevgi::Graphics::Document::Base, &block)
|
|
17
100
|
# Adds an anonymous graphics mixture to a document class.
|
|
@@ -20,6 +103,7 @@ module Sevgi
|
|
|
20
103
|
# @yieldreturn [void]
|
|
21
104
|
# @return [Module] anonymous mixture
|
|
22
105
|
# @raise [Sevgi::ArgumentError] when no named mixture or block is given
|
|
106
|
+
# @see Sevgi.Mixin
|
|
23
107
|
# @see Sevgi::Graphics::Mixtures.mixin
|
|
24
108
|
def Mixin(...) = Graphics::Mixtures.mixin(...)
|
|
25
109
|
|
|
@@ -27,10 +111,11 @@ module Sevgi
|
|
|
27
111
|
# Defines or validates a named paper profile for DSL use.
|
|
28
112
|
# @param width [Numeric] paper width
|
|
29
113
|
# @param height [Numeric] paper height
|
|
30
|
-
# @param name [Symbol] paper profile name
|
|
114
|
+
# @param name [Symbol, String] paper profile name
|
|
31
115
|
# @param unit [String, Symbol] size unit
|
|
32
|
-
# @return [Symbol] the paper profile name
|
|
116
|
+
# @return [Symbol, String] the original paper profile name
|
|
33
117
|
# @raise [Sevgi::ArgumentError] when the profile is invalid or an existing profile differs
|
|
118
|
+
# @see Sevgi.Paper
|
|
34
119
|
# @see Sevgi::Graphics#paper
|
|
35
120
|
def Paper(...) = Graphics.paper(...)
|
|
36
121
|
|
|
@@ -38,10 +123,11 @@ module Sevgi
|
|
|
38
123
|
# Defines or overwrites a named paper profile for DSL use.
|
|
39
124
|
# @param width [Numeric] paper width
|
|
40
125
|
# @param height [Numeric] paper height
|
|
41
|
-
# @param name [Symbol] paper profile name
|
|
126
|
+
# @param name [Symbol, String] paper profile name
|
|
42
127
|
# @param unit [String, Symbol] size unit
|
|
43
|
-
# @return [Symbol] the paper profile name
|
|
128
|
+
# @return [Symbol, String] the original paper profile name
|
|
44
129
|
# @raise [Sevgi::ArgumentError] when the profile is invalid or the paper name is reserved
|
|
130
|
+
# @see Sevgi.Paper!
|
|
45
131
|
# @see Sevgi::Graphics#paper!
|
|
46
132
|
def Paper!(...) = Graphics.paper!(...)
|
|
47
133
|
end
|
|
@@ -4,7 +4,12 @@ require "sevgi/sundries"
|
|
|
4
4
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Toplevel
|
|
7
|
-
# Builds a drawable grid
|
|
7
|
+
# Builds a drawable grid fitted inside a graphics canvas.
|
|
8
|
+
#
|
|
9
|
+
# The canvas margins are minimum clearances. Any span left after fitting
|
|
10
|
+
# whole major intervals is shared equally between the opposite margins, so
|
|
11
|
+
# their requested difference is preserved. The returned grid starts at
|
|
12
|
+
# `(0, 0)`; {Sevgi::Sundries::Grid#canvas} exposes the fitted page margins.
|
|
8
13
|
# @param canvas [Sevgi::Graphics::Canvas] canvas defining page size and margins
|
|
9
14
|
# @param unit [Numeric] minor grid unit
|
|
10
15
|
# @param multiple [Integer] number of minor units in each major interval
|
|
@@ -13,13 +18,16 @@ module Sevgi
|
|
|
13
18
|
# @raise [Sevgi::ArgumentError] when unit is not a finite positive number
|
|
14
19
|
# @raise [Sevgi::ArgumentError] when multiple is not a positive integer
|
|
15
20
|
# @raise [Sevgi::ArgumentError] when canvas dimensions, margins, and grid intervals cannot fit
|
|
21
|
+
# @see Sevgi.Grid
|
|
22
|
+
# @see Sevgi::Sundries::Grid
|
|
16
23
|
def Grid(canvas, unit:, multiple:)
|
|
17
24
|
ArgumentError.("Must be a Canvas: #{canvas}") unless canvas.is_a?(Graphics::Canvas)
|
|
18
25
|
|
|
19
|
-
Sundries::Grid
|
|
20
|
-
Sundries::Ruler.new(brut: canvas.width, unit:, multiple:,
|
|
21
|
-
Sundries::Ruler.new(brut: canvas.height, unit:, multiple:,
|
|
22
|
-
|
|
26
|
+
Sundries::Grid.new(
|
|
27
|
+
x: Sundries::Ruler.new(brut: canvas.width, unit:, multiple:, margins: [canvas.left, canvas.right]),
|
|
28
|
+
y: Sundries::Ruler.new(brut: canvas.height, unit:, multiple:, margins: [canvas.top, canvas.bottom]),
|
|
29
|
+
canvas:
|
|
30
|
+
)
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
promote Sundries::Export
|
data/lib/sevgi/toplevel.rb
CHANGED
|
@@ -7,21 +7,19 @@ module Sevgi
|
|
|
7
7
|
# normally be included directly.
|
|
8
8
|
#
|
|
9
9
|
# @see Sevgi
|
|
10
|
+
# @see https://sevgi.roktas.dev/library-mode/ Library mode guide
|
|
11
|
+
# @see https://sevgi.roktas.dev/script-mode/ Script mode guide
|
|
10
12
|
module Toplevel
|
|
11
|
-
@
|
|
13
|
+
@promotions = {}
|
|
12
14
|
|
|
13
15
|
class << self
|
|
14
|
-
# Returns an immutable snapshot of promoted constants.
|
|
15
|
-
# @return [Hash<Symbol, Object>]
|
|
16
|
-
def constants = @constants.dup.freeze
|
|
17
|
-
|
|
18
16
|
private
|
|
19
17
|
|
|
20
18
|
def inject(base)
|
|
21
19
|
return if base.instance_variable_defined?("@_toplevel_injected_")
|
|
22
20
|
|
|
23
21
|
if base.is_a?(::Module)
|
|
24
|
-
@
|
|
22
|
+
@promotions.each do |name, constant|
|
|
25
23
|
base.const_set(name, constant) unless base.const_defined?(name, false)
|
|
26
24
|
end
|
|
27
25
|
end
|
|
@@ -30,7 +28,7 @@ module Sevgi
|
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
def promote(constant, symbol = Undefined)
|
|
33
|
-
@
|
|
31
|
+
@promotions[Undefined.default(symbol, constant.to_s.split("::").last.to_sym)] = constant
|
|
34
32
|
end
|
|
35
33
|
end
|
|
36
34
|
|
|
@@ -53,6 +51,8 @@ module Sevgi
|
|
|
53
51
|
super
|
|
54
52
|
inject(base)
|
|
55
53
|
end
|
|
54
|
+
|
|
55
|
+
private_class_method :extended, :included
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
extend Toplevel
|
data/lib/sevgi/version.rb
CHANGED
data/lib/sevgi.rb
CHANGED
|
@@ -9,36 +9,121 @@ require "sevgi/sundries"
|
|
|
9
9
|
|
|
10
10
|
require "sevgi/version"
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
require_relative "sevgi/toplevel"
|
|
13
|
+
require_relative "sevgi/svg"
|
|
14
|
+
|
|
15
|
+
# SVG-domain facade installed by `require "sevgi"`.
|
|
16
|
+
# @return [Module] the public SVG operations and types
|
|
17
|
+
SVG = Sevgi::SVG
|
|
15
18
|
|
|
16
19
|
# @overload SVG(document = :default, canvas = Undefined, **attributes, &block)
|
|
17
20
|
# Builds an SVG document through the default top-level DSL entrypoint.
|
|
18
|
-
# @param document [Symbol, Class] document profile name or document class
|
|
19
|
-
# @param canvas [
|
|
21
|
+
# @param document [Symbol, String, Class] document profile name or document class
|
|
22
|
+
# @param canvas [Sevgi::Graphics::Canvas, Sevgi::Graphics::Paper, Symbol, String, Sevgi::Undefined, nil] optional
|
|
23
|
+
# canvas or paper profile
|
|
20
24
|
# @param attributes [Hash] root SVG attributes
|
|
21
25
|
# @yield the document block evaluated in the SVG document context
|
|
22
26
|
# @yieldreturn [void]
|
|
23
27
|
# @return [Sevgi::Graphics::Document::Proto] a rendered SVG document object
|
|
24
28
|
# @raise [Sevgi::ArgumentError] when the document, paper, or canvas arguments are invalid
|
|
25
|
-
|
|
29
|
+
# @example Build through the global library entrypoint
|
|
30
|
+
# SVG(:minimal) { circle r: 4 }.Render
|
|
31
|
+
# @see Sevgi.SVG
|
|
32
|
+
# @see Sevgi::Toplevel#SVG
|
|
33
|
+
def SVG(...) = Sevgi.SVG(...)
|
|
26
34
|
|
|
27
35
|
# Full top-level API for Sevgi library and script consumers.
|
|
28
36
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
37
|
+
# `require "sevgi"` installs the global document builder `SVG(...)` and the independent {Sevgi::SVG} facade. SVG-domain
|
|
38
|
+
# operations use capitalized method names on that facade, such as `SVG.Canvas`, `SVG.Paper`, and `SVG.Derender`; types
|
|
39
|
+
# and namespaces use Ruby's double-colon notation, such as `SVG::Canvas`. The same operations are available on `Sevgi`
|
|
40
|
+
# for consumers that prefer the full toolkit namespace. Process-level operations such as {Sevgi.execute} remain on
|
|
41
|
+
# `Sevgi` rather than the SVG facade.
|
|
42
|
+
#
|
|
43
|
+
# Including this module in a class or module installs the DSL methods plus convenience constants `F`, `Geometry`,
|
|
44
|
+
# `Origin`, and `Export`; script execution provides the same promoted scope. Extending a module does the same. Extending
|
|
45
|
+
# an ordinary object installs methods only and does not write promoted constants to `Object`. Focused component requires
|
|
46
|
+
# expose their namespaced component APIs instead of this full top-level surface.
|
|
47
|
+
#
|
|
48
|
+
# `Load` is meaningful only during {Sevgi.execute}, {Sevgi.execute_file}, or command-line script execution. It resolves
|
|
49
|
+
# nested `.sevgi` files through the active executor scope; it is not a general-purpose replacement for Ruby `require`.
|
|
34
50
|
#
|
|
35
51
|
# @example Include the DSL in an object
|
|
36
52
|
# class Drawing
|
|
37
53
|
# include Sevgi
|
|
38
54
|
# end
|
|
55
|
+
# @see https://sevgi.roktas.dev/library-mode/ Library mode guide
|
|
56
|
+
# @see https://sevgi.roktas.dev/script-mode/ Script mode guide
|
|
57
|
+
# @see https://sevgi.roktas.dev/execution/ Embedded execution guide
|
|
39
58
|
module Sevgi
|
|
40
|
-
#
|
|
41
|
-
|
|
59
|
+
# @!method self.Canvas(...)
|
|
60
|
+
# Builds a canvas from a paper profile or explicit dimensions.
|
|
61
|
+
# @return [Sevgi::Graphics::Canvas] canvas value
|
|
62
|
+
# @see Sevgi::Toplevel#Canvas
|
|
63
|
+
# @!method self.Document(...)
|
|
64
|
+
# Defines, validates, or looks up a document profile.
|
|
65
|
+
# @return [Class] document class
|
|
66
|
+
# @see Sevgi::Toplevel#Document
|
|
67
|
+
# @!method self.Document!(...)
|
|
68
|
+
# Defines or replaces a document profile.
|
|
69
|
+
# @return [Class] document class
|
|
70
|
+
# @see Sevgi::Toplevel#Document!
|
|
71
|
+
# @!method self.SVG(...)
|
|
72
|
+
# Builds an SVG document through the explicit namespaced top-level API.
|
|
73
|
+
# @return [Sevgi::Graphics::Document::Proto] SVG document object
|
|
74
|
+
# @see Sevgi::Toplevel#SVG
|
|
75
|
+
# @!method self.Mixin(...)
|
|
76
|
+
# Extends a document profile with a named or anonymous graphics mixture.
|
|
77
|
+
# @return [Module, nil] anonymous mixture when supplied, otherwise nil
|
|
78
|
+
# @see Sevgi::Toplevel#Mixin
|
|
79
|
+
# @!method self.Paper(...)
|
|
80
|
+
# Defines or validates a named paper profile.
|
|
81
|
+
# @return [Symbol, String] original paper profile name
|
|
82
|
+
# @see Sevgi::Toplevel#Paper
|
|
83
|
+
# @!method self.Paper!(...)
|
|
84
|
+
# Defines or overwrites a named paper profile.
|
|
85
|
+
# @return [Symbol, String] original paper profile name
|
|
86
|
+
# @see Sevgi::Toplevel#Paper!
|
|
87
|
+
# @!method self.Grid(...)
|
|
88
|
+
# Fits a drawable grid to a graphics canvas.
|
|
89
|
+
# @return [Sevgi::Sundries::Grid] fitted grid
|
|
90
|
+
# @see Sevgi::Toplevel#Grid
|
|
91
|
+
# @!method self.Decompile(...)
|
|
92
|
+
# Converts inline SVG/XML into an immutable Derender node.
|
|
93
|
+
# @return [Sevgi::Derender::Node] selected node
|
|
94
|
+
# @see Sevgi::Toplevel#Decompile
|
|
95
|
+
# @!method self.DecompileFile(...)
|
|
96
|
+
# Converts an SVG/XML file into an immutable Derender node.
|
|
97
|
+
# @return [Sevgi::Derender::Node] selected node
|
|
98
|
+
# @see Sevgi::Toplevel#DecompileFile
|
|
99
|
+
# @!method self.Derender(...)
|
|
100
|
+
# Converts inline SVG/XML into formatted Sevgi DSL source.
|
|
101
|
+
# @return [String] formatted Sevgi DSL source
|
|
102
|
+
# @see Sevgi::Toplevel#Derender
|
|
103
|
+
# @!method self.DerenderFile(...)
|
|
104
|
+
# Converts an SVG/XML file into formatted Sevgi DSL source.
|
|
105
|
+
# @return [String] formatted Sevgi DSL source
|
|
106
|
+
# @see Sevgi::Toplevel#DerenderFile
|
|
107
|
+
# @!method self.Evaluate(...)
|
|
108
|
+
# Includes an inline SVG/XML node under a graphics element.
|
|
109
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
|
|
110
|
+
# @see Sevgi::Toplevel#Evaluate
|
|
111
|
+
# @!method self.EvaluateFile(...)
|
|
112
|
+
# Includes an SVG/XML file node under a graphics element.
|
|
113
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when no output is produced
|
|
114
|
+
# @see Sevgi::Toplevel#EvaluateFile
|
|
115
|
+
# @!method self.EvaluateChildren(...)
|
|
116
|
+
# Includes only an inline SVG/XML node's children under a graphics element.
|
|
117
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
118
|
+
# @see Sevgi::Toplevel#EvaluateChildren
|
|
119
|
+
# @!method self.EvaluateChildrenFile(...)
|
|
120
|
+
# Includes only an SVG/XML file node's children under a graphics element.
|
|
121
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
122
|
+
# @see Sevgi::Toplevel#EvaluateChildrenFile
|
|
123
|
+
# @!method self.Load(...)
|
|
124
|
+
# Loads nested `.sevgi` files through the active executor scope.
|
|
125
|
+
# @return [Array<String>] requested file names
|
|
126
|
+
# @see Sevgi::Toplevel#Load
|
|
42
127
|
|
|
43
128
|
# Installs the full toplevel DSL into an including class or module.
|
|
44
129
|
# @param base [Module] the class or module receiving the DSL methods
|
|
@@ -58,4 +143,6 @@ module Sevgi
|
|
|
58
143
|
super
|
|
59
144
|
base.extend(Toplevel)
|
|
60
145
|
end
|
|
146
|
+
|
|
147
|
+
private_class_method :extended, :included
|
|
61
148
|
end
|