erector 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README.txt +17 -3
  2. data/VERSION.yml +2 -2
  3. data/bin/erector +1 -1
  4. data/lib/erector.rb +22 -2
  5. data/lib/erector/after_initialize.rb +34 -0
  6. data/lib/erector/caching.rb +93 -0
  7. data/lib/erector/convenience.rb +58 -0
  8. data/lib/erector/dependencies.rb +24 -0
  9. data/lib/erector/dependency.rb +21 -0
  10. data/lib/erector/{erect.rb → erect/erect.rb} +14 -4
  11. data/lib/erector/{erected.rb → erect/erected.rb} +6 -4
  12. data/lib/erector/{indenting.rb → erect/indenting.rb} +0 -0
  13. data/lib/erector/{rhtml.treetop → erect/rhtml.treetop} +51 -11
  14. data/lib/erector/errors.rb +12 -0
  15. data/lib/erector/extensions/hash.rb +21 -0
  16. data/lib/erector/externals.rb +88 -24
  17. data/lib/erector/html.rb +352 -0
  18. data/lib/erector/inline.rb +5 -5
  19. data/lib/erector/jquery.rb +36 -0
  20. data/lib/erector/mixin.rb +3 -5
  21. data/lib/erector/needs.rb +94 -0
  22. data/lib/erector/output.rb +117 -0
  23. data/lib/erector/rails.rb +2 -2
  24. data/lib/erector/rails/extensions/action_controller.rb +5 -3
  25. data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
  26. data/lib/erector/rails/extensions/rails_widget.rb +98 -56
  27. data/lib/erector/rails/rails_form_builder.rb +8 -4
  28. data/lib/erector/rails/rails_version.rb +2 -2
  29. data/lib/erector/rails/template_handlers/ert_handler.rb +1 -1
  30. data/lib/erector/rails/template_handlers/rb_handler.rb +42 -1
  31. data/lib/erector/raw_string.rb +2 -2
  32. data/lib/erector/sass.rb +22 -0
  33. data/lib/erector/widget.rb +100 -653
  34. data/lib/erector/widgets.rb +1 -0
  35. data/lib/erector/widgets/external_renderer.rb +51 -0
  36. data/lib/erector/widgets/page.rb +45 -63
  37. data/lib/erector/widgets/table.rb +9 -1
  38. data/spec/erect/erect_rails_spec.rb +19 -17
  39. data/spec/erect/erect_spec.rb +11 -1
  40. data/spec/erect/erected_spec.rb +76 -5
  41. data/spec/erect/rhtml_parser_spec.rb +11 -1
  42. data/spec/erector/caching_spec.rb +267 -0
  43. data/spec/erector/convenience_spec.rb +258 -0
  44. data/spec/erector/dependency_spec.rb +46 -0
  45. data/spec/erector/externals_spec.rb +233 -0
  46. data/spec/erector/html_spec.rb +508 -0
  47. data/spec/erector/indentation_spec.rb +84 -24
  48. data/spec/erector/inline_spec.rb +19 -8
  49. data/spec/erector/jquery_spec.rb +35 -0
  50. data/spec/erector/mixin_spec.rb +1 -1
  51. data/spec/erector/needs_spec.rb +120 -0
  52. data/spec/erector/output_spec.rb +199 -0
  53. data/spec/erector/sample-file.txt +1 -0
  54. data/spec/erector/sass_spec.rb +33 -0
  55. data/spec/erector/widget_spec.rb +113 -932
  56. data/spec/erector/widgets/field_table_spec.rb +6 -6
  57. data/spec/erector/widgets/form_spec.rb +3 -3
  58. data/spec/erector/widgets/page_spec.rb +52 -6
  59. data/spec/erector/widgets/table_spec.rb +4 -4
  60. data/spec/spec_helper.rb +70 -29
  61. metadata +56 -19
  62. data/lib/erector/rails/extensions/rails_widget/rails_helpers.rb +0 -137
  63. data/spec/core_spec_suite.rb +0 -3
  64. data/spec/erector/external_spec.rb +0 -110
  65. data/spec/rails_spec_suite.rb +0 -3
  66. data/spec/spec.opts +0 -1
  67. data/spec/spec_suite.rb +0 -40
