sevgi 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/LICENSE +674 -0
  4. data/README.md +15 -0
  5. data/bin/sevgi +136 -0
  6. data/lib/sevgi/errors.rb +11 -0
  7. data/lib/sevgi/external.rb +8 -0
  8. data/lib/sevgi/function.rb +35 -0
  9. data/lib/sevgi/geometry/elements/rect.rb +133 -0
  10. data/lib/sevgi/geometry/elements/segment.rb +102 -0
  11. data/lib/sevgi/geometry/elements.rb +41 -0
  12. data/lib/sevgi/geometry/equation/line/diagonal.rb +90 -0
  13. data/lib/sevgi/geometry/equation/line/horizontal.rb +15 -0
  14. data/lib/sevgi/geometry/equation/line/vertical.rb +52 -0
  15. data/lib/sevgi/geometry/equation/line.rb +38 -0
  16. data/lib/sevgi/geometry/equation.rb +3 -0
  17. data/lib/sevgi/geometry/errors.rb +5 -0
  18. data/lib/sevgi/geometry/external.rb +15 -0
  19. data/lib/sevgi/geometry/operation/align.rb +40 -0
  20. data/lib/sevgi/geometry/operation/sweep.rb +52 -0
  21. data/lib/sevgi/geometry/operation.rb +28 -0
  22. data/lib/sevgi/geometry/point.rb +62 -0
  23. data/lib/sevgi/geometry.rb +10 -0
  24. data/lib/sevgi/graphics/attribute.rb +125 -0
  25. data/lib/sevgi/graphics/canvas.rb +51 -0
  26. data/lib/sevgi/graphics/content.rb +72 -0
  27. data/lib/sevgi/graphics/document/base.rb +51 -0
  28. data/lib/sevgi/graphics/document/default.rb +19 -0
  29. data/lib/sevgi/graphics/document/html.rb +11 -0
  30. data/lib/sevgi/graphics/document/inkscape.rb +17 -0
  31. data/lib/sevgi/graphics/document/minimal.rb +11 -0
  32. data/lib/sevgi/graphics/document.rb +53 -0
  33. data/lib/sevgi/graphics/element.rb +85 -0
  34. data/lib/sevgi/graphics/external.rb +15 -0
  35. data/lib/sevgi/graphics/mixtures/core.rb +81 -0
  36. data/lib/sevgi/graphics/mixtures/duplicate.rb +36 -0
  37. data/lib/sevgi/graphics/mixtures/hatch.rb +21 -0
  38. data/lib/sevgi/graphics/mixtures/identify.rb +93 -0
  39. data/lib/sevgi/graphics/mixtures/inkscape.rb +15 -0
  40. data/lib/sevgi/graphics/mixtures/lint.rb +33 -0
  41. data/lib/sevgi/graphics/mixtures/render.rb +149 -0
  42. data/lib/sevgi/graphics/mixtures/replicate.rb +125 -0
  43. data/lib/sevgi/graphics/mixtures/save.rb +23 -0
  44. data/lib/sevgi/graphics/mixtures/transform.rb +78 -0
  45. data/lib/sevgi/graphics/mixtures/underscore.rb +21 -0
  46. data/lib/sevgi/graphics/mixtures/validate.rb +27 -0
  47. data/lib/sevgi/graphics/mixtures/wrappers.rb +63 -0
  48. data/lib/sevgi/graphics/mixtures.rb +16 -0
  49. data/lib/sevgi/graphics.rb +10 -0
  50. data/lib/sevgi/internal/constant.rb +5 -0
  51. data/lib/sevgi/internal/dim.rb +13 -0
  52. data/lib/sevgi/internal/function/file.rb +30 -0
  53. data/lib/sevgi/internal/function/float.rb +35 -0
  54. data/lib/sevgi/internal/function/formula.rb +71 -0
  55. data/lib/sevgi/internal/function/math.rb +43 -0
  56. data/lib/sevgi/internal/function/string.rb +13 -0
  57. data/lib/sevgi/internal/function.rb +7 -0
  58. data/lib/sevgi/internal/list.rb +27 -0
  59. data/lib/sevgi/internal/locate.rb +47 -0
  60. data/lib/sevgi/internal/margin.rb +23 -0
  61. data/lib/sevgi/internal/minitest/script.rb +50 -0
  62. data/lib/sevgi/internal/minitest/shell.rb +71 -0
  63. data/lib/sevgi/internal/minitest/suite.rb +35 -0
  64. data/lib/sevgi/internal/minitest.rb +5 -0
  65. data/lib/sevgi/internal/paper.rb +26 -0
  66. data/lib/sevgi/internal.rb +10 -0
  67. data/lib/sevgi/standard/conform.rb +55 -0
  68. data/lib/sevgi/standard/data/attribute.rb +496 -0
  69. data/lib/sevgi/standard/data/element.rb +269 -0
  70. data/lib/sevgi/standard/data/specification.rb +1841 -0
  71. data/lib/sevgi/standard/data.rb +79 -0
  72. data/lib/sevgi/standard/errors.rb +28 -0
  73. data/lib/sevgi/standard/model.rb +82 -0
  74. data/lib/sevgi/standard.rb +26 -0
  75. data/lib/sevgi/utensils/external.rb +22 -0
  76. data/lib/sevgi/utensils/grid.rb +49 -0
  77. data/lib/sevgi/utensils/ruler.rb +47 -0
  78. data/lib/sevgi/utensils/tsquare.rb +28 -0
  79. data/lib/sevgi/utensils.rb +7 -0
  80. data/lib/sevgi/version.rb +5 -0
  81. data/lib/sevgi.rb +10 -0
  82. metadata +127 -0
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sevgi/standard"
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ class Element
8
+ private_class_method :new
9
+
10
+ RootParent = Object.new.tap { |this| def this.inspect = "RootParent" }.freeze
11
+
12
+ module Ident
13
+ def id(given) = (@id ||= {})[given] ||= given.to_s.tr("_", "-").to_sym
14
+ end
15
+
16
+ extend Ident
17
+
18
+ attr_reader :name, :attributes, :children, :contents, :parent
19
+
20
+ def initialize(name, attributes: {}, contents: [], parent:, &block)
21
+ @name = name
22
+ @attributes = Attributes.new(attributes)
23
+ @contents = contents
24
+ @parent = parent
25
+ @children = []
26
+
27
+ parent.children << self unless self.class.root?(self)
28
+
29
+ instance_exec(&block) if block
30
+ end
31
+
32
+ def method_missing(name, *, &block)
33
+ Element.valid?(id = Element.id(name)) ? Dispatch.(self, id, *, &block) : super
34
+ end
35
+
36
+ def respond_to_missing?(name, include_private = false)
37
+ Element.valid?(Element.id(name)) || super
38
+ end
39
+
40
+ protected
41
+
42
+ attr_writer :children, :attributes
43
+
44
+ class << self
45
+ def element(name, *, parent:, &block) = new(name, **Dispatch.parse(name, *), parent:, &block)
46
+
47
+ def root(*, &block) = element(:svg, *, parent: RootParent, &block)
48
+
49
+ def root?(element) = element.parent == RootParent
50
+
51
+ def valid?(name) = Standard.element?(name)
52
+ end
53
+
54
+ module Dispatch
55
+ extend self
56
+
57
+ def call(element, name, *, &block)
58
+ # Low-hanging fruit optimization: define missing method to avoid dispatching cost
59
+ Element.class_exec do
60
+ define_method(name) { |*args, &block| self.class.element(name, *args, parent: self, &block) }
61
+ end unless Element.method_defined?(name)
62
+
63
+ element.public_send(name, *, &block)
64
+ end
65
+
66
+ def parse(name, *args)
67
+ attributes, contents = {}, []
68
+
69
+ args.each do |arg|
70
+ case arg
71
+ when ::Hash then attributes = arg
72
+ when ::String then contents << Graphics.Content(arg)
73
+ when Content then contents << arg
74
+ else ArgumentError.("Argument of element '#{name}' must be a Hash or String: #{arg}")
75
+ end
76
+ end
77
+
78
+ { attributes:, contents: }
79
+ end
80
+ end
81
+
82
+ private_constant :Dispatch
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module External
6
+ def Canvas(...) = Canvas.(...)
7
+
8
+ def Verbatim(content) = Content::Verbatim.new(content)
9
+
10
+ def SVG(document = :default, canvas = nil, **, &block) = Profile.(document, canvas, **, &block)
11
+ end
12
+
13
+ extend External
14
+ end
15
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ module Mixtures
8
+ module Core
9
+ module InstanceMethods
10
+ extend Forwardable
11
+ def_delegators :@attributes, :[], :[]=, :has?
12
+ def_delegators :@children, :first, :last, :at
13
+
14
+ def Adopt(new_parent = nil, index: -1)
15
+ tap do
16
+ if new_parent
17
+ ArgumentError.("Element type does not match the new parent type: #{self.class}") unless instance_of?(new_parent.class)
18
+ else
19
+ new_parent = parent
20
+ end
21
+
22
+ self.Orphan
23
+ (@parent = new_parent).children.insert(index, self)
24
+ end
25
+ end
26
+
27
+ def AdoptFirst(*)
28
+ Adopt(*, index: 0)
29
+ end
30
+
31
+ def Append(*elements)
32
+ tap { elements.each { _1.Adopt(self) } }
33
+ end
34
+
35
+ def Orphan
36
+ parent.children&.delete(self) unless Root?
37
+ end
38
+
39
+ def Prepend(*elements)
40
+ tap { elements.each { _1.AdoptFirst(self) } }
41
+ end
42
+
43
+ def Root
44
+ element = self
45
+ while element
46
+ break if element.Root?
47
+
48
+ element = element.parent
49
+ end
50
+ element
51
+ end
52
+
53
+ def Root?
54
+ self.class.root?(self)
55
+ end
56
+
57
+ def Traverse(depth = 0, leave = nil, &block)
58
+ tap do
59
+ yield(self, depth)
60
+
61
+ children.each { |child| child.Traverse(depth + 1, leave, &block) }
62
+ leave&.call(self, depth)
63
+ end
64
+ end
65
+
66
+ def With(element = nil, ...)
67
+ tap { (element || self).parent.instance_exec(self, ...) }
68
+ end
69
+
70
+ def Within(element = nil, ...)
71
+ tap { (element || self).instance_exec(...) }
72
+ end
73
+
74
+ def <<(element)
75
+ Append(element)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Duplicate
7
+ module InstanceMethods
8
+ def Duplicate(dx: nil, dy: nil, parent: nil, &block)
9
+ duplicated = dup
10
+
11
+ duplicated.Traverse do |element|
12
+ element.children = element.children.map(&:dup)
13
+ id = (element.attributes = element.attributes.dup).delete(:id)
14
+ element[:_id] = id if id
15
+ block&.call(element)
16
+ end
17
+
18
+ duplicated.Translate(dx, dy) if dx || dy
19
+
20
+ duplicated.Adopt(parent)
21
+
22
+ duplicated
23
+ end
24
+
25
+ def DuplicateH(dx, parent: nil, &block)
26
+ Duplicate(dx:, dy: 0, parent:, &block)
27
+ end
28
+
29
+ def DuplicateV(dy, parent: nil, &block)
30
+ Duplicate(dx: 0, dy:, parent:, &block)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Hatch
7
+ module InstanceMethods
8
+ def Draw(segments, **)
9
+ segments.each { |segment| segment.draw(self, **) }
10
+ end
11
+
12
+ def Hatch(canvas, direction:, step:, **)
13
+ Geometry::Operation.sweep!(rect = canvas.rect, initial: rect.position, direction:, step:).tap do |segments|
14
+ Draw(segments, **)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Identify
7
+ module InstanceMethods
8
+ class Identifiers
9
+ attr_reader :element, :namespace, :collision
10
+
11
+ def initialize(element)
12
+ @element = element
13
+ @namespace = {}
14
+ @collision = {}
15
+
16
+ build
17
+ end
18
+
19
+ def conflict?
20
+ !@collision.empty?
21
+ end
22
+
23
+ def [](*)
24
+ @namespace[*]
25
+ end
26
+
27
+ private
28
+
29
+ def build
30
+ element.Traverse do |element|
31
+ next unless (id = element[:id])
32
+
33
+ if @namespace.key?(id)
34
+ (@collision[id] ||= [@namespace[id]]) << element
35
+ else
36
+ @namespace[id] = element
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def Disidentify
43
+ Traverse do |element|
44
+ next unless element[:id]
45
+
46
+ element[:_id] = element.attributes.delete(:id)
47
+ end
48
+ end
49
+
50
+ def IdentifyAsTable(...) = IdentifyAsTable.(...)
51
+
52
+ def IdentifyAsList(...) = IdentifyAsList.(...)
53
+
54
+ def Reidentify
55
+ end
56
+
57
+ def Identifiers = Identifiers.new(self)
58
+
59
+ SEPARATOR = "-"
60
+
61
+ IdentifyAs = Data.define(:id) do
62
+ def label(*indexes) = (id and [id, *indexes].map(&:to_s).join(SEPARATOR))
63
+ end
64
+
65
+ IdentifyAsList = Class.new(IdentifyAs) do
66
+ class << self
67
+ def call(element, i)
68
+ element.each_with_index do |it, index|
69
+ i and (label = self[i].label(index + 1)) and it[:id] = label
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ IdentifyAsTable = Class.new(IdentifyAs) do
76
+ class << self
77
+ def call(element, ix: nil, iy: nil)
78
+ element.each_with_index do |row, irow|
79
+ iy and (label = self[iy].label(irow + 1)) and row[:id] = label
80
+ ix and row.children.each_with_index do |col, icol|
81
+ (label = self[ix].label(irow + 1, icol + 1)) and col[:id] = label
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ private_constant :IdentifyAs, :IdentifyAsTable, :IdentifyAsList
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Inkscape
7
+ module InstanceMethods
8
+ def layer(**, &block)
9
+ g("inkscape:groupmode": "layer", "sodipodi:insensitive": "true", **, &block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ LintError = Class.new(Error)
5
+
6
+ module Graphics
7
+ module Mixtures
8
+ module Lint
9
+ module InstanceMethods
10
+ def Lint(...)
11
+ IdentitiesAreUniq.(self, ...)
12
+ end
13
+
14
+ module IdentitiesAreUniq
15
+ extend self
16
+
17
+ def call(element)
18
+ return true unless (identifiers = element.Identifiers).conflict?
19
+
20
+ collisions = identifiers.collision.map do |id, elements|
21
+ "Element(s) with the same id '#{id}': #{elements.map(&:name).uniq.join(", ")}"
22
+ end.map { "\t#{_1}" }.join("\n")
23
+
24
+ LintError.("Found Id collisions:\n#{collisions}")
25
+ end
26
+ end
27
+
28
+ private_constant :IdentitiesAreUniq
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Render
7
+ class Renderer
8
+ DEFAULTS = { indent: " ", linelength: 140, style: :hybrid }.freeze
9
+
10
+ module Attributes
11
+ module Block
12
+ def attributes(element, depth)
13
+ attributes_block(element, depth, element.attributes.to_xml_lines)
14
+ end
15
+ end
16
+
17
+ module Hybrid
18
+ def attributes(element, depth)
19
+ if attributes_as_block?(lines = element.attributes.to_xml_lines, depth)
20
+ attributes_block(element, depth, lines)
21
+ else
22
+ attributes_inline(element, depth, lines)
23
+ end
24
+ end
25
+
26
+ def linelength(lines, depth)
27
+ indent(depth).length + lines.sum(&:length)
28
+ end
29
+
30
+ def attributes_as_block?(lines, depth)
31
+ linelength(lines, depth) > options[:linelength]
32
+ end
33
+ end
34
+
35
+ module Inline
36
+ def attributes(element, depth)
37
+ attributes_inline(element, depth, element.attributes.to_xml_lines)
38
+ end
39
+ end
40
+ end
41
+
42
+ private_constant :Attributes
43
+
44
+ attr_reader :root, :options, :output
45
+
46
+ def initialize(root, **)
47
+ @root = root
48
+ @options = DEFAULTS.merge(**)
49
+ @output = []
50
+
51
+ build
52
+ end
53
+
54
+ def call(*preambles)
55
+ output.append(preambles) unless preambles.empty?
56
+
57
+ root.Traverse(
58
+ 0,
59
+ proc { |element, depth| render_leave(element, depth) },
60
+ ) { |element, depth| render_enter(element, depth) }
61
+
62
+ output.join("\n")
63
+ end
64
+
65
+ def append(depth, *lines)
66
+ unless lines.empty?
67
+ indentation = indent(depth)
68
+ output.append(lines.map { "#{indentation}#{_1}" })
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def build
75
+ ArgumentError.("Missing style") unless options[:style]
76
+
77
+ case options[:style]
78
+ when :hybrid then extend(Attributes::Hybrid)
79
+ when :inline then extend(Attributes::Inline)
80
+ when :block then extend(Attributes::Block)
81
+ else ArgumentError.("Unrecognized style: #{options[:style]}")
82
+ end
83
+
84
+ unclosed
85
+ end
86
+
87
+ def childless?(element)
88
+ element.children.empty? && element.contents.empty?
89
+ end
90
+
91
+ def closed = @closed = true
92
+
93
+ def closed? = @closed.tap { unclosed }
94
+
95
+ def attributes_block(element, depth, lines)
96
+ append(depth, "<#{element.name}")
97
+ append(depth + 1, *lines)
98
+ append(depth, ">") unless childless?(element)
99
+ end
100
+
101
+ def attributes_inline(element, depth, lines)
102
+ line = "<#{[element.name, *lines].join(" ")}"
103
+
104
+ append(depth, (childless?(element) ? "#{line}/>".tap { closed } : "#{line}>"))
105
+ end
106
+
107
+ def contents(element, depth)
108
+ return if element.contents.empty?
109
+
110
+ if floating?(element)
111
+ append(depth, *element.contents.map(&:to_s))
112
+ else
113
+ element.contents.each { |content| content.render(self, depth) }
114
+ end
115
+ end
116
+
117
+ def indent(depth)
118
+ options[:indent] * depth
119
+ end
120
+
121
+ def render_enter(element, depth)
122
+ attributes(element, depth) unless floating?(element)
123
+ contents(element, depth)
124
+ end
125
+
126
+ def render_leave(element, depth)
127
+ return if closed? || floating?(element)
128
+
129
+ append(depth, (childless?(element) ? "/>" : "</#{element.name}>").to_s)
130
+ end
131
+
132
+ def unclosed = @closed = false
133
+
134
+ def floating?(element) = element.name == :"-"
135
+
136
+ class << self
137
+ def call(root, **) = new(root, **).call(*root.class.preambles)
138
+ end
139
+ end
140
+
141
+ private_constant :Renderer
142
+
143
+ module InstanceMethods
144
+ def Render(**) = Renderer.(self, **)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Replicate
7
+ module InstanceMethods
8
+ def Replicate(nx: Undefined, dx: Undefined, ix: nil, ny: Undefined, dy: Undefined, iy: nil, id: nil, &block)
9
+ Ensure.without_block(block, nx:, dx:, ny:, dy:)
10
+
11
+ With do |base|
12
+ layer(id:) do
13
+ row = layer do
14
+ (nx - 1).times do |time|
15
+ base.DuplicateH((time + 1) * dx, parent: self)
16
+ end
17
+ end
18
+
19
+ base.AdoptFirst(row)
20
+
21
+ (ny - 1).times do |time|
22
+ row.DuplicateV((time + 1) * dy)
23
+ end
24
+ end.tap do |element|
25
+ IdentifyAsTable(element.children, ix:, iy:)
26
+ end
27
+ end
28
+ end
29
+
30
+ def ReplicateH(n: Undefined, d: Undefined, i: nil, id: nil, &block)
31
+ Ensure.without_block(block, n:, d:)
32
+
33
+ With do |base|
34
+ layer(id:) do
35
+ (n - 1).times do |time|
36
+ base.DuplicateH((time + 1) * d, parent: self)
37
+ end
38
+ end.tap do |element|
39
+ base.AdoptFirst(element)
40
+ IdentifyAsList(element.children, i)
41
+ end
42
+ end
43
+ end
44
+
45
+ def ReplicateV(n: Undefined, d: Undefined, i: nil, id: nil, &block)
46
+ Ensure.without_block(block, n:, d:)
47
+
48
+ With do |base|
49
+ layer(id:) do
50
+ (n - 1).times do |time|
51
+ base.DuplicateV((time + 1) * d, parent: self)
52
+ end
53
+ end.tap do |element|
54
+ base.AdoptFirst(element)
55
+ IdentifyAsList(element.children, i)
56
+ end
57
+ end
58
+ end
59
+
60
+ def Tile(symbol = Undefined, nx: Undefined, dx: Undefined, ix: nil, ny: Undefined, dy: Undefined, iy: nil, id: nil, &block)
61
+ Ensure.with_block(block, symbol:, nx:, dx:, ny:, dy:)
62
+
63
+ symbol(id: symbol).Within(&block)
64
+ use("xlink:href": "##{symbol}").Replicate(nx:, dx:, ix:, ny:, dy:, iy:, id:)
65
+ end
66
+
67
+ def TileH(symbol = Undefined, n: Undefined, d: Undefined, i: nil, id: nil, &block)
68
+ Ensure.with_block(block, symbol:, n:, d:)
69
+
70
+ symbol(id: symbol).Within(&block)
71
+ use("xlink:href": "##{symbol}").ReplicateH(n:, d:, i:, id:)
72
+ end
73
+
74
+ def TileV(symbol = Undefined, n: Undefined, d: Undefined, i: nil, id: nil, &block)
75
+ Ensure.with_block(block, symbol:, n:, d:)
76
+
77
+ symbol(id: symbol).Within(&block)
78
+ use("xlink:href": "##{symbol}").ReplicateV(n:, d:, i:, id:)
79
+ end
80
+ end
81
+
82
+ module Ensure
83
+ extend self
84
+
85
+ ASSERTION = {
86
+ symbol: proc { |name, value| "Argument '#{name}' must be a string" unless value.is_a?(::String) },
87
+ n: proc { |name, value| "Argument '#{name}' must be a positive integer" unless value.positive? },
88
+ nx: proc { |name, value| "Argument '#{name}' must be a positive integer" unless value.positive? },
89
+ ny: proc { |name, value| "Argument '#{name}' must be a positive integer" unless value.positive? },
90
+ d: proc { |name, value| "Argument '#{name}' must be a number" unless value.is_a?(::Numeric) },
91
+ dx: proc { |name, value| "Argument '#{name}' must be a number" unless value.is_a?(::Numeric) },
92
+ dy: proc { |name, value| "Argument '#{name}' must be a number" unless value.is_a?(::Numeric) },
93
+ }.freeze
94
+
95
+ def with_block(block, **)
96
+ ArgumentError.("Block required") unless block
97
+
98
+ call(**)
99
+ end
100
+
101
+ def without_block(block, **)
102
+ ArgumentError.("Block not allowed") if block
103
+
104
+ call(**)
105
+ end
106
+
107
+ private
108
+
109
+ def call(**kwargs)
110
+ kwargs.each do |name, value|
111
+ issue = "Argument '#{name}' required" if value == Undefined
112
+
113
+ unless issue
114
+ next unless (assertion = ASSERTION[name])
115
+ next unless (issue = assertion.call(name, value))
116
+ end
117
+
118
+ ArgumentError.(issue)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Mixtures
6
+ module Save
7
+ module InstanceMethods
8
+ EXT = ".svg"
9
+
10
+ def Out(*, **) = F.out(self.(**), *)
11
+
12
+ def Out!(*, **) = F.out(self.(**), *, smart: true)
13
+
14
+ def Save(dir = ".", **) = Out(Savefile(caller_locations(1..1).first.path, dir), **)
15
+
16
+ def Save!(dir = ".", **) = Out!(Savefile(caller_locations(1..1).first.path, dir), **)
17
+
18
+ def Savefile(path, dir = ".") = ::File.expand_path(::File.join(dir, "#{::File.basename(path, ".*")}#{EXT}"), ::File.dirname(path))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end