honkster-erector 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +116 -0
- data/VERSION.yml +4 -0
- data/bin/erector +14 -0
- data/lib/erector.rb +34 -0
- data/lib/erector/abstract_widget.rb +172 -0
- data/lib/erector/after_initialize.rb +34 -0
- data/lib/erector/caching.rb +93 -0
- data/lib/erector/convenience.rb +58 -0
- data/lib/erector/dependencies.rb +24 -0
- data/lib/erector/dependency.rb +30 -0
- data/lib/erector/erect/erect.rb +160 -0
- data/lib/erector/erect/erected.rb +75 -0
- data/lib/erector/erect/indenting.rb +36 -0
- data/lib/erector/erect/rhtml.treetop +233 -0
- data/lib/erector/errors.rb +12 -0
- data/lib/erector/extensions/hash.rb +21 -0
- data/lib/erector/extensions/object.rb +18 -0
- data/lib/erector/externals.rb +97 -0
- data/lib/erector/html.rb +352 -0
- data/lib/erector/inline.rb +37 -0
- data/lib/erector/jquery.rb +36 -0
- data/lib/erector/mixin.rb +12 -0
- data/lib/erector/needs.rb +94 -0
- data/lib/erector/output.rb +117 -0
- data/lib/erector/rails.rb +27 -0
- data/lib/erector/rails/extensions/action_controller.rb +16 -0
- data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
- data/lib/erector/rails/extensions/rails_widget.rb +126 -0
- data/lib/erector/rails/rails_form_builder.rb +24 -0
- data/lib/erector/rails/rails_version.rb +6 -0
- data/lib/erector/rails/template_handlers/ert_handler.rb +32 -0
- data/lib/erector/rails/template_handlers/rb_handler.rb +52 -0
- data/lib/erector/raw_string.rb +8 -0
- data/lib/erector/sass.rb +22 -0
- data/lib/erector/unicode.rb +18185 -0
- data/lib/erector/unicode_builder.rb +67 -0
- data/lib/erector/version.rb +12 -0
- data/lib/erector/widget.rb +54 -0
- data/lib/erector/widgets.rb +6 -0
- data/lib/erector/widgets/environment_badge.rb +29 -0
- data/lib/erector/widgets/external_renderer.rb +51 -0
- data/lib/erector/widgets/field_table.rb +110 -0
- data/lib/erector/widgets/form.rb +30 -0
- data/lib/erector/widgets/page.rb +165 -0
- data/lib/erector/widgets/table.rb +104 -0
- data/rails/init.rb +4 -0
- data/spec/erect/erect_rails_spec.rb +114 -0
- data/spec/erect/erect_spec.rb +175 -0
- data/spec/erect/erected_spec.rb +164 -0
- data/spec/erect/rhtml_parser_spec.rb +361 -0
- data/spec/erector/caching_spec.rb +269 -0
- data/spec/erector/convenience_spec.rb +259 -0
- data/spec/erector/dependency_spec.rb +67 -0
- data/spec/erector/externals_spec.rb +236 -0
- data/spec/erector/html_spec.rb +509 -0
- data/spec/erector/indentation_spec.rb +211 -0
- data/spec/erector/inline_spec.rb +94 -0
- data/spec/erector/jquery_spec.rb +35 -0
- data/spec/erector/mixin_spec.rb +65 -0
- data/spec/erector/needs_spec.rb +120 -0
- data/spec/erector/output_spec.rb +199 -0
- data/spec/erector/sample-file.txt +1 -0
- data/spec/erector/sass_spec.rb +33 -0
- data/spec/erector/unicode_builder_spec.rb +75 -0
- data/spec/erector/widget_spec.rb +250 -0
- data/spec/erector/widgets/field_table_spec.rb +133 -0
- data/spec/erector/widgets/form_spec.rb +31 -0
- data/spec/erector/widgets/page_spec.rb +85 -0
- data/spec/erector/widgets/table_spec.rb +99 -0
- data/spec/spec_helper.rb +95 -0
- metadata +191 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module Erector
|
2
|
+
module Convenience
|
3
|
+
# Render (like to_html) but adding newlines and indentation.
|
4
|
+
# You may just want to call to_html(:prettyprint => true)
|
5
|
+
# so you can pass in other rendering options as well.
|
6
|
+
def to_pretty(options = {})
|
7
|
+
to_html(options.merge(:prettyprint => true))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Render (like to_html) but stripping all tags and inserting some
|
11
|
+
# appropriate formatting. Currently we format p, br, ol, ul, and li
|
12
|
+
# tags.
|
13
|
+
def to_text(options = {})
|
14
|
+
# TODO: make text output a first class rendering strategy, like HTML is now,
|
15
|
+
# so we can do things like nested lists and numbered lists
|
16
|
+
html = to_html(options.merge(:prettyprint => false))
|
17
|
+
html.gsub!(/^<p[^>]*>/m, '')
|
18
|
+
html.gsub!(/(<(ul|ol)>)?<li>/, "\n* ")
|
19
|
+
html.gsub!(/<(\/?(ul|ol|p|br))[^>]*( \/)?>/, "\n")
|
20
|
+
CGI.unescapeHTML(html.gsub(/<[^>]*>/, ''))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Emits the result of joining the elements in array with the separator.
|
24
|
+
# The array elements and separator can be Erector::Widget objects,
|
25
|
+
# which are rendered, or strings, which are html-escaped and output.
|
26
|
+
def join(array, separator)
|
27
|
+
first = true
|
28
|
+
array.each do |widget_or_text|
|
29
|
+
if !first
|
30
|
+
text separator
|
31
|
+
end
|
32
|
+
first = false
|
33
|
+
text widget_or_text
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convenience method to emit a css file link, which looks like this:
|
38
|
+
# <link href="erector.css" rel="stylesheet" type="text/css" />
|
39
|
+
# The parameter is the full contents of the href attribute, including any ".css" extension.
|
40
|
+
#
|
41
|
+
# If you want to emit raw CSS inline, use the #style method instead.
|
42
|
+
def css(href, options = {})
|
43
|
+
link({:rel => 'stylesheet', :type => 'text/css', :href => href}.merge(options))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Convenience method to emit an anchor tag whose href and text are the same,
|
47
|
+
# e.g. <a href="http://example.com">http://example.com</a>
|
48
|
+
def url(href, options = {})
|
49
|
+
a href, ({:href => href}.merge(options))
|
50
|
+
end
|
51
|
+
|
52
|
+
# makes a unique id based on the widget's class name and object id
|
53
|
+
# that you can use as the HTML id of an emitted element
|
54
|
+
def dom_id
|
55
|
+
"#{self.class.name.gsub(/:+/,"_")}_#{self.object_id}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Erector
|
2
|
+
class Dependencies < Array
|
3
|
+
def push(*new_dependencies_args)
|
4
|
+
new_dependencies = new_dependencies_args.select do |new_dependency|
|
5
|
+
!include?(new_dependency)
|
6
|
+
end
|
7
|
+
new_dependencies.each do |dep|
|
8
|
+
unless dep.is_a? Erector::Dependency
|
9
|
+
raise "expected Dependency, got #{dep.class}: #{dep.inspect}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
super(*new_dependencies)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :<<, :push
|
16
|
+
|
17
|
+
def uniq
|
18
|
+
inject(self.class.new) do |memo, item|
|
19
|
+
memo << item unless memo.any? {|memo_item| memo_item == item}
|
20
|
+
memo
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Erector
|
2
|
+
class Dependency
|
3
|
+
attr_reader :type, :text, :options
|
4
|
+
|
5
|
+
def initialize(type, text, options = {})
|
6
|
+
text = text.read if text.is_a? IO
|
7
|
+
text = self.class.interpolate(text) if options[:interpolate] # todo: test
|
8
|
+
@type, @text, @options = type, text, options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.interpolate(s)
|
12
|
+
eval("<<INTERPOLATE\n" + s + "\nINTERPOLATE").chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
(self.type == other.type and
|
17
|
+
self.text == other.text and
|
18
|
+
self.options == other.options) ? true : false
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
self == other
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash
|
26
|
+
# this is a fairly inefficient hash function but it does the trick for now
|
27
|
+
"#{type}#{text}#{options}".hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "rake"
|
3
|
+
require "erector/erect/erected" # pull this out so we don't recreate the grammar every time
|
4
|
+
|
5
|
+
module Erector
|
6
|
+
class Erect
|
7
|
+
attr_reader :files, :verbose, :mode, :output_dir, :superklass, :method_name
|
8
|
+
def initialize(args)
|
9
|
+
@verbose = true
|
10
|
+
@mode = :to_erector
|
11
|
+
@output_dir = nil
|
12
|
+
@superklass = 'Erector::Widget'
|
13
|
+
@method_name = 'content'
|
14
|
+
|
15
|
+
opts = OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: erector [options] [file|dir]*"
|
17
|
+
|
18
|
+
opts.separator "Converts from html/rhtml files to erector widgets, or from erector widgets to html files"
|
19
|
+
opts.separator ""
|
20
|
+
opts.separator "Options:"
|
21
|
+
|
22
|
+
opts.on("-q", "--quiet",
|
23
|
+
"Operate silently except in case of error") do |quiet|
|
24
|
+
@verbose = !quiet
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--to-erector", "(default) Convert from html/rhtml to erector classes") do
|
28
|
+
@mode = :to_erector
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("--to-html", "Convert from erector to html") do
|
32
|
+
@mode = :to_html
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("--superclass SUPERCLASS", "Superclass for new widget (default Erector::Widget)") do |superklass|
|
36
|
+
@superklass = superklass
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("--method METHOD", "Method containing content for widget (default 'content')") do |method_name|
|
40
|
+
@method_name = method_name
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-o", "--output-dir DIRECTORY", "Output files to DIRECTORY (default: output files go next to input files)") do |dir|
|
44
|
+
@output_dir = dir
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
48
|
+
@mode = :help
|
49
|
+
puts opts
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on_tail("-v", "--version", "Show version") do
|
54
|
+
puts Erector::VERSION
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
opts.parse!(args)
|
60
|
+
@files = args
|
61
|
+
explode_dirs
|
62
|
+
end
|
63
|
+
|
64
|
+
def say(msg)
|
65
|
+
print msg if verbose
|
66
|
+
end
|
67
|
+
|
68
|
+
#todo: unit test
|
69
|
+
def explode_dirs
|
70
|
+
exploded_files = FileList.new
|
71
|
+
files.each do |file|
|
72
|
+
if File.directory?(file)
|
73
|
+
exploded_files.add(explode(file))
|
74
|
+
else
|
75
|
+
exploded_files.add(file)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@files = exploded_files
|
79
|
+
end
|
80
|
+
|
81
|
+
def explode(dir)
|
82
|
+
case mode
|
83
|
+
when :to_erector
|
84
|
+
FileList["#{dir}/**/*.rhtml", "#{dir}/**/*.html", "#{dir}/**/*.html.erb"]
|
85
|
+
when :to_html
|
86
|
+
FileList["#{dir}/**/*.rb"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def run
|
91
|
+
@success = true
|
92
|
+
self.send(mode)
|
93
|
+
@success
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_erector
|
97
|
+
files.each do |file|
|
98
|
+
say "Erecting #{file}... "
|
99
|
+
begin
|
100
|
+
e = Erector::Erected.new(file, @superklass, @method_name)
|
101
|
+
e.convert
|
102
|
+
say " --> #{e.filename}\n"
|
103
|
+
rescue => e
|
104
|
+
puts e
|
105
|
+
puts e.backtrace.join("\n\t")
|
106
|
+
@success = false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_html
|
112
|
+
files.each do |file|
|
113
|
+
say "Erecting #{file}... "
|
114
|
+
#todo: move this into Erected with better tests for the naming methods
|
115
|
+
begin
|
116
|
+
#todo: fail if file isn't a .rb file
|
117
|
+
require file
|
118
|
+
filename = file.split('/').last.gsub(/\.rb$/, '')
|
119
|
+
widget_name = camelize(filename)
|
120
|
+
widget_class = constantize(widget_name)
|
121
|
+
|
122
|
+
if widget_class < Erector::Widget
|
123
|
+
widget = widget_class.new
|
124
|
+
#todo: skip if it's missing a no-arg constructor
|
125
|
+
dir = output_dir || File.dirname(file)
|
126
|
+
FileUtils.mkdir_p(dir)
|
127
|
+
output_file = "#{dir}/#{filename}.html"
|
128
|
+
File.open(output_file, "w") do |f|
|
129
|
+
f.puts widget.to_html
|
130
|
+
end
|
131
|
+
say " --> #{output_file}\n"
|
132
|
+
else
|
133
|
+
say " -- not a widget, skipping\n"
|
134
|
+
end
|
135
|
+
rescue => e
|
136
|
+
puts e
|
137
|
+
puts e.backtrace.join("\n\t")
|
138
|
+
@success = false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# stolen from activesuppport/lib/inflector.rb
|
144
|
+
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
145
|
+
if first_letter_in_uppercase
|
146
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
147
|
+
else
|
148
|
+
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
def constantize(camel_cased_word)
|
152
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
153
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
154
|
+
end
|
155
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
require "#{File.dirname(__FILE__)}/indenting"
|
5
|
+
Treetop.load("#{File.dirname(__FILE__)}/rhtml.treetop")
|
6
|
+
|
7
|
+
module Erector
|
8
|
+
class Erected
|
9
|
+
|
10
|
+
def initialize(in_file, superklass = 'Erector::Widget', method_name = 'content')
|
11
|
+
@in_file = in_file
|
12
|
+
@superklass = superklass
|
13
|
+
@method_name = method_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def filename
|
17
|
+
dir + basename + ".rb"
|
18
|
+
end
|
19
|
+
|
20
|
+
def classnames
|
21
|
+
base = classize(basename)
|
22
|
+
parent = File.dirname(@in_file)
|
23
|
+
grandparent = File.dirname(parent)
|
24
|
+
if File.basename(grandparent) == "views"
|
25
|
+
["Views::" + classize(File.basename(parent)) + "::" + base, @superklass]
|
26
|
+
else
|
27
|
+
[base, @superklass]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def classname
|
32
|
+
classnames[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def parent_class
|
36
|
+
classnames[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def text
|
40
|
+
File.read(@in_file)
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert
|
44
|
+
parser = RhtmlParser.new
|
45
|
+
parsed = parser.parse(File.read(@in_file))
|
46
|
+
if parsed.nil?
|
47
|
+
raise "Could not parse #{@in_file}\n" +
|
48
|
+
parser.failure_reason
|
49
|
+
else
|
50
|
+
File.open(filename, "w") do |f|
|
51
|
+
f.puts("class #{classname} < #{parent_class}")
|
52
|
+
f.puts(" def #{@method_name}")
|
53
|
+
f.puts(parsed.set_indent(2).convert)
|
54
|
+
f.puts(" end")
|
55
|
+
f.puts("end")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def basename
|
63
|
+
@in_file.split("/").last.gsub(/\..*$/, '')
|
64
|
+
end
|
65
|
+
|
66
|
+
def dir
|
67
|
+
x = File.dirname(@in_file)
|
68
|
+
return (x == ".") ? "" : "#{x}/"
|
69
|
+
end
|
70
|
+
|
71
|
+
def classize(filename)
|
72
|
+
filename.split("_").map{|part| part.capitalize}.join
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
module Erector
|
5
|
+
class Indenting < Treetop::Runtime::SyntaxNode #:nodoc:
|
6
|
+
@@indent = 0
|
7
|
+
|
8
|
+
def set_indent(x)
|
9
|
+
@@indent = x
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def indent
|
14
|
+
[0, @@indent].max
|
15
|
+
end
|
16
|
+
|
17
|
+
def indented(s)
|
18
|
+
" " * indent + s + "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def line(s)
|
22
|
+
indented(s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def line_in(s)
|
26
|
+
s = indented(s)
|
27
|
+
@@indent += 1
|
28
|
+
s
|
29
|
+
end
|
30
|
+
|
31
|
+
def line_out(s)
|
32
|
+
@@indent -= 1
|
33
|
+
indented(s)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
grammar Rhtml
|
2
|
+
|
3
|
+
rule doc
|
4
|
+
space node space x:doc? <Erector::Indenting> {
|
5
|
+
def convert
|
6
|
+
if x.empty?
|
7
|
+
node.convert
|
8
|
+
else
|
9
|
+
node.convert + x.convert
|
10
|
+
end
|
11
|
+
end
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
rule node
|
16
|
+
yield_with_name / yield / hprintlet / printlet / scriptlet / doctype / directive / self_closing_tag / imgtag / closetag / opentag / text
|
17
|
+
end
|
18
|
+
|
19
|
+
# Argh. For some reason I can't get this to work, so I split it into two rules
|
20
|
+
# rule yield
|
21
|
+
# '<%=' space 'yield' space (':' varname space)? '%>' <Erector::Indenting> {
|
22
|
+
# def convert
|
23
|
+
# var = "@content_for_" + varname.nil? ? "layout" : varname.text_value
|
24
|
+
# line "rawtext #{var} # Note: you must define #{var} elsewhere"
|
25
|
+
# end
|
26
|
+
# }
|
27
|
+
# end
|
28
|
+
|
29
|
+
rule yield_with_name
|
30
|
+
'<%=' space 'yield' space ':' varname space '%>' <Erector::Indenting> {
|
31
|
+
def convert
|
32
|
+
var = "@content_for_" + varname.text_value
|
33
|
+
line "rawtext #{var} # Note: you must define #{var} elsewhere"
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
rule yield
|
39
|
+
'<%=' space 'yield' space '%>' <Erector::Indenting> {
|
40
|
+
def convert
|
41
|
+
var = "@content_for_layout"
|
42
|
+
line "rawtext #{var} # Note: you must define #{var} elsewhere"
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
rule scriptlet
|
48
|
+
'<%' space code space '%>' <Erector::Indenting> {
|
49
|
+
def convert
|
50
|
+
text = code.text_value_removing_trims.strip
|
51
|
+
if text =~ /\bdo( |.*|)?$/
|
52
|
+
line_in text
|
53
|
+
elsif text == "end"
|
54
|
+
line_out text
|
55
|
+
else
|
56
|
+
line text
|
57
|
+
end
|
58
|
+
end
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
rule printlet
|
63
|
+
'<%=' space code space '%>' <Erector::Indenting> {
|
64
|
+
def convert
|
65
|
+
line "rawtext #{code.convert_removing_trims}"
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule hprintlet
|
71
|
+
'<%=' space 'h' ' '+ code space '%>' <Erector::Indenting> {
|
72
|
+
def convert
|
73
|
+
line "text #{code.convert_removing_trims}"
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
rule code
|
79
|
+
(('%' !'>') / [^%])* <Erector::Indenting> {
|
80
|
+
def convert_removing_trims
|
81
|
+
convert.gsub(/\s*\-\s*$/, '')
|
82
|
+
end
|
83
|
+
|
84
|
+
def text_value_removing_trims
|
85
|
+
text_value.gsub(/\s*\-\s*$/, '')
|
86
|
+
end
|
87
|
+
|
88
|
+
def convert
|
89
|
+
code = text_value.strip
|
90
|
+
# matches a word, followed by either a word, a string, or a symbol
|
91
|
+
result = code.gsub(/^(\w+) ([\w:"'].*)$/, '\1(\2)')
|
92
|
+
result
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
rule doctype
|
98
|
+
'<!DOCTYPE' [^>]* '>' <Erector::Indenting> {
|
99
|
+
def convert
|
100
|
+
line "rawtext '#{text_value}'"
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
rule directive
|
106
|
+
'<!' [^>]* '>' <Erector::Indenting> {
|
107
|
+
def convert
|
108
|
+
line "rawtext '#{text_value}'"
|
109
|
+
end
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
rule tagname
|
114
|
+
[A-Za-z0-9_:-]+
|
115
|
+
end
|
116
|
+
|
117
|
+
rule varname
|
118
|
+
[A-Za-z0-9_]+
|
119
|
+
end
|
120
|
+
|
121
|
+
rule self_closing_tag
|
122
|
+
'<' tag_name:tagname attrs:attributes? space '/>' <Erector::Indenting> {
|
123
|
+
def convert
|
124
|
+
line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}"
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
rule opentag
|
130
|
+
'<' tag_name:tagname attrs:attributes? space '>' <Erector::Indenting> {
|
131
|
+
def convert
|
132
|
+
line_in "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert} do"
|
133
|
+
end
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
rule imgtag
|
138
|
+
'<' tag_name:'img' attrs:attributes? space '>' <Erector::Indenting> {
|
139
|
+
def convert
|
140
|
+
line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}"
|
141
|
+
end
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
rule closetag
|
146
|
+
'</' tag_name:tagname '>' <Erector::Indenting> {
|
147
|
+
def convert
|
148
|
+
line_out "end"
|
149
|
+
end
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
rule text
|
154
|
+
(([<>] !(tagname / [/%!])) / [^<>])+ <Erector::Indenting> {
|
155
|
+
def convert
|
156
|
+
stripped = text_value.strip
|
157
|
+
if stripped.empty?
|
158
|
+
""
|
159
|
+
else
|
160
|
+
line "text '#{stripped.html_unescape.gsub(/\'/, "\\\\'")
|
161
|
+
}'"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
rule attributes
|
168
|
+
first:attribute rest:attributes* {
|
169
|
+
def convert(internal = false)
|
170
|
+
out = " " + first.convert +
|
171
|
+
if rest.empty?
|
172
|
+
""
|
173
|
+
else
|
174
|
+
",#{rest.elements.first.convert(true)}" # this is hacky -- is there a better way?
|
175
|
+
end
|
176
|
+
|
177
|
+
if (! internal) && out =~ /[\(\)]/ && out =~ /^(\s*)(.*?)(\s*)$/
|
178
|
+
out = "(#{$2})#{$3}"
|
179
|
+
end
|
180
|
+
|
181
|
+
out
|
182
|
+
end
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
rule attribute
|
187
|
+
space n:(tagname) space '=' space v:quoted space {
|
188
|
+
def convert
|
189
|
+
attr_name = (n.text_value =~ /[-:]/) ? "'#{n.text_value}'" : ":#{n.text_value}"
|
190
|
+
"#{attr_name} => #{v.convert}"
|
191
|
+
end
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
rule quoted
|
196
|
+
(('"' val:([^"]*) '"') / ('\'' val:([^']*) '\'')) {
|
197
|
+
def value
|
198
|
+
val.text_value
|
199
|
+
end
|
200
|
+
|
201
|
+
def convert
|
202
|
+
extract_erb(val.text_value)
|
203
|
+
end
|
204
|
+
|
205
|
+
def parenthesize_if_necessary(s)
|
206
|
+
return s if s.strip =~ /^\(.*\)$/ || s =~ /^[A-Z0-9_]*$/i
|
207
|
+
"(" + s + ")"
|
208
|
+
end
|
209
|
+
|
210
|
+
def extract_erb(s, parenthesize = true)
|
211
|
+
if s =~ /^(.*?)<%=(.*?)%>(.*?)$/
|
212
|
+
pre, code, post = $1.html_unescape.escape_single_quotes, $2, $3.html_unescape.escape_single_quotes
|
213
|
+
out = ""
|
214
|
+
out = "'#{pre}' + " unless pre.length == 0
|
215
|
+
out += parenthesize_if_necessary(code.strip)
|
216
|
+
unless post.length == 0
|
217
|
+
post = extract_erb(post, false)
|
218
|
+
out += " + #{post}"
|
219
|
+
end
|
220
|
+
out = parenthesize_if_necessary(out) if parenthesize
|
221
|
+
out
|
222
|
+
else
|
223
|
+
"'" + s.html_unescape.escape_single_quotes + "'"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
rule space
|
230
|
+
[ \n\t]*
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|