data/README.txt CHANGED
@@ -13,7 +13,8 @@ modular decomposition, encapsulation) in views. See the rdoc for the
13
13
  Erector::Widget class to learn how to make your own widgets, and visit the
14
14
  project site at http://erector.rubyforge.org for more documentation.
15
15
 
16
- No, seriously, we've got hella docs at http://erector.rubyforge.org
16
+ No, seriously, we've got hella docs at http://erector.rubyforge.org -- go
17
+ check it out.
17
18
 
18
19
  == SYNOPSIS
19
20
 
@@ -34,7 +35,7 @@ No, seriously, we've got hella docs at http://erector.rubyforge.org
34
35
  end
35
36
  end
36
37
 
37
- Hello.new(:target => 'world').to_s
38
+ Hello.new(:target => 'world').to_html
38
39
  => "<html><head><title>Hello</title></head><body>Hello, <b class=\"big\">world</b>!</body></html>"
39
40
 
40
41
  include Erector::Mixin
@@ -62,6 +63,17 @@ To install as a Rails plugin:
62
63
  When installing this way, erector is automatically available to your Rails code
63
64
  (no require directive is needed).
64
65
 
66
+ == TESTS
67
+
68
+ Three spec rake tasks are provided: spec:core (core functionality),
69
+ spec:erect (the erector command line tool), and spec:rails (rails integration).
70
+ You do not need to have Rails installed to run the latter two; they will clone
71
+ the rails git repository and set it up for testing automatically. You can test
72
+ against a different version of Rails by changing the constants in
73
+ lib/erector/rails/rails_version.rb
74
+
75
+ 'rake spec' will run the complete set of specs.
76
+
65
77
  == CREDITS:
66
78
 
67
79
  Core Team:
@@ -75,12 +87,14 @@ Special Thanks To:
75
87
  * John Firebaugh
76
88
  * Nathan Sobo
77
89
  * Nick Kallen
90
+ * Alon Salant
91
+ * Andy Peterson
78
92
 
79
93
  == LICENSE:
80
94
 
81
95
  (The MIT License)
82
96
 
83
- Copyright (c) 2007-2009 Pivotal Labs
97
+ Copyright (c) 2007-2010 Pivotal Labs and the Erector Project
84
98
 
85
99
  Permission is hereby granted, free of charge, to any person obtaining
86
100
  a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 7
4
- :patch: 2
3
+ :minor: 8
4
+ :patch: 0
@@ -7,7 +7,7 @@ rescue LoadError
7
7
  require 'erector'
8
8
  end
9
9
 
10
- require 'erector/erect'
10
+ require 'erector/erect/erect'
11
11
 
12
12
  unless Erector::Erect.new(ARGV).run
13
13
  exit 1
@@ -1,11 +1,31 @@
1
+ module Erector
2
+ end
3
+
1
4
  require "cgi"
2
5
  require "yaml"
3
- require "active_support/inflector"
4
- require "active_support/inflections"
6
+ begin
7
+ require "sass"
8
+ rescue LoadError => e
9
+ # oh well, no Sass
10
+ end
11
+
12
+ require "erector/errors"
5
13
  require "erector/extensions/object"
14
+ require "erector/extensions/hash"
6
15
  require "erector/raw_string"
16
+ require "erector/dependencies"
17
+ require "erector/dependency"
7
18
  require "erector/externals"
19
+ require "erector/output"
20
+ require "erector/caching"
21
+ require "erector/after_initialize"
22
+ require "erector/needs"
23
+ require "erector/html"
24
+ require "erector/convenience"
25
+ require "erector/jquery"
26
+ require "erector/sass"
8
27
  require "erector/widget"
28
+
9
29
  require "erector/inline"
10
30
  require "erector/unicode"
11
31
  require "erector/widgets"
