dolores-cfml 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/cfml.rb +8 -0
- data/lib/cfml/liquid_filters.rb +16 -0
- data/lib/cfml/parser.rb +60 -0
- data/lib/cfml/tag.rb +75 -0
- data/lib/cfml/tags/checkbox.rb +31 -0
- data/lib/cfml/tags/checkboxes.rb +28 -0
- data/lib/cfml/tags/group.rb +14 -0
- data/lib/cfml/tags/hidden.rb +13 -0
- data/lib/cfml/tags/radio.rb +18 -0
- data/lib/cfml/tags/radios.rb +28 -0
- data/lib/cfml/tags/ratings.rb +42 -0
- data/lib/cfml/tags/select.rb +16 -0
- data/lib/cfml/tags/text.rb +10 -0
- data/lib/cfml/tags/textarea.rb +10 -0
- data/lib/cfml/tags/unknown.rb +17 -0
- data/spec/complex_spec.rb +53 -0
- data/spec/fixtures/complex.cfml +23 -0
- data/spec/gold_spec.rb +23 -0
- data/spec/sorta_match.rb +41 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tags/checkbox_spec.rb +81 -0
- data/spec/tags/checkboxes_spec.rb +26 -0
- data/spec/tags/group_spec.rb +35 -0
- data/spec/tags/hidden_spec.rb +15 -0
- data/spec/tags/radio_spec.rb +17 -0
- data/spec/tags/radios_spec.rb +26 -0
- data/spec/tags/select_spec.rb +26 -0
- data/spec/tags/text_spec.rb +36 -0
- data/spec/tags/textarea_spec.rb +18 -0
- data/spec/tags/unknown_spec.rb +19 -0
- metadata +103 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Chris Van Pelt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
= cfml
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but
|
13
|
+
bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2009 Chris Van Pelt. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "cfml"
|
8
|
+
gem.summary = "CFML is Crowd Flower Marup Language"
|
9
|
+
gem.description = "CFML let's you easily create form elements and custom interfaces for the CrowdFlower Croudsourcing platform"
|
10
|
+
gem.email = "vanpelt@doloreslabs.com"
|
11
|
+
gem.homepage = "http://github.com/dolores/cfml"
|
12
|
+
gem.authors = ["Chris Van Pelt"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
if File.exist?('VERSION.yml')
|
40
|
+
config = YAML.load(File.read('VERSION.yml'))
|
41
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
42
|
+
else
|
43
|
+
version = ""
|
44
|
+
end
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "cfml #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
51
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/cfml.rb
ADDED
data/lib/cfml/parser.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module CFML
|
2
|
+
class Parser
|
3
|
+
def initialize(content, opts = {})
|
4
|
+
@opts = opts
|
5
|
+
@doc = Nokogiri::XML("<root xmlns:cf='http://crowdflower.com'>#{content}</root>")
|
6
|
+
@cftags = @doc.xpath("//cf:*[not(ancestor::cf:*)]")
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert
|
10
|
+
@cftags.each do |t|
|
11
|
+
t.replace(tag_class(t.name).new(t, @opts).convert)
|
12
|
+
end
|
13
|
+
@doc
|
14
|
+
end
|
15
|
+
|
16
|
+
def golds
|
17
|
+
return @golds if @golds
|
18
|
+
@golds = {}
|
19
|
+
@doc.xpath("//cf:gold|//cf:*[@gold]").map do |g|
|
20
|
+
p = g.name == "gold" ? g.parent : g
|
21
|
+
next unless p
|
22
|
+
ref = tag_class(p.name).new(p, @opts).name
|
23
|
+
gold = (g.attributes["gold"] || g.attributes["src"]).to_s
|
24
|
+
gold = "#{ref}_gold" if gold =~ /^(true|\s*)$/
|
25
|
+
@golds[ref] = gold
|
26
|
+
["strict","regex"].each do |attrs|
|
27
|
+
if a = g.attributes[attrs]
|
28
|
+
@golds["_#{gold}_#{attrs}"] = attrs == "regex" ? [a.to_s,g.attributes["flags"].to_s] : a.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@golds
|
33
|
+
end
|
34
|
+
|
35
|
+
def wrap(content)
|
36
|
+
@opts[:no_wrap] ? content : "<div class=\"cfml #{@opts[:prefix]}\">#{content}</div>"
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_html
|
40
|
+
convert
|
41
|
+
wrap(@doc.at("root").inner_html)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.escape( string )
|
45
|
+
string.to_s.gsub( /&/, "&" ).
|
46
|
+
gsub( /</, "<" ).
|
47
|
+
gsub( />/, ">" ).
|
48
|
+
gsub( /"/, """ )
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
#This takes the name of the tag and converts it to the appropriate task
|
54
|
+
def tag_class(name)
|
55
|
+
CFML::Tags.module_eval(name.gsub(/(^|_)(.)/) { $2.upcase })
|
56
|
+
rescue
|
57
|
+
CFML::Tags::Unknown
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/cfml/tag.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module CFML
|
2
|
+
class Tag
|
3
|
+
def initialize(cfml, opts = {})
|
4
|
+
@cfml = cfml
|
5
|
+
@attrs = @cfml.attributes#Hash[*cfml.attributes.map {|k,v| [k.intern,v]}.flatten]
|
6
|
+
@tag = @cfml.name
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def prefix(name)
|
11
|
+
@opts[:prefix] ? "#{@opts[:prefix]}[#{name}]" : "#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
(@attrs["name"] || @attrs["label"]).to_s.downcase.gsub(/\s/,"_").gsub(/\W/,"")
|
16
|
+
end
|
17
|
+
|
18
|
+
def wrapper_classes
|
19
|
+
extras = @attrs["only-if"] ? "only-if:#{@attrs["only-if"]}" : ""
|
20
|
+
"#{@tag} #{extras}".strip
|
21
|
+
end
|
22
|
+
|
23
|
+
def classes
|
24
|
+
@classes ||= [name] << validations << @attrs["class"].to_s.split(/ /)
|
25
|
+
@classes.uniq.reject {|c| c.length == 0 }.join(" ").strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def convert
|
29
|
+
Nokogiri::HTML.fragment(to_html).children.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_html
|
33
|
+
@parsed ||= Liquid::Template.parse(self.class::Template).render(data, [LiquidFilters])
|
34
|
+
wrapper(@parsed)
|
35
|
+
end
|
36
|
+
|
37
|
+
def label(label, tag = "label")
|
38
|
+
required = " <span class=\"required\">(required)</span>" if validations.include?("required")
|
39
|
+
"<#{tag}>#{label}#{required}</#{tag}>" if label
|
40
|
+
end
|
41
|
+
|
42
|
+
def legend(label)
|
43
|
+
label(label, "legend") if label
|
44
|
+
end
|
45
|
+
|
46
|
+
def validations
|
47
|
+
@validations ||= @attrs["validations"].to_s.split(/ /).map {|v| "validates-#{v}"}
|
48
|
+
end
|
49
|
+
|
50
|
+
def instructions
|
51
|
+
@instructions ||= @attrs["instructions"] || (@cfml.at("instructions") ? @cfml.at("instructions").inner_html : nil)
|
52
|
+
@instructions ? "<p class=\"instructions\">#{@instructions}</p>" : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrapper(parsed, tag = "div")
|
56
|
+
#Deal with a label only wrapper... kinda jank
|
57
|
+
if tag == "label" && instructions
|
58
|
+
tag = "div"
|
59
|
+
parsed = "<label>#{parsed}</label>"
|
60
|
+
end
|
61
|
+
#Inserting instructions here makes it a non-variable input maybe...
|
62
|
+
"<#{tag} class=\"#{wrapper_classes}\">#{parsed}#{instructions}</#{tag}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
def data
|
66
|
+
(@opts[:data] || {}).merge({
|
67
|
+
"name" => prefix(name),
|
68
|
+
"label" => label(@attrs["label"]),
|
69
|
+
"legend" => legend(@attrs["label"]),
|
70
|
+
"classes" => classes,
|
71
|
+
"value" => @attrs["value"].to_s
|
72
|
+
})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Checkbox < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{default}}
|
6
|
+
<input name="{{name}}" type="checkbox" value="{{value}}" class="{{classes}}"/>
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def wrapper(parsed)
|
10
|
+
super("#{parsed} #{@attrs["label"]}", "label")
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix(name)
|
14
|
+
super(name) + (grouped? ? "[]" : "")
|
15
|
+
end
|
16
|
+
|
17
|
+
def grouped?
|
18
|
+
@cfml.parent && @cfml.parent.name == "checkboxes"
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
default = ""
|
23
|
+
if @attrs["default"]
|
24
|
+
default = Parser.new("<cf:hidden name=\"#{name}\" value=\"#{@attrs["default"]}\"/>", @opts.merge(:no_wrap => true)).to_html
|
25
|
+
end
|
26
|
+
value = @attrs["value"] || (grouped? ? @attrs["label"] : "true")
|
27
|
+
super.merge({"value" => value.to_s, "default" => default})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Checkboxes < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{% for checkbox in checkboxes %}
|
7
|
+
{{checkbox}}
|
8
|
+
{% endfor %}
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"checkboxes" => checkboxes})
|
13
|
+
end
|
14
|
+
|
15
|
+
def checkboxes
|
16
|
+
@cfml.xpath("cf:checkbox").map do |c|
|
17
|
+
c["name"] ||= name
|
18
|
+
c["validations"] ||= @attrs["validations"].to_s
|
19
|
+
Checkbox.new(c, @opts).to_html
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wrapper(parsed)
|
24
|
+
super(parsed,"fieldset")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Radio < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
<input name="{{name}}" type="radio" value="{{value}}" class="{{classes}}"/>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
def wrapper(parsed)
|
9
|
+
super("#{parsed} #{@attrs["label"]}", "label")
|
10
|
+
end
|
11
|
+
|
12
|
+
def data
|
13
|
+
value = @attrs["value"] || @attrs["label"]
|
14
|
+
super.merge({"value" => value.to_s})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Radios < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{% for radio in radios %}
|
7
|
+
{{radio}}
|
8
|
+
{% endfor %}
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"radios" => radios})
|
13
|
+
end
|
14
|
+
|
15
|
+
def radios
|
16
|
+
@cfml.xpath("cf:radio").map do |c|
|
17
|
+
c["name"] ||= name
|
18
|
+
c["validations"] ||= @attrs["validations"].to_s
|
19
|
+
Radio.new(c, @opts).to_html
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wrapper(parsed)
|
24
|
+
super(parsed, "fieldset")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Ratings < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
<table>
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th></th>
|
10
|
+
{% for rating in ratings limit:10 %}
|
11
|
+
<th>{{forloop.index}}</th>
|
12
|
+
{% endfor %}
|
13
|
+
<th></th>
|
14
|
+
</tr>
|
15
|
+
</thead>
|
16
|
+
<tbody>
|
17
|
+
<tr>
|
18
|
+
<td>{{from}}</td>
|
19
|
+
{% for rating in ratings limit:10 %}
|
20
|
+
<td>{{rating}}</td>
|
21
|
+
{% endfor %}
|
22
|
+
<td>{{to}}</td>
|
23
|
+
</tr>
|
24
|
+
</tbody>
|
25
|
+
</table>
|
26
|
+
HTML
|
27
|
+
|
28
|
+
def data
|
29
|
+
super.merge({"ratings" => ratings, "from" => @attrs["from"].to_s, "to" => @attrs["to"].to_s})
|
30
|
+
end
|
31
|
+
|
32
|
+
def ratings
|
33
|
+
@cfml.xpath("cf:rating").map do |c|
|
34
|
+
c["name"] ||= prefix
|
35
|
+
c["validations"] ||= @attrs["validations"]
|
36
|
+
c["value"] ||= @attrs["label"]
|
37
|
+
Radio.new(c).convert
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CFML
|
2
|
+
module Tags
|
3
|
+
class Select < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
<select name="{{name}}" class="{{classes}}">{% for option in options %}
|
7
|
+
{{option}}{% endfor %}
|
8
|
+
</select>
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"options" => @cfml.xpath("option").map {|c| c.to_xhtml}})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Complex" do
|
4
|
+
it "parses" do
|
5
|
+
@p = Parser.new(File.read(File.dirname(__FILE__) + "/fixtures/complex.cfml"), {:prefix => "u12345"})
|
6
|
+
@p.to_html.should sorta_match(<<-HTML)
|
7
|
+
<div class="cfml u12345">
|
8
|
+
<h1>This is my name</h1>
|
9
|
+
<p>This is a description about me</p>
|
10
|
+
<div class="select">
|
11
|
+
<label>Awesome</label>
|
12
|
+
<select name="u12345[awesome]" class="awesome validates-required">
|
13
|
+
<option value="">Choose an option</option>
|
14
|
+
<option>Awesome</option>
|
15
|
+
<option>Cool</option>
|
16
|
+
<option>Rad</option>
|
17
|
+
<option>Neat</option>
|
18
|
+
</select>
|
19
|
+
<p class="instructions">Just be cool about it</p>
|
20
|
+
</div>
|
21
|
+
<div class="text">
|
22
|
+
<label>Opinion</label>
|
23
|
+
<input name="u12345[opinion]" class="opinion validates-minLength:30 large" type="text" value="">
|
24
|
+
</div>
|
25
|
+
<div class="text">
|
26
|
+
<label>Email</label>
|
27
|
+
<input name="u12345[email]" class="email validates-required validates-email" type="text" value="">
|
28
|
+
</div>
|
29
|
+
<div class="rad">
|
30
|
+
<label class="checkbox">
|
31
|
+
<input name="u12345[you_sure]" class="you_sure" type="checkbox" value="yes"> You sure?
|
32
|
+
</label>
|
33
|
+
</div>
|
34
|
+
<div class="group only-if:awesome">
|
35
|
+
|
36
|
+
<fieldset class="checkboxes">
|
37
|
+
<legend>Cool checkboxes</legend>
|
38
|
+
|
39
|
+
<label class="checkbox">
|
40
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Wanna do it?">
|
41
|
+
Wanna do it?</label>
|
42
|
+
|
43
|
+
<label class="checkbox">
|
44
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Sure?">
|
45
|
+
Sure?</label>
|
46
|
+
|
47
|
+
</fieldset>
|
48
|
+
<input type="hidden" name="u12345[secret]" value="cool" class="secret" />
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
HTML
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h1>This is my name</h1>
|
2
|
+
<p>This is a description about me</p>
|
3
|
+
<cf:select label="Awesome" validations="required" instructions="Just be cool about it">
|
4
|
+
<cf:gold src="awesome_gold" regex="\\.asdf\\." flags="i" strict="true" />
|
5
|
+
<option value="">Choose an option</option>
|
6
|
+
<option>Awesome</option>
|
7
|
+
<option>Cool</option>
|
8
|
+
<option>Rad</option>
|
9
|
+
<option>Neat</option>
|
10
|
+
</cf:select>
|
11
|
+
<cf:text label="Opinion" validations="minLength:30" class="large" />
|
12
|
+
<cf:text label="Email" validations="required email" />
|
13
|
+
<div class="rad">
|
14
|
+
<cf:checkbox label="You sure?" value="yes" gold="true" />
|
15
|
+
</div>
|
16
|
+
<cf:group only-if="awesome">
|
17
|
+
<cf:checkboxes label="Cool checkboxes" name="rad">
|
18
|
+
<cf:instructions>This <i>is</i> great</cf:instructions>
|
19
|
+
<cf:checkbox label="Wanna do it?"/>
|
20
|
+
<cf:checkbox label="Sure?"/>
|
21
|
+
</cf:checkboxes>
|
22
|
+
<cf:hidden name="secret" value="cool"/>
|
23
|
+
</cf:group>
|
data/spec/gold_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Gold" do
|
4
|
+
it "should give me da gold" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkboxes label="Checkboxes">
|
7
|
+
<cf:gold src="awesome_gold" regex="\\.asdf\\." flags="i" strict="true" />
|
8
|
+
<cf:checkbox label="Wanna do it?" />
|
9
|
+
<cf:checkbox label="Sure?" />
|
10
|
+
</cf:checkboxes>
|
11
|
+
<cf:text name="cool" gold="true" />
|
12
|
+
<cf:textarea name="sweet" gold="sweetness" />
|
13
|
+
HTML
|
14
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
15
|
+
@p.golds.should == {
|
16
|
+
"_awesome_gold_strict" => "true",
|
17
|
+
"sweet" => "sweetness",
|
18
|
+
"checkboxes" => "awesome_gold",
|
19
|
+
"_awesome_gold_regex" => ["\\.asdf\\.", "i"],
|
20
|
+
"cool" => "cool_gold"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
data/spec/sorta_match.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class SortaMatch
|
2
|
+
def initialize(dom1,dom2)
|
3
|
+
@nodes1 = Nokogiri::HTML(dom1).xpath("//*").to_a
|
4
|
+
@nodes2 = Nokogiri::HTML(dom2).xpath("//*").to_a
|
5
|
+
@children = []
|
6
|
+
@nodes1.each_with_index { |d,i| @children << [d,@nodes2[i]] }
|
7
|
+
end
|
8
|
+
|
9
|
+
def missed
|
10
|
+
[present(@nodes1[@i]), present(@nodes2[@i])]
|
11
|
+
end
|
12
|
+
|
13
|
+
def present(node)
|
14
|
+
node ? "<#{node.name}#{node.attributes.map {|k,v| " #{k}=\"#{v}\""}}>#{node.children.first if first_text?(node)}" : @why
|
15
|
+
end
|
16
|
+
|
17
|
+
def first_text?(node)
|
18
|
+
node.children.first && node.children.first.text?
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?
|
22
|
+
@i = 0
|
23
|
+
@children.detect do |a,b|
|
24
|
+
if a.nil? || b.nil?
|
25
|
+
@why = "missing"
|
26
|
+
elsif !a.text?
|
27
|
+
if a.name != b.name
|
28
|
+
@why = "tag"
|
29
|
+
elsif a.attributes.map {|k,v| [k.to_s,v.to_s]} != b.attributes.map {|k,v| [k.to_s,v.to_s]}
|
30
|
+
@why = "attributes"
|
31
|
+
elsif first_text?(b) && a.children.first.to_s.strip != b.children.first.to_s.strip
|
32
|
+
@why = "text"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@why = nil
|
36
|
+
end
|
37
|
+
@i += 1 unless @why
|
38
|
+
@why
|
39
|
+
end.nil?
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'cfml'
|
4
|
+
require 'spec'
|
5
|
+
require 'sorta_match'
|
6
|
+
#require 'spec/autorun'
|
7
|
+
include CFML
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
Spec::Matchers.define :sorta_match do |expected|
|
14
|
+
match do |actual|
|
15
|
+
@sm = SortaMatch.new(expected, actual)
|
16
|
+
@sm.matches?
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message_for_should do |actual|
|
20
|
+
"Expected DOM's to sorta match but they had these differences:\n #{@sm.missed.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def convert(html)
|
25
|
+
Nokogiri::HTML.fragment(html)
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radio" do
|
4
|
+
it "should render basic checkbox" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkbox label="Check"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<label class="checkbox">
|
12
|
+
<input type="checkbox" value="true" name="u12345[check]" class="check"/> Check
|
13
|
+
</label>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should render checkbox custom value" do
|
19
|
+
cfml = <<-HTML
|
20
|
+
<cf:checkbox label="Check" value="yes"/>
|
21
|
+
HTML
|
22
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
23
|
+
@p.to_html.should sorta_match(<<-HTML)
|
24
|
+
<div class="cfml u12345">
|
25
|
+
<label class="checkbox">
|
26
|
+
<input type="checkbox" value="yes" name="u12345[check]" class="check"/> Check
|
27
|
+
</label>
|
28
|
+
</div>
|
29
|
+
HTML
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should render default checkbox" do
|
33
|
+
cfml = <<-HTML
|
34
|
+
<cf:checkbox label="Check" default="false"/>
|
35
|
+
HTML
|
36
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
37
|
+
@p.to_html.should sorta_match(<<-HTML)
|
38
|
+
<div class="cfml u12345">
|
39
|
+
<label class="checkbox">
|
40
|
+
<input type="hidden" value="false" name="u12345[check]" class="check"/>
|
41
|
+
<input type="checkbox" value="true" name="u12345[check]" class="check"/> Check
|
42
|
+
</label>
|
43
|
+
</div>
|
44
|
+
HTML
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should render checkbox with instructions attribute" do
|
48
|
+
cfml = <<-HTML
|
49
|
+
<cf:checkbox label="Rad" instructions="Please be careful when you are selecting these."/>
|
50
|
+
HTML
|
51
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
52
|
+
@p.to_html.should sorta_match(<<-HTML)
|
53
|
+
<div class="cfml u12345">
|
54
|
+
<div class="checkbox">
|
55
|
+
<label>
|
56
|
+
<input type="checkbox" value="true" name="u12345[rad]" class="rad"/> Rad
|
57
|
+
</label>
|
58
|
+
<p class="instructions">Please be careful when you are selecting these.</p>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
HTML
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should render checkbox nested" do
|
65
|
+
cfml = <<-HTML
|
66
|
+
<div class="rando">
|
67
|
+
<cf:checkbox label="Rad"/>
|
68
|
+
</div>
|
69
|
+
HTML
|
70
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
71
|
+
@p.to_html.should sorta_match(<<-HTML)
|
72
|
+
<div class="cfml u12345">
|
73
|
+
<div class="rando">
|
74
|
+
<label class="checkbox">
|
75
|
+
<input type="checkbox" value="true" name="u12345[rad]" class="rad"/> Rad
|
76
|
+
</label>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
HTML
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Checkboxes" do
|
4
|
+
it "should render basic checkboxes" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:checkboxes label="Cool checkboxes">
|
7
|
+
<cf:checkbox label="Wanna do it?"/>
|
8
|
+
<cf:checkbox label="Sure?">
|
9
|
+
</cf:checkboxes>
|
10
|
+
HTML
|
11
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
12
|
+
@p.to_html.should sorta_match(<<-HTML)
|
13
|
+
<div class="cfml u12345">
|
14
|
+
<fieldset class="checkboxes">
|
15
|
+
<legend>Cool checkboxes</legend>
|
16
|
+
<label class="checkbox">
|
17
|
+
<input type="checkbox" value="Wanna do it?" name="u12345[cool_checkboxes][]" class="cool_checkboxes"/> Wanna do it?
|
18
|
+
</label>
|
19
|
+
<label class="checkbox">
|
20
|
+
<input type="checkbox" value="Sure?" name="u12345[cool_checkboxes][]" class="cool_checkboxes"/> Sure?
|
21
|
+
</label>
|
22
|
+
</fieldset>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Select" do
|
4
|
+
it "should render group" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:group only-if="awesome">
|
7
|
+
<cf:checkboxes label="Cool checkboxes" name="rad">
|
8
|
+
<cf:checkbox label="Wanna do it?"/>
|
9
|
+
<cf:checkbox label="Sure?"/>
|
10
|
+
</cf:checkboxes>
|
11
|
+
<cf:text label="Something"/>
|
12
|
+
</cf:group>
|
13
|
+
HTML
|
14
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
15
|
+
@p.to_html.should sorta_match(<<-HTML)
|
16
|
+
<div class="cfml u12345">
|
17
|
+
<div class="group only-if:awesome">
|
18
|
+
<fieldset class="checkboxes">
|
19
|
+
<legend>Cool checkboxes</legend>
|
20
|
+
<label class="checkbox">
|
21
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Wanna do it?" /> Wanna do it?
|
22
|
+
</label>
|
23
|
+
<label class="checkbox">
|
24
|
+
<input name="u12345[rad][]" class="rad" type="checkbox" value="Sure?" /> Sure?
|
25
|
+
</label>
|
26
|
+
</fieldset>
|
27
|
+
<div class="text">
|
28
|
+
<label>Something</label>
|
29
|
+
<input name="u12345[something]" class="something" type="text" value="" />
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
HTML
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Hidden" do
|
4
|
+
it "should render basic hidden field" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:hidden name="secret" value="shit"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<input type="hidden" value="shit" name="u12345[secret]" class="secret"/>
|
12
|
+
</div>
|
13
|
+
HTML
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radio" do
|
4
|
+
it "should render basic radio" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:radio label="Rad"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<label class="radio">
|
12
|
+
<input type="radio" value="Rad" name="u12345[rad]" class="rad"/> Rad
|
13
|
+
</label>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Radios" do
|
4
|
+
it "should render basic radios" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:radios label="Cool radios">
|
7
|
+
<cf:radio label="Wanna do it?"/>
|
8
|
+
<cf:radio label="Sure!">
|
9
|
+
</cf:radios>
|
10
|
+
HTML
|
11
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
12
|
+
@p.to_html.should sorta_match(<<-HTML)
|
13
|
+
<div class="cfml u12345">
|
14
|
+
<fieldset class="radios">
|
15
|
+
<legend>Cool radios</legend>
|
16
|
+
<label class="radio">
|
17
|
+
<input type="radio" value="Wanna do it?" name="u12345[cool_radios]" class="cool_radios"/> Wanna do it?
|
18
|
+
</label>
|
19
|
+
<label class="radio">
|
20
|
+
<input type="radio" value="Sure!" name="u12345[cool_radios]" class="cool_radios"/> Sure!
|
21
|
+
</label>
|
22
|
+
</fieldset>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Select" do
|
4
|
+
it "should render basic select" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:select label="Basic">
|
7
|
+
<option>One</option>
|
8
|
+
<option>Two</option>
|
9
|
+
<option>Three</option>
|
10
|
+
</cf:select>
|
11
|
+
HTML
|
12
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
13
|
+
@p.to_html.should sorta_match(<<-HTML)
|
14
|
+
<div class="cfml u12345">
|
15
|
+
<div class="select">
|
16
|
+
<label>Basic</label>
|
17
|
+
<select name="u12345[basic]" class="basic">
|
18
|
+
<option>One</option>
|
19
|
+
<option>Two</option>
|
20
|
+
<option>Three</option>
|
21
|
+
</select>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
HTML
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Text" do
|
4
|
+
it "should render basic text" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:text label="Coolio"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<div class="text">
|
12
|
+
<label>Coolio</label>
|
13
|
+
<input type="text" value="" name="u12345[coolio]" class="coolio"/>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
HTML
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should render text with classes and instructions tag" do
|
20
|
+
cfml = <<-HTML
|
21
|
+
<cf:text label="Coolio" class="small">
|
22
|
+
<instructions>Just be <b>cool</b> bro!</instructions>
|
23
|
+
</cf:text>
|
24
|
+
HTML
|
25
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
26
|
+
@p.to_html.should sorta_match(<<-HTML)
|
27
|
+
<div class="cfml u12345">
|
28
|
+
<div class="text">
|
29
|
+
<label>Coolio</label>
|
30
|
+
<input type="text" class="coolio small" value="" name="u12345[coolio]" class="coolio"/>
|
31
|
+
<p class="instructions">Just be <b>cool</b> bro!</p>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
HTML
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Textarea" do
|
4
|
+
it "should render basic text" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:textarea label="Sweet dude man!" value="4 sure"/>
|
7
|
+
HTML
|
8
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
9
|
+
@p.to_html.should sorta_match(<<-HTML)
|
10
|
+
<div class="cfml u12345">
|
11
|
+
<div class="textarea">
|
12
|
+
<label>Sweet dude man!</label>
|
13
|
+
<textarea name="u12345[sweet_dude_man]" class="sweet_dude_man">4 sure</textarea>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
HTML
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CFML Unknown" do
|
4
|
+
it "should render unknown field" do
|
5
|
+
cfml = <<-HTML
|
6
|
+
<cf:crazy name="secret">
|
7
|
+
Something
|
8
|
+
</cf:crazy>
|
9
|
+
HTML
|
10
|
+
@p = Parser.new(cfml, :prefix => "u12345")
|
11
|
+
@p.to_html.should sorta_match(<<-HTML)
|
12
|
+
<div class="cfml u12345">
|
13
|
+
<div class="crazy unknown">
|
14
|
+
Something
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
HTML
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dolores-cfml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Van Pelt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: CFML let's you easily create form elements and custom interfaces for the CrowdFlower Croudsourcing platform
|
17
|
+
email: vanpelt@doloreslabs.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- lib/cfml.rb
|
33
|
+
- lib/cfml/liquid_filters.rb
|
34
|
+
- lib/cfml/parser.rb
|
35
|
+
- lib/cfml/tag.rb
|
36
|
+
- lib/cfml/tags/checkbox.rb
|
37
|
+
- lib/cfml/tags/checkboxes.rb
|
38
|
+
- lib/cfml/tags/group.rb
|
39
|
+
- lib/cfml/tags/hidden.rb
|
40
|
+
- lib/cfml/tags/radio.rb
|
41
|
+
- lib/cfml/tags/radios.rb
|
42
|
+
- lib/cfml/tags/ratings.rb
|
43
|
+
- lib/cfml/tags/select.rb
|
44
|
+
- lib/cfml/tags/text.rb
|
45
|
+
- lib/cfml/tags/textarea.rb
|
46
|
+
- lib/cfml/tags/unknown.rb
|
47
|
+
- spec/complex_spec.rb
|
48
|
+
- spec/fixtures/complex.cfml
|
49
|
+
- spec/gold_spec.rb
|
50
|
+
- spec/sorta_match.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
- spec/tags/checkbox_spec.rb
|
53
|
+
- spec/tags/checkboxes_spec.rb
|
54
|
+
- spec/tags/group_spec.rb
|
55
|
+
- spec/tags/hidden_spec.rb
|
56
|
+
- spec/tags/radio_spec.rb
|
57
|
+
- spec/tags/radios_spec.rb
|
58
|
+
- spec/tags/select_spec.rb
|
59
|
+
- spec/tags/text_spec.rb
|
60
|
+
- spec/tags/textarea_spec.rb
|
61
|
+
- spec/tags/unknown_spec.rb
|
62
|
+
has_rdoc: false
|
63
|
+
homepage: http://github.com/dolores/cfml
|
64
|
+
licenses:
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --charset=UTF-8
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.5
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: CFML is Crowd Flower Marup Language
|
89
|
+
test_files:
|
90
|
+
- spec/complex_spec.rb
|
91
|
+
- spec/gold_spec.rb
|
92
|
+
- spec/sorta_match.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
- spec/tags/checkbox_spec.rb
|
95
|
+
- spec/tags/checkboxes_spec.rb
|
96
|
+
- spec/tags/group_spec.rb
|
97
|
+
- spec/tags/hidden_spec.rb
|
98
|
+
- spec/tags/radio_spec.rb
|
99
|
+
- spec/tags/radios_spec.rb
|
100
|
+
- spec/tags/select_spec.rb
|
101
|
+
- spec/tags/text_spec.rb
|
102
|
+
- spec/tags/textarea_spec.rb
|
103
|
+
- spec/tags/unknown_spec.rb
|