SAVAGE 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|