SAVAGE 0.1.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.
- data/LICENSE.txt +13 -0
- data/README.md +69 -0
- data/bin/savage +12 -0
- data/lib/lexer.rb +100 -0
- data/lib/parser.rb +82 -0
- data/lib/parsers/circle.rb +28 -0
- data/lib/parsers/element.rb +20 -0
- data/lib/parsers/ellipse.rb +29 -0
- data/lib/parsers/line.rb +31 -0
- data/lib/parsers/path.rb +34 -0
- data/lib/parsers/rect.rb +29 -0
- data/lib/parsers/round_rect.rb +31 -0
- data/lib/parsers/sass.rb +27 -0
- data/lib/parsers/svg.rb +41 -0
- data/lib/parsers/text.rb +27 -0
- metadata +77 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2013 Mason Simon
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# SAVAGE
|
2
|
+
|
3
|
+
## Intro
|
4
|
+
|
5
|
+
Savage is a graphics language layered on top of SVG, in the spirit of Sass and Haml.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
1. clone repo
|
10
|
+
1. bundle
|
11
|
+
1. bundle exec ruby compiler.rb < test.svge > test.svg
|
12
|
+
1. View test.svg in a web browser or other SVG renderer.
|
13
|
+
|
14
|
+
## Example
|
15
|
+
|
16
|
+
// Set dimensions of your graphic (width x height).
|
17
|
+
400 x 300
|
18
|
+
|
19
|
+
// Draw a circle of radius 100 at (200,150).
|
20
|
+
(100) @ 200,150
|
21
|
+
|
22
|
+
// Draw an ellipse with x-radius 50 and y-radius 40 at 10,10.
|
23
|
+
(50,40) @ 10,10
|
24
|
+
|
25
|
+
// Draw a square of side length 10.
|
26
|
+
[10] @ 100,200
|
27
|
+
|
28
|
+
// Draw a rectangle of width 50, height 30.
|
29
|
+
[50,30] @ 300,0
|
30
|
+
|
31
|
+
// Draw a line from (20,30) to (40,50), then to (50,60).
|
32
|
+
-
|
33
|
+
@ 20,30
|
34
|
+
@ 40,50
|
35
|
+
@ 50,60
|
36
|
+
|
37
|
+
// Draw an arrow.
|
38
|
+
~
|
39
|
+
M 100,225
|
40
|
+
L 100,115
|
41
|
+
L 130,115
|
42
|
+
L 70,15
|
43
|
+
L 10,115
|
44
|
+
L 40,115
|
45
|
+
L 40,225
|
46
|
+
z
|
47
|
+
|
48
|
+
// Style using Sass.
|
49
|
+
:sass
|
50
|
+
.dot
|
51
|
+
fill: red
|
52
|
+
|
53
|
+
#point
|
54
|
+
fill: blue
|
55
|
+
|
56
|
+
[15].dot @ 300,250
|
57
|
+
|
58
|
+
[15].dot @ 350,270
|
59
|
+
|
60
|
+
(15)#point @ 360, 230
|
61
|
+
|
62
|
+
## License
|
63
|
+
|
64
|
+
Apache 2.0. See LICENSE.txt.
|
65
|
+
|
66
|
+
## Credits
|
67
|
+
|
68
|
+
Marc-André Cournoyer for writing [http://createyourproglang.com/](http://createyourproglang.com/), which got this project off the ground. The authors of [Haml](http://haml.info/), [Sass](http://sass-lang.com/), and [Coffeescript](http://coffeescript.org/), for demonstrating that we need not be content with the languages W3C gives us.
|
69
|
+
|
data/bin/savage
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
require 'lexer'
|
5
|
+
require 'parser'
|
6
|
+
|
7
|
+
tokens = Lexer.new.tokenize(ARGF.read)
|
8
|
+
parsed = Parser.new(tokens)
|
9
|
+
|
10
|
+
puts '<?xml version="1.0" standalone="yes"?>'
|
11
|
+
puts parsed.translate
|
12
|
+
|
data/lib/lexer.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
class Lexer
|
2
|
+
def tokenize(code)
|
3
|
+
code.chomp!
|
4
|
+
|
5
|
+
tokens = []
|
6
|
+
|
7
|
+
# TODO: Switch to a state machine framework (state_machine, Ragel);
|
8
|
+
# this is getting gross.
|
9
|
+
|
10
|
+
# Advance 1 char at a time until finding something parsable.
|
11
|
+
i = 0
|
12
|
+
|
13
|
+
indented = false
|
14
|
+
|
15
|
+
while i < code.size
|
16
|
+
chunk = code[i..-1]
|
17
|
+
|
18
|
+
if comment = chunk.match(/\A\/\/.*?\n/)
|
19
|
+
i += comment.to_s.size - 1
|
20
|
+
elsif dimension = chunk.match(/\A(\d+)\sx\s(\d+)/)
|
21
|
+
w, h = dimension.captures.map(&:to_i)
|
22
|
+
tokens << [:DIMENSION, [w, h]]
|
23
|
+
i += dimension.to_s.size
|
24
|
+
elsif sass = chunk.match(/\A\:sass\n((?:(?:[ ]+.*?\n)|\n)+)/)
|
25
|
+
src = sass[1]
|
26
|
+
indented = true
|
27
|
+
tokens << [:SASS, src]
|
28
|
+
i += sass.to_s.size - 1
|
29
|
+
elsif circle = chunk.match(/\A\((\d+)\)/)
|
30
|
+
r = circle[1].to_i
|
31
|
+
tokens << [:CIRCLE, r]
|
32
|
+
i += circle.to_s.size
|
33
|
+
elsif ellipse = chunk.match(/\A\((\d+),\s*(\d+)\)/)
|
34
|
+
rx, ry = ellipse.captures.map(&:to_i)
|
35
|
+
tokens << [:ELLIPSE, [rx, ry]]
|
36
|
+
i += ellipse.to_s.size
|
37
|
+
elsif round_rect = chunk.match(/\A\[(\d+),\s*(\d+),\s*(\d+),\s*(\d+)\)/)
|
38
|
+
w, h, rx, ry = round_rect.captures.map(&:to_i)
|
39
|
+
tokens << [:ROUND_RECT, [w, h, rx, ry]]
|
40
|
+
i += round_rect.to_s.size
|
41
|
+
elsif rect = chunk.match(/\A\[(\d+),\s*(\d+)\]/)
|
42
|
+
w, h = rect.captures.map(&:to_i)
|
43
|
+
tokens << [:RECT, [w, h]]
|
44
|
+
i += rect.to_s.size
|
45
|
+
elsif square = chunk.match(/\A\[(\d+)\]/)
|
46
|
+
s = square[1].to_i
|
47
|
+
tokens << [:RECT, [s, s]]
|
48
|
+
i += square.to_s.size
|
49
|
+
elsif text = chunk.match(/\A\"([^\"]+)\"/)
|
50
|
+
t = text[1]
|
51
|
+
tokens << [:TEXT, t]
|
52
|
+
i += text.to_s.size
|
53
|
+
elsif line = chunk.match(/\A\-/)
|
54
|
+
tokens << [:LINE]
|
55
|
+
i += line.to_s.size
|
56
|
+
elsif path = chunk.match(/\A\~/)
|
57
|
+
tokens << [:PATH]
|
58
|
+
i += path.to_s.size
|
59
|
+
elsif pathdef = chunk.match(/\A([MmZzLlHhVvCcSsQqTtAa])\s?((?:[\d\.\-,](?: )?)+)?/)
|
60
|
+
command, params = pathdef.captures
|
61
|
+
tokens << [:PATHDEF, [command, params]]
|
62
|
+
i += pathdef.to_s.size
|
63
|
+
elsif id = chunk.match(/\A#([a-zA-Z0-9_\-]+)/)
|
64
|
+
name = id[1]
|
65
|
+
tokens << [:ID, name]
|
66
|
+
i += id.to_s.size
|
67
|
+
elsif classname = chunk.match(/\A\.([a-zA-Z0-9_\-]+)/)
|
68
|
+
name = classname[1]
|
69
|
+
tokens << [:CLASS, name]
|
70
|
+
i += classname.to_s.size
|
71
|
+
elsif coords = chunk.match(/\A@\s*(\d+)\s*,\s*(\d+)/)
|
72
|
+
x, y = coords.captures
|
73
|
+
tokens << [:LOCATION, [x.to_i, y.to_i]]
|
74
|
+
i += coords.to_s.size
|
75
|
+
elsif chunk.match(/\A\n\n/)
|
76
|
+
tokens << [:NEWLINE]
|
77
|
+
i += 1
|
78
|
+
elsif indent = chunk.match(/\A\n\s+/)
|
79
|
+
indented = true
|
80
|
+
tokens << [:INDENT]
|
81
|
+
i += indent.to_s.size
|
82
|
+
elsif indented && chunk.match(/\A\n[^\s]/)
|
83
|
+
indented = false
|
84
|
+
tokens << [:DEDENT]
|
85
|
+
i += 1
|
86
|
+
elsif indented && (attribute = chunk.match(/\A(\w+):\s*([^\s]+?)(?:\n|\Z)/))
|
87
|
+
key, value = attribute.captures
|
88
|
+
tokens << [:ATTRIBUTE, [key, value]]
|
89
|
+
i += attribute.to_s.size - 1 # Don't advance beyond newline.
|
90
|
+
else
|
91
|
+
i += 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
tokens << [:EOF]
|
95
|
+
|
96
|
+
tokens
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'parsers/svg'
|
2
|
+
require_relative 'parsers/sass'
|
3
|
+
require_relative 'parsers/circle'
|
4
|
+
require_relative 'parsers/ellipse'
|
5
|
+
require_relative 'parsers/round_rect'
|
6
|
+
require_relative 'parsers/rect'
|
7
|
+
require_relative 'parsers/text'
|
8
|
+
require_relative 'parsers/line'
|
9
|
+
require_relative 'parsers/path'
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
attr_reader :els
|
13
|
+
|
14
|
+
def initialize(tokens)
|
15
|
+
@els = Svg.new
|
16
|
+
curr = @els
|
17
|
+
|
18
|
+
# TODO: Switch to a state machine framework (state_machine, Ragel);
|
19
|
+
# this is getting gross.
|
20
|
+
tokens.each do |token|
|
21
|
+
if curr == @els
|
22
|
+
if token.first == :DIMENSION
|
23
|
+
el = Svg.new
|
24
|
+
el << token
|
25
|
+
@els = el
|
26
|
+
curr = el
|
27
|
+
elsif token.first == :SASS
|
28
|
+
el = SassEl.new
|
29
|
+
el << token
|
30
|
+
curr << el
|
31
|
+
curr = el
|
32
|
+
elsif token.first == :CIRCLE
|
33
|
+
el = Circle.new
|
34
|
+
el << token
|
35
|
+
curr << el
|
36
|
+
curr = el
|
37
|
+
elsif token.first == :ELLIPSE
|
38
|
+
el = Ellipse.new
|
39
|
+
el << token
|
40
|
+
curr << el
|
41
|
+
curr = el
|
42
|
+
elsif token.first == :ROUND_RECT
|
43
|
+
el = RoundRect.new
|
44
|
+
el << token
|
45
|
+
curr << el
|
46
|
+
curr = el
|
47
|
+
elsif token.first == :RECT
|
48
|
+
el = Rect.new
|
49
|
+
el << token
|
50
|
+
curr << el
|
51
|
+
curr = el
|
52
|
+
elsif token.first == :TEXT
|
53
|
+
el = Text.new
|
54
|
+
el << token
|
55
|
+
curr << el
|
56
|
+
curr = el
|
57
|
+
elsif token.first == :LINE
|
58
|
+
el = Line.new
|
59
|
+
el << token
|
60
|
+
curr << el
|
61
|
+
curr = el
|
62
|
+
elsif token.first == :PATH
|
63
|
+
el = Path.new
|
64
|
+
el << token
|
65
|
+
curr << el
|
66
|
+
curr = el
|
67
|
+
end
|
68
|
+
else
|
69
|
+
if [:EOF, :DEDENT, :NEWLINE].include? token.first
|
70
|
+
curr = @els
|
71
|
+
else
|
72
|
+
curr << token
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def translate
|
79
|
+
@els.translate
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Circle < Element
|
4
|
+
def <<(token)
|
5
|
+
if @tokens.empty?
|
6
|
+
raise "Invalid starting token: #{token}" unless token.first == :CIRCLE
|
7
|
+
radius = token.last
|
8
|
+
@attributes['r'] = radius
|
9
|
+
end
|
10
|
+
|
11
|
+
if token.first == :LOCATION
|
12
|
+
x, y = token.last
|
13
|
+
@attributes['cx'] = x
|
14
|
+
@attributes['cy'] = y
|
15
|
+
elsif token.first == :ATTRIBUTE
|
16
|
+
k, v = token.last
|
17
|
+
@attributes[k] = v
|
18
|
+
end
|
19
|
+
|
20
|
+
super(token)
|
21
|
+
end
|
22
|
+
|
23
|
+
def translate
|
24
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
25
|
+
"<circle #{attrs} />"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Element
|
2
|
+
def initialize
|
3
|
+
@tokens = []
|
4
|
+
@attributes = {}
|
5
|
+
@children = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def <<(token)
|
9
|
+
if token.is_a? Array
|
10
|
+
if token.first == :ID
|
11
|
+
@attributes['id'] = token.last
|
12
|
+
elsif token.first == :CLASS
|
13
|
+
@attributes['class'] = token.last
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@tokens << token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Ellipse < Element
|
4
|
+
def <<(token)
|
5
|
+
if @tokens.empty?
|
6
|
+
raise "Invalid starting token: #{token}" unless token.first == :ELLIPSE
|
7
|
+
rx, ry = token.last
|
8
|
+
@attributes['rx'] = rx
|
9
|
+
@attributes['ry'] = ry
|
10
|
+
end
|
11
|
+
|
12
|
+
if token.first == :LOCATION
|
13
|
+
x, y = token.last
|
14
|
+
@attributes['cx'] = x
|
15
|
+
@attributes['cy'] = y
|
16
|
+
elsif token.first == :ATTRIBUTE
|
17
|
+
k, v = token.last
|
18
|
+
@attributes[k] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
super(token)
|
22
|
+
end
|
23
|
+
|
24
|
+
def translate
|
25
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
26
|
+
"<ellipse #{attrs} />"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/lib/parsers/line.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Line < Element
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@points = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(token)
|
10
|
+
if @tokens.empty?
|
11
|
+
raise "Invalid starting token: #{token}" unless token.first == :LINE
|
12
|
+
end
|
13
|
+
|
14
|
+
if token.first == :LOCATION
|
15
|
+
x, y = token.last
|
16
|
+
@points << [x, y]
|
17
|
+
elsif token.first == :ATTRIBUTE
|
18
|
+
k, v = token.last
|
19
|
+
@attributes[k] = v
|
20
|
+
end
|
21
|
+
|
22
|
+
super(token)
|
23
|
+
end
|
24
|
+
|
25
|
+
def translate
|
26
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
27
|
+
points = @points.map { |coords| coords.join(',') }.join(' ')
|
28
|
+
"<polyline points='#{points}' #{attrs} />"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/lib/parsers/path.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Path < Element
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@pathdefs = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(token)
|
10
|
+
if @tokens.empty?
|
11
|
+
raise "Invalid starting token: #{token}" unless token.first == :PATH
|
12
|
+
end
|
13
|
+
|
14
|
+
if token.first == :LOCATION
|
15
|
+
x, y = token.last
|
16
|
+
@points << [x, y]
|
17
|
+
elsif token.first == :ATTRIBUTE
|
18
|
+
k, v = token.last
|
19
|
+
@attributes[k] = v
|
20
|
+
elsif token.first == :PATHDEF
|
21
|
+
command, params = token.last
|
22
|
+
@pathdefs << [command, params]
|
23
|
+
end
|
24
|
+
|
25
|
+
super(token)
|
26
|
+
end
|
27
|
+
|
28
|
+
def translate
|
29
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
30
|
+
definition = @pathdefs.map { |command, params| "#{command} #{params}" }.join(' ')
|
31
|
+
"<path d='#{definition}' #{attrs} />"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/lib/parsers/rect.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Rect < Element
|
4
|
+
def <<(token)
|
5
|
+
if @tokens.empty?
|
6
|
+
raise "Invalid starting token: #{token}" unless token.first == :RECT
|
7
|
+
w, h = token.last
|
8
|
+
@attributes['width'] = w
|
9
|
+
@attributes['height'] = h
|
10
|
+
end
|
11
|
+
|
12
|
+
if token.first == :LOCATION
|
13
|
+
x, y = token.last
|
14
|
+
@attributes['x'] = x
|
15
|
+
@attributes['y'] = y
|
16
|
+
elsif token.first == :ATTRIBUTE
|
17
|
+
k, v = token.last
|
18
|
+
@attributes[k] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
super(token)
|
22
|
+
end
|
23
|
+
|
24
|
+
def translate
|
25
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
26
|
+
"<rect #{attrs} />"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class RoundRect < Element
|
4
|
+
def <<(token)
|
5
|
+
if @tokens.empty?
|
6
|
+
raise "Invalid starting token: #{token}" unless token.first == :ROUND_RECT
|
7
|
+
w, h, rx, ry = token.last
|
8
|
+
@attributes['width'] = w
|
9
|
+
@attributes['height'] = h
|
10
|
+
@attributes['rx'] = rx
|
11
|
+
@attributes['ry'] = ry
|
12
|
+
end
|
13
|
+
|
14
|
+
if token.first == :LOCATION
|
15
|
+
x, y = token.last
|
16
|
+
@attributes['x'] = x
|
17
|
+
@attributes['y'] = y
|
18
|
+
elsif token.first == :ATTRIBUTE
|
19
|
+
k, v = token.last
|
20
|
+
@attributes[k] = v
|
21
|
+
end
|
22
|
+
|
23
|
+
super(token)
|
24
|
+
end
|
25
|
+
|
26
|
+
def translate
|
27
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
28
|
+
"<rect #{attrs} />"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/lib/parsers/sass.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
require 'sass'
|
3
|
+
|
4
|
+
class SassEl < Element
|
5
|
+
def <<(token)
|
6
|
+
if @tokens.empty?
|
7
|
+
raise "Invalid starting token: #{token}" unless token.first == :SASS
|
8
|
+
|
9
|
+
# Dedent the text.
|
10
|
+
lines = token.last.split("\n")
|
11
|
+
indent = lines.first.match(/\A(\s+)/)[1].size
|
12
|
+
@sass = lines.map { |l| l[indent..-1] }.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
super(token)
|
16
|
+
end
|
17
|
+
|
18
|
+
def translate
|
19
|
+
css = Sass::Engine.new(@sass).render
|
20
|
+
"""<style type='text/css' media='screen'>
|
21
|
+
<![CDATA[
|
22
|
+
#{css}
|
23
|
+
]]>
|
24
|
+
</style>"""
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/parsers/svg.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Svg < Element
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@attributes = {
|
7
|
+
'width' => 100,
|
8
|
+
'height' => 100,
|
9
|
+
'version' => '1.1',
|
10
|
+
'xmlns' => 'http://www.w3.org/2000/svg',
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(token)
|
15
|
+
if @tokens.empty?
|
16
|
+
raise "Invalid starting token: #{token}" unless token.first == :DIMENSION
|
17
|
+
w, h = token.last
|
18
|
+
@attributes['width'] = w
|
19
|
+
@attributes['height'] = h
|
20
|
+
end
|
21
|
+
|
22
|
+
if token.is_a? Array
|
23
|
+
if token.first == :ATTRIBUTE
|
24
|
+
k, v = token.last
|
25
|
+
@attributes[k] = v
|
26
|
+
end
|
27
|
+
elsif token.class < Element
|
28
|
+
@children << token
|
29
|
+
end
|
30
|
+
|
31
|
+
super(token)
|
32
|
+
end
|
33
|
+
|
34
|
+
def translate
|
35
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
36
|
+
"""<svg #{attrs}>
|
37
|
+
#{@children.map { |el| el.translate }.join("\n")}
|
38
|
+
</svg>"""
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/parsers/text.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'element'
|
2
|
+
|
3
|
+
class Text < Element
|
4
|
+
def <<(token)
|
5
|
+
if @tokens.empty?
|
6
|
+
raise "Invalid starting token: #{token}" unless token.first == :TEXT
|
7
|
+
@text = token.last
|
8
|
+
end
|
9
|
+
|
10
|
+
if token.first == :LOCATION
|
11
|
+
x, y = token.last
|
12
|
+
@attributes['x'] = x
|
13
|
+
@attributes['y'] = y
|
14
|
+
elsif token.first == :ATTRIBUTE
|
15
|
+
k, v = token.last
|
16
|
+
@attributes[k] = v
|
17
|
+
end
|
18
|
+
|
19
|
+
super(token)
|
20
|
+
end
|
21
|
+
|
22
|
+
def translate
|
23
|
+
attrs = @attributes.map { |k,v| "#{k}='#{v}'" }.join(' ')
|
24
|
+
"<text #{attrs}>#{@text}</text>"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: SAVAGE
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mason Simon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sass
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Savage is a graphics language layered on top of SVG, in the spirit of
|
31
|
+
Sass and Haml.
|
32
|
+
email: masonsimon@gmail.com
|
33
|
+
executables:
|
34
|
+
- savage
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/lexer.rb
|
39
|
+
- lib/parser.rb
|
40
|
+
- lib/parsers/circle.rb
|
41
|
+
- lib/parsers/element.rb
|
42
|
+
- lib/parsers/ellipse.rb
|
43
|
+
- lib/parsers/line.rb
|
44
|
+
- lib/parsers/path.rb
|
45
|
+
- lib/parsers/rect.rb
|
46
|
+
- lib/parsers/round_rect.rb
|
47
|
+
- lib/parsers/sass.rb
|
48
|
+
- lib/parsers/svg.rb
|
49
|
+
- lib/parsers/text.rb
|
50
|
+
- bin/savage
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
homepage: https://bitbucket.org/masonicboom/savage
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.8.23
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Text-editor friendly graphics language that compiles to SVG.
|
77
|
+
test_files: []
|