sevgi-derender 0.93.1 → 0.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/LICENSE +5 -0
- data/README.md +40 -1
- data/lib/sevgi/derender/attributes.rb +2 -1
- data/lib/sevgi/derender/document.rb +36 -21
- data/lib/sevgi/derender/evaluator.rb +52 -0
- data/lib/sevgi/derender/internal.rb +5 -1
- data/lib/sevgi/derender/node.rb +42 -39
- data/lib/sevgi/derender/version.rb +1 -1
- data/lib/sevgi/derender.rb +93 -24
- metadata +9 -7
- data/lib/sevgi/binaries/igsev.rb +0 -114
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f1b7265f14be33da5b43f634ac82059a6d438e723ecc2153a062452ea1610fc
|
|
4
|
+
data.tar.gz: 5c52f4aef783cd3b3c712f19883ebbcab9c28efce3802318fe1b1f55b2a7fd88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98d494547c8854554dc9f791c4abd6ef1e99eb726face62c03b49756a0694be0267d311b103ac35a3797ad52dcc60cacf770c270569babcf5c4a6e9ee1bb0930
|
|
7
|
+
data.tar.gz: 3b127ab458de366d81b7efc0fb8b4ba94d6ce26cf2c9761389e4375aea02a079aaaa0c8447841416e45857674d9daee4494ffb1c28e2bca30b8944ba3addeeb3
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
data/README.md
CHANGED
|
@@ -2,4 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
Converts SVG/XML content back into Sevgi DSL source.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
gem install sevgi-derender
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Require
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "sevgi/derender"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
source = "<svg><rect width=\"3\" height=\"5\"/></svg>"
|
|
21
|
+
Sevgi::Derender.derender(source)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Executable
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
igves drawing.svg
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Ruby compatibility
|
|
31
|
+
|
|
32
|
+
Requires Ruby 3.4.0 or newer. CI verifies Ruby 3.4 and the current development Ruby from `.ruby-version`.
|
|
33
|
+
|
|
34
|
+
## Native prerequisites
|
|
35
|
+
|
|
36
|
+
None beyond Ruby and this gem's Ruby dependencies. Nokogiri may use platform packages depending on the target Ruby
|
|
37
|
+
platform.
|
|
38
|
+
|
|
39
|
+
## Links
|
|
40
|
+
|
|
41
|
+
- Documentation: https://sevgi.roktas.dev
|
|
42
|
+
- API documentation: https://www.rubydoc.info/gems/sevgi-derender
|
|
43
|
+
- Source: https://github.com/roktas/sevgi/tree/main/derender
|
|
44
|
+
- Changelog: https://github.com/roktas/sevgi/blob/main/CHANGELOG.md
|
|
@@ -38,7 +38,8 @@ module Sevgi
|
|
|
38
38
|
key = Css.to_key(key) if key.is_a?(::String)
|
|
39
39
|
|
|
40
40
|
if key == "style"
|
|
41
|
-
|
|
41
|
+
style = Css.to_h!(value)
|
|
42
|
+
style.empty? ? "{}" : "{ #{Attributes.decompile(style)} }"
|
|
42
43
|
elsif value.is_a?(::String)
|
|
43
44
|
Css.to_value(value)
|
|
44
45
|
elsif value.is_a?(::Hash)
|
|
@@ -6,17 +6,13 @@ module Sevgi
|
|
|
6
6
|
module Derender
|
|
7
7
|
# Parsed SVG/XML document wrapper used by the derender pipeline.
|
|
8
8
|
class Document
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
attr_reader :cache
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Loads and parses an SVG/XML file, using the parse cache when possible.
|
|
9
|
+
# Loads and parses an SVG/XML file.
|
|
10
|
+
#
|
|
11
|
+
# Each call reads the current file content and returns an isolated parsed document. Caller mutation of one loaded
|
|
12
|
+
# document does not affect later loads of the same path.
|
|
17
13
|
# @param path [String] path to the source file, with or without `.svg` extension
|
|
18
14
|
# @return [Sevgi::Derender::Document] document wrapper
|
|
19
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found
|
|
15
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found or file content is malformed XML
|
|
20
16
|
# @raise [Errno::EACCES] when the file cannot be read
|
|
21
17
|
def self.load_file(path)
|
|
22
18
|
entry = ::File.expand_path(F.qualify(path, "svg"))
|
|
@@ -24,26 +20,30 @@ module Sevgi
|
|
|
24
20
|
ArgumentError.("File not found: #{path}") unless ::File.exist?(entry)
|
|
25
21
|
|
|
26
22
|
content = ::File.read(entry)
|
|
27
|
-
new(content)
|
|
28
|
-
@doc = self.class.cache[entry] ||
|
|
29
|
-
begin
|
|
30
|
-
self.class.cache[entry] = self.class.parse(content)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
23
|
+
new(content)
|
|
33
24
|
end
|
|
34
25
|
|
|
35
|
-
# Parses SVG/XML content.
|
|
26
|
+
# Parses SVG/XML content in strict XML mode.
|
|
36
27
|
# @param content [String] SVG/XML source content
|
|
37
28
|
# @return [Nokogiri::XML::Document] parsed XML document
|
|
38
|
-
|
|
29
|
+
# @raise [Sevgi::ArgumentError] when content is not well-formed XML
|
|
30
|
+
# @raise [Sevgi::ArgumentError] when content has no root element
|
|
31
|
+
def self.parse(content)
|
|
32
|
+
Nokogiri::XML(content.to_s.lstrip, &:strict).tap do |doc|
|
|
33
|
+
ArgumentError.("XML document has no root element") unless doc.root
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
rescue Nokogiri::XML::SyntaxError => e
|
|
37
|
+
raise ArgumentError, "Malformed XML: #{e.message.lines.first.strip}", cause: e
|
|
38
|
+
end
|
|
39
39
|
|
|
40
40
|
# Extracts the XML declaration from SVG/XML content.
|
|
41
41
|
# @param content [String] SVG/XML source content
|
|
42
42
|
# @return [String, nil] XML declaration line, if present
|
|
43
43
|
def self.declaration(content)
|
|
44
|
-
return unless (content = content.lstrip).start_with?("<?xml ")
|
|
44
|
+
return unless (content = content.to_s.lstrip).start_with?("<?xml ")
|
|
45
45
|
|
|
46
|
-
content
|
|
46
|
+
content[/\A<\?xml\b.*?\?>/m]
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# @!attribute [r] doc
|
|
@@ -57,6 +57,7 @@ module Sevgi
|
|
|
57
57
|
# @yield optional initializer used by {load_file} to install cached parse state
|
|
58
58
|
# @yieldreturn [void]
|
|
59
59
|
# @return [void]
|
|
60
|
+
# @raise [Sevgi::ArgumentError] when content is malformed XML
|
|
60
61
|
def initialize(content, &block)
|
|
61
62
|
instance_exec(&block) if block
|
|
62
63
|
|
|
@@ -67,7 +68,7 @@ module Sevgi
|
|
|
67
68
|
# Converts the root or selected node into a derender node.
|
|
68
69
|
# @param id [String, nil] optional SVG id selecting a node inside the document
|
|
69
70
|
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
70
|
-
# @raise [Sevgi::ArgumentError] when the id is absent
|
|
71
|
+
# @raise [Sevgi::ArgumentError] when the document has no root element or the id is absent
|
|
71
72
|
def decompile(id = nil)
|
|
72
73
|
if id
|
|
73
74
|
if (found = doc.xpath("//*[@id=#{xpath_literal(id)}]") || []).empty?
|
|
@@ -79,7 +80,9 @@ module Sevgi
|
|
|
79
80
|
doc.root
|
|
80
81
|
end => element
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
ArgumentError.("XML document has no root element") unless element
|
|
84
|
+
|
|
85
|
+
Node.new(element, pres, namespaces: namespace_scope(element))
|
|
83
86
|
end
|
|
84
87
|
|
|
85
88
|
# Returns XML declaration and pre-root nodes preserved for root decompilation.
|
|
@@ -93,6 +96,18 @@ module Sevgi
|
|
|
93
96
|
|
|
94
97
|
private
|
|
95
98
|
|
|
99
|
+
def namespace_scope(element)
|
|
100
|
+
element == doc.root ? local_namespaces(element) : element.namespaces
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def local_namespaces(element)
|
|
104
|
+
return {} unless element.respond_to?(:namespace_definitions)
|
|
105
|
+
|
|
106
|
+
element.namespace_definitions.to_h do |namespace|
|
|
107
|
+
[namespace.prefix ? "xmlns:#{namespace.prefix}" : "xmlns", namespace.href]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
96
111
|
def xpath_literal(value)
|
|
97
112
|
value = value.to_s
|
|
98
113
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevgi
|
|
4
|
+
module Derender
|
|
5
|
+
# Builds graphics element trees from derender nodes without evaluating Ruby source.
|
|
6
|
+
# @api private
|
|
7
|
+
class Evaluator
|
|
8
|
+
# Builds an evaluator.
|
|
9
|
+
# @param parent [Sevgi::Graphics::Element] target graphics parent
|
|
10
|
+
# @return [void]
|
|
11
|
+
def initialize(parent) = @parent = parent
|
|
12
|
+
|
|
13
|
+
# Appends a derender node to the target parent.
|
|
14
|
+
# @param node [Sevgi::Derender::Node] derender node
|
|
15
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when the node does not produce graphics output
|
|
16
|
+
def append(node)
|
|
17
|
+
case node.type
|
|
18
|
+
when :CSS
|
|
19
|
+
append_css(node)
|
|
20
|
+
when :Text
|
|
21
|
+
parent.Element(:_, node.content)
|
|
22
|
+
else
|
|
23
|
+
append_element(node)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :parent
|
|
30
|
+
|
|
31
|
+
def append_css(node)
|
|
32
|
+
return unless (hash = Css.to_h(node.node.content))
|
|
33
|
+
|
|
34
|
+
parent.Element(:style, Graphics::Content.css(hash), type: "text/css", **node.attributes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def append_element(node)
|
|
38
|
+
parent.Element(node.name, *content(node), **attributes(node)).tap do |element|
|
|
39
|
+
node.children.each { self.class.new(element).append(it) } unless content(node).any?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def attributes(node) = node.attributes!
|
|
44
|
+
|
|
45
|
+
def content(node)
|
|
46
|
+
node.children.one? && node.children.first.node.text? ? [node.content] : []
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private_constant :Evaluator
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -21,7 +21,11 @@ module Sevgi
|
|
|
21
21
|
# Parses an inline style declaration string.
|
|
22
22
|
# @param style_string [String] CSS declaration source
|
|
23
23
|
# @return [Hash] parsed declarations for the synthetic universal selector
|
|
24
|
-
def to_h!(style_string)
|
|
24
|
+
def to_h!(style_string)
|
|
25
|
+
hash = to_h("* { #{style_string} }")
|
|
26
|
+
|
|
27
|
+
hash ? hash.fetch("*", {}) : {}
|
|
28
|
+
end
|
|
25
29
|
|
|
26
30
|
# Converts a CSS key into a Ruby hash key.
|
|
27
31
|
# @param arg [String] CSS key
|
data/lib/sevgi/derender/node.rb
CHANGED
|
@@ -15,10 +15,13 @@ module Sevgi
|
|
|
15
15
|
# Builds a derender node.
|
|
16
16
|
# @param node [Nokogiri::XML::Node] source XML node
|
|
17
17
|
# @param pres [Array<String>] preamble XML lines carried by the root node
|
|
18
|
+
# @param namespaces [Hash{String => String}, nil] namespace declarations to emit on this node; selected roots use
|
|
19
|
+
# their full inherited namespace scope, while ordinary children use only declarations from their own element
|
|
18
20
|
# @return [void]
|
|
19
|
-
def initialize(node, pres = [])
|
|
21
|
+
def initialize(node, pres = [], namespaces: nil)
|
|
20
22
|
@node = node
|
|
21
23
|
@pres = pres
|
|
24
|
+
@namespaces = namespaces
|
|
22
25
|
@type = dispatch
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -39,23 +42,11 @@ module Sevgi
|
|
|
39
42
|
|
|
40
43
|
# Returns source XML attributes keyed with namespace prefixes when present.
|
|
41
44
|
# @return [Hash{String => String}] XML attributes
|
|
42
|
-
def attributes
|
|
43
|
-
@attributes ||= node.attribute_nodes.to_h do |attr|
|
|
44
|
-
name, value = attr.name, attr.value
|
|
45
|
+
def attributes = @attributes ||= node.attribute_nodes.to_h { [attribute_key(it), it.value] }
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
"#{prefix}:#{name}"
|
|
48
|
-
else
|
|
49
|
-
name
|
|
50
|
-
end => key
|
|
51
|
-
|
|
52
|
-
[key, value]
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Returns source XML attributes.
|
|
47
|
+
# Returns source XML attributes and namespace declarations emitted on this node.
|
|
57
48
|
# @return [Hash{String => String}] XML attributes
|
|
58
|
-
def attributes! = attributes
|
|
49
|
+
def attributes! = {**attributes, **namespaces}
|
|
59
50
|
|
|
60
51
|
# Returns non-ignorable child derender nodes.
|
|
61
52
|
# @return [Array<Sevgi::Derender::Node>] child nodes
|
|
@@ -66,9 +57,12 @@ module Sevgi
|
|
|
66
57
|
.reject { |child| ignorable_child?(child) }
|
|
67
58
|
end
|
|
68
59
|
|
|
69
|
-
# Returns node text content
|
|
60
|
+
# Returns node text content.
|
|
61
|
+
#
|
|
62
|
+
# `xml:space="preserve"` keeps content verbatim. Default-space text nodes are stripped for ordinary pretty-printed
|
|
63
|
+
# content, but inline text boundary spaces next to element siblings are kept because they affect rendered SVG text.
|
|
70
64
|
# @return [String]
|
|
71
|
-
def content = @content ||= preserve_space? ? node.content :
|
|
65
|
+
def content = @content ||= preserve_space? ? node.content : normalized_content
|
|
72
66
|
|
|
73
67
|
# Converts this node into formatted Sevgi DSL Ruby source.
|
|
74
68
|
# @return [String] formatted Sevgi DSL source
|
|
@@ -82,19 +76,18 @@ module Sevgi
|
|
|
82
76
|
# Evaluates this node under a graphics element.
|
|
83
77
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
84
78
|
# @param include_current [Boolean] true to evaluate this node, false to evaluate only children
|
|
85
|
-
# @return [Sevgi::Graphics::Element]
|
|
86
|
-
#
|
|
79
|
+
# @return [Sevgi::Graphics::Element, Array<Sevgi::Graphics::Element>, nil] included current element, included child
|
|
80
|
+
# elements when include_current is false, or nil when the node does not produce graphics output
|
|
87
81
|
def evaluate(element, include_current = true)
|
|
88
|
-
return element.
|
|
82
|
+
return Evaluator.new(element).append(self) if include_current
|
|
89
83
|
|
|
90
|
-
|
|
84
|
+
evaluate_children(element)
|
|
91
85
|
end
|
|
92
86
|
|
|
93
87
|
# Evaluates only this node's children under a graphics element.
|
|
94
88
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
95
|
-
# @return [Sevgi::Graphics::Element]
|
|
96
|
-
|
|
97
|
-
def evaluate!(element) = evaluate(element, false)
|
|
89
|
+
# @return [Array<Sevgi::Graphics::Element>] included child elements
|
|
90
|
+
def evaluate_children(element) = children.map { it.evaluate(element) }.compact
|
|
98
91
|
|
|
99
92
|
# Finds the first descendant whose attribute matches a value.
|
|
100
93
|
# @param arg [String] attribute value to find
|
|
@@ -103,22 +96,16 @@ module Sevgi
|
|
|
103
96
|
def find(arg, by: "id")
|
|
104
97
|
return self if attributes[by] == arg
|
|
105
98
|
|
|
106
|
-
children
|
|
107
|
-
found = child.find(arg, by:)
|
|
108
|
-
|
|
109
|
-
return found if found
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
nil
|
|
99
|
+
children.lazy.map { it.find(arg, by:) }.find(&:itself)
|
|
113
100
|
end
|
|
114
101
|
|
|
115
102
|
# Returns the source XML node name.
|
|
116
103
|
# @return [String]
|
|
117
104
|
def name = @name ||= node.name
|
|
118
105
|
|
|
119
|
-
# Returns source XML
|
|
106
|
+
# Returns source XML namespace declarations emitted on this node.
|
|
120
107
|
# @return [Hash{String => String}] namespace declarations
|
|
121
|
-
def namespaces = (@namespaces ||=
|
|
108
|
+
def namespaces = (@namespaces ||= local_namespaces)
|
|
122
109
|
|
|
123
110
|
# Reports whether this node is the SVG root strategy.
|
|
124
111
|
# @return [Boolean]
|
|
@@ -126,14 +113,17 @@ module Sevgi
|
|
|
126
113
|
|
|
127
114
|
private
|
|
128
115
|
|
|
116
|
+
def attribute_key(attribute) = [attribute.namespace&.prefix, attribute.name].compact.join(":")
|
|
117
|
+
|
|
129
118
|
def dispatch
|
|
130
|
-
|
|
119
|
+
case
|
|
120
|
+
when node.text?
|
|
131
121
|
:Text
|
|
132
|
-
|
|
122
|
+
when node.comment?
|
|
133
123
|
:Junk
|
|
134
|
-
|
|
124
|
+
when node.name == "style"
|
|
135
125
|
:CSS
|
|
136
|
-
|
|
126
|
+
when node.name == "svg"
|
|
137
127
|
:Root
|
|
138
128
|
else
|
|
139
129
|
:Any
|
|
@@ -142,7 +132,8 @@ module Sevgi
|
|
|
142
132
|
end
|
|
143
133
|
|
|
144
134
|
def ignorable_child?(child)
|
|
145
|
-
child.type == :Junk ||
|
|
135
|
+
child.type == :Junk ||
|
|
136
|
+
(child.node.text? && child.node.text.strip.empty? && !child.preserve_space? && !child.inline_text?)
|
|
146
137
|
end
|
|
147
138
|
|
|
148
139
|
protected
|
|
@@ -160,8 +151,20 @@ module Sevgi
|
|
|
160
151
|
false
|
|
161
152
|
end
|
|
162
153
|
|
|
154
|
+
def inline_text? = node.text? && !node.content.match?(/[\r\n]/) && node.parent&.children&.any?(&:element?)
|
|
155
|
+
|
|
163
156
|
private
|
|
164
157
|
|
|
158
|
+
def normalized_content = inline_text? ? node.content : node.content.strip
|
|
159
|
+
|
|
160
|
+
def local_namespaces
|
|
161
|
+
return {} unless node.respond_to?(:namespace_definitions)
|
|
162
|
+
|
|
163
|
+
node.namespace_definitions.to_h do |namespace|
|
|
164
|
+
[namespace.prefix ? "xmlns:#{namespace.prefix}" : "xmlns", namespace.href]
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
165
168
|
def each_node
|
|
166
169
|
current = node
|
|
167
170
|
|
data/lib/sevgi/derender.rb
CHANGED
|
@@ -5,32 +5,98 @@ require_relative "derender/internal"
|
|
|
5
5
|
require_relative "derender/attributes"
|
|
6
6
|
require_relative "derender/document"
|
|
7
7
|
require_relative "derender/elements"
|
|
8
|
+
require_relative "derender/evaluator"
|
|
8
9
|
require_relative "derender/node"
|
|
9
10
|
|
|
10
11
|
require_relative "derender/version"
|
|
11
12
|
|
|
12
13
|
module Sevgi
|
|
13
14
|
# Converts SVG/XML content into Sevgi DSL source or evaluates it into graphics elements.
|
|
15
|
+
#
|
|
16
|
+
# Evaluation APIs treat SVG/XML as data: they build graphics element trees directly and do not execute generated Ruby
|
|
17
|
+
# source. Malformed, rootless, or unmatched input is rejected with {Sevgi::ArgumentError}.
|
|
14
18
|
module Derender
|
|
19
|
+
# @!method self.decompile(content, id: nil)
|
|
20
|
+
# Converts SVG/XML content into a derender node.
|
|
21
|
+
# @param content [String] SVG/XML source content
|
|
22
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
23
|
+
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
24
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
25
|
+
# @!method self.decompile_file(file, id: nil)
|
|
26
|
+
# Converts an SVG/XML file into a derender node.
|
|
27
|
+
# @param file [String] path to the source SVG/XML file
|
|
28
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
29
|
+
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
30
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
31
|
+
# absent
|
|
32
|
+
# @!method self.derender(content, id: nil)
|
|
33
|
+
# Converts SVG/XML content into Sevgi DSL Ruby source.
|
|
34
|
+
# @param content [String] SVG/XML source content
|
|
35
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
36
|
+
# @return [String] formatted Sevgi DSL source
|
|
37
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
38
|
+
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
39
|
+
# @!method self.derender_file(file, id: nil)
|
|
40
|
+
# Converts an SVG/XML file into Sevgi DSL Ruby source.
|
|
41
|
+
# @param file [String] path to the source SVG/XML file
|
|
42
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
43
|
+
# @return [String] formatted Sevgi DSL source
|
|
44
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
45
|
+
# absent
|
|
46
|
+
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
47
|
+
# @!method self.evaluate(content, element, id: nil)
|
|
48
|
+
# Evaluates SVG/XML content under a graphics element, including the selected node.
|
|
49
|
+
# @param content [String] SVG/XML source content
|
|
50
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
51
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
52
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
53
|
+
# produces no graphics output
|
|
54
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
55
|
+
# @!method self.evaluate_children(content, element, id: nil)
|
|
56
|
+
# Evaluates only the selected node's children under a graphics element.
|
|
57
|
+
# @param content [String] SVG/XML source content
|
|
58
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
59
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
60
|
+
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
61
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
62
|
+
# @!method self.evaluate_file(file, element, id: nil)
|
|
63
|
+
# Evaluates an SVG/XML file under a graphics element, including the selected node.
|
|
64
|
+
# @param file [String] path to the source SVG/XML file
|
|
65
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
66
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
67
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
68
|
+
# produces no graphics output
|
|
69
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
70
|
+
# absent
|
|
71
|
+
# @!method self.evaluate_file_children(file, element, id: nil)
|
|
72
|
+
# Evaluates only the selected node's children from an SVG/XML file under a graphics element.
|
|
73
|
+
# @param file [String] path to the source SVG/XML file
|
|
74
|
+
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
75
|
+
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
76
|
+
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
77
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
78
|
+
# absent
|
|
79
|
+
|
|
15
80
|
# Converts SVG/XML content into a derender node.
|
|
16
81
|
# @param content [String] SVG/XML source content
|
|
17
82
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
18
83
|
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
19
|
-
# @raise [Sevgi::ArgumentError] when the id is absent
|
|
84
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
20
85
|
def decompile(content, id: nil) = Document.new(content).decompile(id)
|
|
21
86
|
|
|
22
87
|
# Converts an SVG/XML file into a derender node.
|
|
23
88
|
# @param file [String] path to the source SVG/XML file
|
|
24
89
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
25
90
|
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
26
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found or the id is
|
|
91
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
92
|
+
# absent
|
|
27
93
|
def decompile_file(file, id: nil) = Document.load_file(file).decompile(id)
|
|
28
94
|
|
|
29
95
|
# Converts SVG/XML content into Sevgi DSL Ruby source.
|
|
30
96
|
# @param content [String] SVG/XML source content
|
|
31
97
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
32
98
|
# @return [String] formatted Sevgi DSL source
|
|
33
|
-
# @raise [Sevgi::ArgumentError] when the id is absent
|
|
99
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
34
100
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
35
101
|
def derender(content, id: nil) = Document.new(content).decompile(id).derender
|
|
36
102
|
|
|
@@ -38,7 +104,8 @@ module Sevgi
|
|
|
38
104
|
# @param file [String] path to the source SVG/XML file
|
|
39
105
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
40
106
|
# @return [String] formatted Sevgi DSL source
|
|
41
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found or the id is
|
|
107
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
108
|
+
# absent
|
|
42
109
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
43
110
|
def derender_file(file, id: nil) = Document.load_file(file).decompile(id).derender
|
|
44
111
|
|
|
@@ -46,37 +113,39 @@ module Sevgi
|
|
|
46
113
|
# @param content [String] SVG/XML source content
|
|
47
114
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
48
115
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
49
|
-
# @return [Sevgi::Graphics::Element]
|
|
50
|
-
#
|
|
51
|
-
# @raise [Sevgi::
|
|
116
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
117
|
+
# produces no graphics output
|
|
118
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
52
119
|
def evaluate(content, element, id: nil) = Document.new(content).decompile(id).evaluate(element)
|
|
53
120
|
|
|
54
|
-
# Evaluates
|
|
55
|
-
# @param
|
|
121
|
+
# Evaluates only the selected node's children under a graphics element.
|
|
122
|
+
# @param content [String] SVG/XML source content
|
|
56
123
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
57
124
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
58
|
-
# @return [Sevgi::Graphics::Element]
|
|
59
|
-
# @raise [Sevgi::ArgumentError] when
|
|
60
|
-
|
|
61
|
-
def evaluate_file(file, element, id: nil) = Document.load_file(file).decompile(id).evaluate(element)
|
|
125
|
+
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
126
|
+
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
127
|
+
def evaluate_children(content, element, id: nil) = Document.new(content).decompile(id).evaluate_children(element)
|
|
62
128
|
|
|
63
|
-
# Evaluates SVG/XML
|
|
64
|
-
# @param
|
|
129
|
+
# Evaluates an SVG/XML file under a graphics element, including the selected node.
|
|
130
|
+
# @param file [String] path to the source SVG/XML file
|
|
65
131
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
66
132
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
67
|
-
# @return [Sevgi::Graphics::Element]
|
|
68
|
-
#
|
|
69
|
-
# @raise [Sevgi::
|
|
70
|
-
|
|
133
|
+
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
134
|
+
# produces no graphics output
|
|
135
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
136
|
+
# absent
|
|
137
|
+
def evaluate_file(file, element, id: nil) = Document.load_file(file).decompile(id).evaluate(element)
|
|
71
138
|
|
|
72
|
-
# Evaluates an SVG/XML file under a graphics element
|
|
139
|
+
# Evaluates only the selected node's children from an SVG/XML file under a graphics element.
|
|
73
140
|
# @param file [String] path to the source SVG/XML file
|
|
74
141
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
75
142
|
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
76
|
-
# @return [Sevgi::Graphics::Element]
|
|
77
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found or the id is
|
|
78
|
-
#
|
|
79
|
-
def
|
|
143
|
+
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
144
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
145
|
+
# absent
|
|
146
|
+
def evaluate_file_children(file, element, id: nil)
|
|
147
|
+
Document.load_file(file).decompile(id).evaluate_children(element)
|
|
148
|
+
end
|
|
80
149
|
|
|
81
150
|
extend self
|
|
82
151
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sevgi-derender
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.94.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Recai Oktaş
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.94.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.94.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: sevgi-graphics
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.94.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.94.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: css_parser
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,9 +86,10 @@ executables:
|
|
|
86
86
|
extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
|
88
88
|
files:
|
|
89
|
+
- CHANGELOG.md
|
|
90
|
+
- LICENSE
|
|
89
91
|
- README.md
|
|
90
92
|
- bin/igves
|
|
91
|
-
- lib/sevgi/binaries/igsev.rb
|
|
92
93
|
- lib/sevgi/binaries/igves.rb
|
|
93
94
|
- lib/sevgi/derender.rb
|
|
94
95
|
- lib/sevgi/derender/attributes.rb
|
|
@@ -99,6 +100,7 @@ files:
|
|
|
99
100
|
- lib/sevgi/derender/elements/junk.rb
|
|
100
101
|
- lib/sevgi/derender/elements/root.rb
|
|
101
102
|
- lib/sevgi/derender/elements/text.rb
|
|
103
|
+
- lib/sevgi/derender/evaluator.rb
|
|
102
104
|
- lib/sevgi/derender/internal.rb
|
|
103
105
|
- lib/sevgi/derender/node.rb
|
|
104
106
|
- lib/sevgi/derender/version.rb
|
|
@@ -117,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
117
119
|
requirements:
|
|
118
120
|
- - ">="
|
|
119
121
|
- !ruby/object:Gem::Version
|
|
120
|
-
version: 3.4.0
|
|
122
|
+
version: 3.4.0
|
|
121
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
124
|
requirements:
|
|
123
125
|
- - ">="
|
data/lib/sevgi/binaries/igsev.rb
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "sevgi"
|
|
4
|
-
|
|
5
|
-
module Sevgi
|
|
6
|
-
module Binaries
|
|
7
|
-
# Implements the `igsev` executable that derenders SVG and executes the generated Sevgi DSL.
|
|
8
|
-
module Igsev
|
|
9
|
-
extend self
|
|
10
|
-
|
|
11
|
-
# Executable name used in help output.
|
|
12
|
-
PROGNAME = "igsev"
|
|
13
|
-
|
|
14
|
-
# Error raised for invalid command-line usage.
|
|
15
|
-
Error = Class.new(::Sevgi::Error)
|
|
16
|
-
|
|
17
|
-
# Parsed command-line options for the `igsev` executable.
|
|
18
|
-
# @api private
|
|
19
|
-
Options = Struct.new(:require, :vomit, :help, :version) do
|
|
20
|
-
# Parses command-line options and removes them from the argv array.
|
|
21
|
-
# @param argv [Array<String>] mutable command-line argument array
|
|
22
|
-
# @return [Sevgi::Binaries::Igsev::Options] parsed options
|
|
23
|
-
# @raise [Sevgi::Binaries::Igsev::Error] when an option is not recognized
|
|
24
|
-
def self.parse(argv)
|
|
25
|
-
new.tap do |options|
|
|
26
|
-
argv.first.start_with?("-") ? option(argv, options) : break until argv.empty?
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
class << self
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def option(argv, options)
|
|
34
|
-
case (arg = argv.shift)
|
|
35
|
-
when "-r", "--require"
|
|
36
|
-
options.require = argv.shift
|
|
37
|
-
when "-x", "--exception"
|
|
38
|
-
options.vomit = true
|
|
39
|
-
|
|
40
|
-
when "-h", "--help"
|
|
41
|
-
options.help = true
|
|
42
|
-
when "-v", "--version"
|
|
43
|
-
options.version = true
|
|
44
|
-
else
|
|
45
|
-
Error.("Not a valid option: #{arg}")
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private_constant :Options
|
|
52
|
-
|
|
53
|
-
# Runs the `igsev` command-line interface.
|
|
54
|
-
# @param argv [Array<String>, String, nil] command-line arguments
|
|
55
|
-
# @return [nil]
|
|
56
|
-
# @raise [LoadError] when a required Ruby library cannot be loaded
|
|
57
|
-
# @raise [Sevgi::ArgumentError] when the SVG file cannot be found
|
|
58
|
-
# @raise [Sevgi::Executor::Error] when `--exception` or `SEVGI_VOMIT` requests raw errors
|
|
59
|
-
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
60
|
-
# @raise [SystemExit] when command-line usage or script execution aborts
|
|
61
|
-
def call(argv)
|
|
62
|
-
return puts(help) if (options = Options.parse(argv = Array(argv))).help
|
|
63
|
-
return puts(::Sevgi::VERSION) if options.version
|
|
64
|
-
|
|
65
|
-
result = run(file = argv.shift, options)
|
|
66
|
-
|
|
67
|
-
if result.error?
|
|
68
|
-
raise result.error if options.vomit || ENV[ENVVOMIT]
|
|
69
|
-
|
|
70
|
-
die(result.error, file)
|
|
71
|
-
else
|
|
72
|
-
result.recent.Out()
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
rescue Binaries::Igsev::Error => e
|
|
76
|
-
abort(e.message)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
def die(error, _file)
|
|
82
|
-
warn(error.message)
|
|
83
|
-
warn("")
|
|
84
|
-
error.backtrace!.each { warn(" #{it}") }
|
|
85
|
-
|
|
86
|
-
exit(1)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def help
|
|
90
|
-
<<~HELP
|
|
91
|
-
Usage: #{PROGNAME} [options...] <SVG file>
|
|
92
|
-
|
|
93
|
-
See documentation for detailed help.
|
|
94
|
-
|
|
95
|
-
Options:
|
|
96
|
-
|
|
97
|
-
-r, --require LIB Require Ruby LIB
|
|
98
|
-
-x, --exception Raise exception instead of abort
|
|
99
|
-
|
|
100
|
-
-h, --help Show this help
|
|
101
|
-
-v, --version Display version
|
|
102
|
-
HELP
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def run(file, options)
|
|
106
|
-
Error.("No SVG file given.") unless file
|
|
107
|
-
|
|
108
|
-
sevgi = Derender.derender_file(file)
|
|
109
|
-
|
|
110
|
-
::Sevgi.execute(sevgi, require: options.require)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|