@@ -0,0 +1,34 @@
1
+ module Erector
2
+ module AfterInitialize
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def after_initialize(instance=nil, &blk)
9
+ if blk
10
+ after_initialize_parts << blk
11
+ elsif instance
12
+ if superclass.respond_to?(:after_initialize)
13
+ superclass.after_initialize instance
14
+ end
15
+ after_initialize_parts.each do |part|
16
+ instance.instance_eval &part
17
+ end
18
+ else
19
+ raise ArgumentError, "You must provide either an instance or a block"
20
+ end
21
+ end
22
+
23
+ protected
24
+ def after_initialize_parts
25
+ @after_initialize_parts ||= []
26
+ end
27
+ end
28
+
29
+ def initialize(*args, &blk)
30
+ super
31
+ self.class.after_initialize self
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,93 @@
1
+ module Erector
2
+ class Cache
3
+ def initialize
4
+ @stores = {}
5
+ end
6
+
7
+ def store_for(klass)
8
+ @stores[klass] ||= Hash.new {|h,k| h[k] = {}}
9
+ end
10
+
11
+ def []=(*args)
12
+ value = args.pop
13
+ klass = args.shift
14
+ params = args.first.is_a?(Hash) ? args.first : {}
15
+ content_method = args.last.is_a?(Symbol) ? args.last : nil
16
+ store_for(klass)[key(params)][content_method] = value
17
+ end
18
+
19
+ def [](klass, params = {}, content_method = nil)
20
+ store_for(klass)[key(params)][content_method]
21
+ end
22
+
23
+ def delete(klass, params = {})
24
+ store_for(klass).delete(key(params))
25
+ end
26
+
27
+ def delete_all(klass)
28
+ @stores.delete(klass)
29
+ end
30
+
31
+ # convert hash-key to array-key for compatibility with 1.8.6
32
+ def key(params)
33
+ params.to_a
34
+ end
35
+ end
36
+
37
+ module Caching
38
+ def self.included(base)
39
+ base.extend ClassMethods
40
+ end
41
+
42
+ module ClassMethods
43
+ def cacheable(value = true)
44
+ @cachable = value
45
+ end
46
+
47
+ def cachable(value = true)
48
+ @cachable = value
49
+ end
50
+
51
+ def cachable?
52
+ if @cachable.nil?
53
+ superclass.respond_to?(:cachable?) && superclass.cachable?
54
+ else
55
+ @cachable
56
+ end
57
+ end
58
+
59
+ def cache
60
+ @@cache ||= nil
61
+ end
62
+
63
+ def cache=(c)
64
+ @@cache = c
65
+ end
66
+ end
67
+
68
+ def cache
69
+ self.class.cache
70
+ end
71
+
72
+ def should_cache?
73
+ cache && block.nil? && self.class.cachable?
74
+ end
75
+
76
+ def _render(options = {})
77
+ if should_cache?
78
+ cache[self.class, assigns, options[:content_method_name]] ||= super
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ def _render_via(parent, options = {})
85
+ if should_cache?
86
+ parent.output << cache[self.class, assigns, options[:content_method_name]] ||= parent.capture { super }
87
+ parent.output.widgets << self.class # todo: test!!!
88
+ else
89
+ super
90
+ end
91
+ end
92
+ end
93
+ end
@@ -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,21 @@
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
+ end
21
+ end
@@ -1,14 +1,16 @@
1
1
  require "optparse"
2
2
  require "rake"
3
- require "erector/erected" # pull this out so we don't recreate the grammar every time
3
+ require "erector/erect/erected" # pull this out so we don't recreate the grammar every time
4
4
 
5
5
  module Erector
6
6
  class Erect
7
- attr_reader :files, :verbose, :mode, :output_dir
7
+ attr_reader :files, :verbose, :mode, :output_dir, :superklass, :method_name
8
8
  def initialize(args)
9
9
  @verbose = true
10
10
  @mode = :to_erector
11
11
  @output_dir = nil
12
+ @superklass = 'Erector::Widget'
13
+ @method_name = 'content'
12
14
 
13
15
  opts = OptionParser.new do |opts|
14
16
  opts.banner = "Usage: erector [options] [file|dir]*"
@@ -30,6 +32,14 @@ module Erector
30
32
  @mode = :to_html
31
33
  end
32
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
+
33
43
  opts.on("-o", "--output-dir DIRECTORY", "Output files to DIRECTORY (default: output files go next to input files)") do |dir|
34
44
  @output_dir = dir
35
45
  end
@@ -87,7 +97,7 @@ module Erector
87
97
  files.each do |file|
88
98
  say "Erecting #{file}... "
89
99
  begin
90
- e = Erector::Erected.new(file)
100
+ e = Erector::Erected.new(file, @superklass, @method_name)
91
101
  e.convert
