erector 0.7.2 → 0.8.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.
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