azebiki 0.0.1

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.
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Eric Allam
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.
@@ -0,0 +1,75 @@
1
+ ## Overview
2
+
3
+ Azebiki provides a simple dsl for declaring CSS and XPATH matchers against a block of HTML content. It is built on top of Webrat matchers and nokogiri.
4
+
5
+ ## Example:
6
+
7
+ Given this block of HTML:
8
+
9
+ <html>
10
+ <head><title>Example</title></head>
11
+ <body>
12
+ <!--
13
+ <a href="http://incomment.com">In Comment</a>
14
+ -->
15
+ <div id='main' class="big">
16
+ <p id="body">
17
+ <table class='short table'>
18
+ <thead>
19
+ <tr>
20
+ <th>First Column</th>
21
+ </tr>
22
+ </thead>
23
+ </table>
24
+ </p>
25
+ </div>
26
+
27
+ Given this Azebiki::Checker definition:
28
+
29
+ c = Azebiki::Checker.new(HTML) do
30
+ div('#main.big') do
31
+ p('#body') do
32
+ table do
33
+ thead do
34
+ tr do
35
+ th(:content => 'First Column')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ Will result in:
44
+
45
+ c.success?
46
+ # => true
47
+
48
+ Or given this definition:
49
+
50
+ c = Azebiki::Checker.new(HTML) do
51
+ a(:href => 'http://incomment.com', :content => 'In Comment')
52
+ end
53
+
54
+ Will result in:
55
+
56
+ c.success?
57
+ # => false
58
+ c.errors.inspect
59
+ # => ['Content should have included <a href="http://incomment.com">In Comment</a>, but did not']
60
+
61
+ Or give it a custom error message:
62
+
63
+ c = Azebiki::Checker.new(HTML) do
64
+ a(:href => 'http://incomment.com', :content => 'In Comment').failure_message('No tag :tag, SORRY!')
65
+ end
66
+
67
+ c.errors.inspect
68
+ # => ['No tag <a href="http://incomment.com">In Comment</a>, SORRY!']
69
+
70
+
71
+ ## Install
72
+
73
+ gem install azebiki
74
+
75
+
File without changes
@@ -0,0 +1 @@
1
+ require "azebiki/azebiki"
@@ -0,0 +1,234 @@
1
+ require 'webrat/core/matchers/have_selector'
2
+ require 'webrat/core/matchers/have_content'
3
+ require 'nokogiri'
4
+
5
+ # This class is useful for making sure an HTML text has certain tags/content
6
+ # It supports both XML and HTML. For example, if you want to make sure someone has included
7
+ # a link that points to google, has no follow, with the text 'Google Sucks!':
8
+ # c = Checker.new(html) do |v|
9
+ # v.matches('a', :href => "http://google.com", :rel => "nofollow", :content => "Google Sucks!")
10
+ # end
11
+ #
12
+ # c.success? == true # if html does include the link to google
13
+ # c.errors == [] # errors is a list of error messages for each match that did not succeed, which can be customized:
14
+ #
15
+ # c = Checker.new(html) do |v|
16
+ # v.matches('a', :href => "http://google.com", :rel => "nofollow", :content => "Google Sucks!").failure_message('Sorry, no google link, should have :tag')
17
+ # end
18
+ #
19
+ # c.errors
20
+ #=> ["Sorry, no google link, should have <a href='http://google.com' rel='nofollow'>Google Sucks!</a>"]
21
+ #
22
+ # You can also nest matches, to match against children content. For example, if you
23
+ # want check for an image link:
24
+ #
25
+ # c = Checker.new(html) do |v|
26
+ # v.matches('a', :href => "http://google.com", :rel => "nofollow", :content => "Google Sucks!") do |a|
27
+ # a.matches('img', :src => 'http://google.com/sucks.png')
28
+ # end
29
+ # end
30
+ #
31
+ # which will succeed only if the img tag is nested below the a tag. You can nest matchers pretty deep
32
+ module Azebiki
33
+ class Checker
34
+
35
+ class MyHaveSelector < Webrat::Matchers::HaveSelector
36
+ def add_attributes_conditions_to(query)
37
+ attribute_conditions = []
38
+
39
+ @options.each do |key, value|
40
+ next if [:content, :count].include?(key)
41
+ if value.is_a?(Hash)
42
+ func, match = value.keys.first, value.values.first
43
+ attribute_conditions << "#{func}(@#{key}, #{xpath_escape(match)})"
44
+ else
45
+ attribute_conditions << "@#{key} = #{xpath_escape(value)}"
46
+ end
47
+ end
48
+
49
+ if attribute_conditions.any?
50
+ query << "[#{attribute_conditions.join(' and ')}]"
51
+ end
52
+ end
53
+
54
+ def matches?(stringlike, &block)
55
+ @block ||= block
56
+ matched = matches(stringlike)
57
+
58
+ if @options[:count]
59
+ matched.size == @options[:count] && (!@block || @block.call(matched))
60
+ else
61
+ matched.any? && (!@block || @block.call(matched))
62
+ end
63
+ end
64
+
65
+
66
+ end
67
+
68
+ class MatcherProxy
69
+
70
+ def initialize(have_matcher)
71
+ @have_matcher = have_matcher
72
+ @failure_message = "Content should have included #{content_message}, but did not"
73
+ end
74
+
75
+ def failure_message(new_failure_message)
76
+ @failure_message = new_failure_message.gsub(/:tag/, content_message)
77
+ end
78
+
79
+ def content_message
80
+ if @have_matcher.respond_to?(:tag_inspect)
81
+ @have_matcher.tag_inspect
82
+ else
83
+ @have_matcher.content_message
84
+ end
85
+ end
86
+
87
+ def matches?(content)
88
+ @have_matcher.matches?(content)
89
+ end
90
+
91
+ def message
92
+ @failure_message
93
+ end
94
+
95
+ end
96
+
97
+ class MatcherBuilder < BasicObject
98
+
99
+ attr_reader :tags, :contents, :failure_message
100
+
101
+ def initialize(&block)
102
+ @tags = []
103
+ @contents = []
104
+ instance_eval &block
105
+ end
106
+
107
+ def has_content(text)
108
+ @contents << text
109
+ end
110
+
111
+ def method_missing(method, *args, &block)
112
+ tag = {:tag_name => method.to_s}
113
+
114
+ if args.first.is_a?(::String)
115
+ id_or_class = args.first
116
+
117
+ if id_or_class.split('#').size == 2
118
+ id_and_classes = id_or_class.split('#').last
119
+ id_and_classes = id_and_classes.split('.')
120
+ tag[:id] = id_and_classes.shift
121
+ tag[:class] = id_and_classes
122
+ end
123
+
124
+ elsif args.first.is_a?(::Hash)
125
+ tag.merge!(args.first)
126
+ end
127
+
128
+ if args[1] && args[1].is_a?(::Hash)
129
+ if classes = args[1][:class]
130
+ tag[:class] ||= []
131
+ classes.split('.').reject {|s| s.empty? }.each do |s|
132
+ tag[:class] << s
133
+ end
134
+
135
+ tag[:class].uniq!
136
+ end
137
+
138
+ tag.merge!(args[1])
139
+ end
140
+
141
+ if tag[:class]
142
+ tag[:class] = tag[:class].join(' ')
143
+ tag.delete(:class) if tag[:class].strip.empty?
144
+ end
145
+
146
+ tag[:child] = block
147
+
148
+ @tags << tag
149
+
150
+ def tag.failure_message(text)
151
+ self[:failure_message] = text
152
+ end
153
+
154
+ return tag
155
+ end
156
+ end
157
+
158
+ attr_accessor :content, :have_matchers, :errors
159
+
160
+ def initialize(content, &block)
161
+ @content = content
162
+ @errors = []
163
+ @have_matchers = []
164
+ @self_before_instance_eval = eval "self", block.binding
165
+ @matcher_builder = MatcherBuilder.new(&block)
166
+ build_contents
167
+ build_matchers
168
+ run_matchers
169
+ end
170
+
171
+ def contains(matching_text)
172
+ selector = MatcherProxy.new(Webrat::Matchers::HasContent.new(matching_text))
173
+ @have_matchers << selector
174
+ selector
175
+ end
176
+
177
+
178
+ def matches(name, attributes = {}, &block)
179
+ if block_given?
180
+
181
+ have = MyHaveSelector.new(name, attributes) do |n|
182
+ Azebiki::Checker.new(n, &block).success?
183
+ end
184
+
185
+ selector = MatcherProxy.new(have)
186
+ else
187
+ selector = MatcherProxy.new(MyHaveSelector.new(name, attributes))
188
+ end
189
+
190
+ @have_matchers << selector
191
+ selector
192
+ end
193
+
194
+ def success?
195
+ @success
196
+ end
197
+
198
+ private
199
+
200
+ def build_contents
201
+ @matcher_builder.contents.each do |s|
202
+ contains s
203
+ end
204
+ end
205
+
206
+ def method_missing(method, *args, &block)
207
+ @self_before_instance_eval.send method, *args, &block
208
+ end
209
+
210
+ def build_matchers
211
+ @matcher_builder.tags.each do |tag|
212
+ name = tag.delete(:tag_name)
213
+ b = tag.delete(:child)
214
+ attributes = tag
215
+ selector = matches(name, attributes, &b)
216
+ if tag[:failure_message]
217
+ selector.failure_message(tag[:failure_message])
218
+ end
219
+ end
220
+ end
221
+
222
+ def run_matchers
223
+ return true if @have_matchers.empty?
224
+ @have_matchers.each do |selector|
225
+ if !selector.matches?(@content)
226
+ @errors << selector.message
227
+ end
228
+ end
229
+
230
+ @success = @errors.empty?
231
+ end
232
+
233
+ end
234
+ end
@@ -0,0 +1,3 @@
1
+ module Azebiki
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: azebiki
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Eric Allam
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-20 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: webrat
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: ""
34
+ email:
35
+ - eric@envylabs.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - lib/azebiki/azebiki.rb
44
+ - lib/azebiki/version.rb
45
+ - lib/azebiki.rb
46
+ - LICENSE
47
+ - CHANGELOG.md
48
+ - README.md
49
+ - ROADMAP.md
50
+ has_rdoc: true
51
+ homepage: http://github.com/rubymaverick/azebiki
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 1
74
+ - 3
75
+ - 6
76
+ version: 1.3.6
77
+ requirements: []
78
+
79
+ rubyforge_project: azebiki
80
+ rubygems_version: 1.3.7
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: A DSL for validating HTML
84
+ test_files: []
85
+