honkster-erector 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/README.txt +116 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/erector +14 -0
  4. data/lib/erector.rb +34 -0
  5. data/lib/erector/abstract_widget.rb +172 -0
  6. data/lib/erector/after_initialize.rb +34 -0
  7. data/lib/erector/caching.rb +93 -0
  8. data/lib/erector/convenience.rb +58 -0
  9. data/lib/erector/dependencies.rb +24 -0
  10. data/lib/erector/dependency.rb +30 -0
  11. data/lib/erector/erect/erect.rb +160 -0
  12. data/lib/erector/erect/erected.rb +75 -0
  13. data/lib/erector/erect/indenting.rb +36 -0
  14. data/lib/erector/erect/rhtml.treetop +233 -0
  15. data/lib/erector/errors.rb +12 -0
  16. data/lib/erector/extensions/hash.rb +21 -0
  17. data/lib/erector/extensions/object.rb +18 -0
  18. data/lib/erector/externals.rb +97 -0
  19. data/lib/erector/html.rb +352 -0
  20. data/lib/erector/inline.rb +37 -0
  21. data/lib/erector/jquery.rb +36 -0
  22. data/lib/erector/mixin.rb +12 -0
  23. data/lib/erector/needs.rb +94 -0
  24. data/lib/erector/output.rb +117 -0
  25. data/lib/erector/rails.rb +27 -0
  26. data/lib/erector/rails/extensions/action_controller.rb +16 -0
  27. data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
  28. data/lib/erector/rails/extensions/rails_widget.rb +126 -0
  29. data/lib/erector/rails/rails_form_builder.rb +24 -0
  30. data/lib/erector/rails/rails_version.rb +6 -0
  31. data/lib/erector/rails/template_handlers/ert_handler.rb +32 -0
  32. data/lib/erector/rails/template_handlers/rb_handler.rb +52 -0
  33. data/lib/erector/raw_string.rb +8 -0
  34. data/lib/erector/sass.rb +22 -0
  35. data/lib/erector/unicode.rb +18185 -0
  36. data/lib/erector/unicode_builder.rb +67 -0
  37. data/lib/erector/version.rb +12 -0
  38. data/lib/erector/widget.rb +54 -0
  39. data/lib/erector/widgets.rb +6 -0
  40. data/lib/erector/widgets/environment_badge.rb +29 -0
  41. data/lib/erector/widgets/external_renderer.rb +51 -0
  42. data/lib/erector/widgets/field_table.rb +110 -0
  43. data/lib/erector/widgets/form.rb +30 -0
  44. data/lib/erector/widgets/page.rb +165 -0
  45. data/lib/erector/widgets/table.rb +104 -0
  46. data/rails/init.rb +4 -0
  47. data/spec/erect/erect_rails_spec.rb +114 -0
  48. data/spec/erect/erect_spec.rb +175 -0
  49. data/spec/erect/erected_spec.rb +164 -0
  50. data/spec/erect/rhtml_parser_spec.rb +361 -0
  51. data/spec/erector/caching_spec.rb +269 -0
  52. data/spec/erector/convenience_spec.rb +259 -0
  53. data/spec/erector/dependency_spec.rb +67 -0
  54. data/spec/erector/externals_spec.rb +236 -0
  55. data/spec/erector/html_spec.rb +509 -0
  56. data/spec/erector/indentation_spec.rb +211 -0
  57. data/spec/erector/inline_spec.rb +94 -0
  58. data/spec/erector/jquery_spec.rb +35 -0
  59. data/spec/erector/mixin_spec.rb +65 -0
  60. data/spec/erector/needs_spec.rb +120 -0
  61. data/spec/erector/output_spec.rb +199 -0
  62. data/spec/erector/sample-file.txt +1 -0
  63. data/spec/erector/sass_spec.rb +33 -0
  64. data/spec/erector/unicode_builder_spec.rb +75 -0
  65. data/spec/erector/widget_spec.rb +250 -0
  66. data/spec/erector/widgets/field_table_spec.rb +133 -0
  67. data/spec/erector/widgets/form_spec.rb +31 -0
  68. data/spec/erector/widgets/page_spec.rb +85 -0
  69. data/spec/erector/widgets/table_spec.rb +99 -0
  70. data/spec/spec_helper.rb +95 -0
  71. 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