92
102
  say " --> #{e.filename}\n"
93
103
  rescue => e
@@ -116,7 +126,7 @@ module Erector
116
126
  FileUtils.mkdir_p(dir)
117
127
  output_file = "#{dir}/#{filename}.html"
118
128
  File.open(output_file, "w") do |f|
119
- f.puts widget.to_s
129
+ f.puts widget.to_html
120
130
  end
121
131
  say " --> #{output_file}\n"
122
132
  else
@@ -7,8 +7,10 @@ Treetop.load("#{dir}/rhtml.treetop")
7
7
  module Erector
8
8
  class Erected
9
9
 
10
- def initialize(in_file)
10
+ def initialize(in_file, superklass = 'Erector::Widget', method_name = 'content')
11
11
  @in_file = in_file
12
+ @superklass = superklass
13
+ @method_name = method_name
12
14
  end
13
15
 
14
16
  def filename
@@ -20,9 +22,9 @@ module Erector
20
22
  parent = File.dirname(@in_file)
21
23
  grandparent = File.dirname(parent)
22
24
  if File.basename(grandparent) == "views"
23
- ["Views::" + classize(File.basename(parent)) + "::" + base, "Erector::Widget"]
25
+ ["Views::" + classize(File.basename(parent)) + "::" + base, @superklass]
24
26
  else
25
- [base, "Erector::Widget"]
27
+ [base, @superklass]
26
28
  end
27
29
  end
28
30
 
@@ -47,7 +49,7 @@ module Erector
47
49
  else
48
50
  File.open(filename, "w") do |f|
49
51
  f.puts("class #{classname} < #{parent_class}")
50
- f.puts(" def content")
52
+ f.puts(" def #{@method_name}")
51
53
  f.puts(parsed.set_indent(2).convert)
52
54
  f.puts(" end")
53
55
  f.puts("end")
@@ -47,7 +47,7 @@ grammar Rhtml
47
47
  rule scriptlet
48
48
  '<%' space code space '%>' <Erector::Indenting> {
49
49
  def convert
50
- text = code.text_value.strip
50
+ text = code.text_value_removing_trims.strip
51
51
  if text =~ /\bdo( |.*|)?$/
52
52
  line_in text
53
53
  elsif text == "end"
@@ -62,7 +62,7 @@ grammar Rhtml
62
62
  rule printlet
63
63
  '<%=' space code space '%>' <Erector::Indenting> {
64
64
  def convert
65
- line "rawtext #{code.convert}"
65
+ line "rawtext #{code.convert_removing_trims}"
66
66
  end
67
67
  }
68
68
  end
@@ -70,13 +70,21 @@ grammar Rhtml
70
70
  rule hprintlet
71
71
  '<%=' space 'h' ' '+ code space '%>' <Erector::Indenting> {
72
72
  def convert
73
- line "text #{code.convert}"
73
+ line "text #{code.convert_removing_trims}"
74
74
  end
75
75
  }
76
76
  end
77
77
 
78
78
  rule code
79
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
+
80
88
  def convert
81
89
  code = text_value.strip
82
90
  # matches a word, followed by either a word, a string, or a symbol
@@ -158,13 +166,19 @@ grammar Rhtml
158
166
 
159
167
  rule attributes
160
168
  first:attribute rest:attributes* {
161
- def convert
162
- " " + first.convert +
163
- if rest.empty?
164
- ""
165
- else
166
- ",#{rest.elements.first.convert}" # this is hacky -- is there a better way?
167
- end
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
168
182
  end
169
183
  }
170
184
  end
@@ -173,7 +187,7 @@ grammar Rhtml
173
187
  space n:(tagname) space '=' space v:quoted space {
174
188
  def convert
175
189
  attr_name = (n.text_value =~ /[-:]/) ? "'#{n.text_value}'" : ":#{n.text_value}"
176
- "#{attr_name} => '#{v.value.html_unescape.escape_single_quotes}'"
190
+ "#{attr_name} => #{v.convert}"
177
191
  end
178
192
  }
179
193
  end
@@ -183,6 +197,32 @@ grammar Rhtml
183
197
  def value
184
198
  val.text_value
185
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
186
226
  }
187
227
  end
188
228