elementor 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def extract_options!
3
+ last.is_a?(Hash) ? pop : { }
4
+ end
5
+ end unless [].respond_to?(:extract_options!)
@@ -1,10 +1,16 @@
1
1
  module Kernel
2
- def blank_context(ivars={}, &block)
2
+ def blank_context(*args, &block)
3
+ ivars = args.extract_options!
4
+
5
+ args.push(/(^__)|instance_/)
6
+
3
7
  klass = Class.new do
4
- instance_methods.each { |m| undef_method(m) unless m =~ /^(__|instance_|meta)/ }
8
+ instance_methods.each do |m|
9
+ undef_method(m) unless args.any? { |pattern| m =~ pattern }
10
+ end
5
11
  end
6
12
 
7
- klass.class_eval(&block)
13
+ klass.class_eval(&block) if block_given?
8
14
  instance = klass.new
9
15
  ivars.each { |key, value| instance.instance_variable_set("@#{key}", value) }
10
16
  instance
@@ -1,8 +1,4 @@
1
1
  class Object
2
- def try(sym, *args, &block)
3
- respond_to?(sym) ? send(sym, *args, &block) : nil
4
- end
5
-
6
2
  def metaclass
7
3
  class << self; self end
8
4
  end
data/lib/elementor.rb CHANGED
@@ -3,6 +3,7 @@ $LOAD_PATH << File.dirname(__FILE__) + '/elementor'
3
3
 
4
4
  require 'rubygems'
5
5
  require 'nokogiri'
6
+ require 'array'
6
7
  require 'kernel'
7
8
  require 'object'
8
9
  require 'symbol'
@@ -11,8 +12,6 @@ require 'element_set'
11
12
 
12
13
  module Elementor
13
14
  def elements(opts={}, &block)
14
- namer = Result.new(self, opts, &block)
15
- namer.define_elements!
16
- namer.dispatcher
15
+ Result.new(self, opts, &block).dispatcher
17
16
  end
18
17
  end
@@ -1,41 +1,54 @@
1
1
  module Elementor
2
+ # ElementSet objects wrap a Nokogiri #search result and
3
+ # add additional functionality such as chained filtering.
2
4
  class ElementSet < Array
3
5
  attr_accessor :result, :selector
4
6
 
7
+ # A simple filter for selecting only elements with content
8
+ # that either includes a String passed in, or matches a Regexp.
5
9
  def with_text(matcher)
6
- replace(select { |item|
10
+ filter do |item|
7
11
  case matcher
8
12
  when Regexp then item.text =~ matcher
9
13
  when String then item.text.include?(matcher)
14
+ else item.text.include?(matcher.to_s)
10
15
  end
11
- }) ; self
16
+ end
12
17
  end
13
18
 
19
+ alias_method :text, :with_text
20
+
21
+ # Attribute filtering using hashes. See the specs for examples.
14
22
  def with_attrs(options={})
15
- replace(select { |item|
16
- options.all? { |key, value|
23
+ filter do |item|
24
+ options.all? do |key, value|
17
25
  case value
18
26
  when Regexp then item[key.to_s] =~ value
19
27
  when String then item[key.to_s] == value
28
+ else item[key.to_s] == value.to_s
20
29
  end
21
- }
22
- }) ; self
23
- end
24
-
25
- def inspect
26
- map(&:text).inspect
30
+ end
31
+ end
27
32
  end
28
33
 
34
+ alias_method :attrs, :with_attrs
35
+
29
36
  def method_missing(sym, *args, &block)
30
- result.try(sym, doc, *args) || super
37
+ result.respond_to?(sym) ? result.send(sym, doc, *args) : super
31
38
  end
32
39
 
33
40
  def respond_to?(sym)
34
41
  result.respond_to?(sym) || super
35
42
  end
36
43
 
44
+ private
45
+
37
46
  def doc
38
47
  result.doc.search(selector)
39
48
  end
49
+
50
+ def filter(&block)
51
+ replace(select(&block)) ; return self
52
+ end
40
53
  end
41
54
  end
@@ -8,63 +8,97 @@ module Elementor
8
8
  @context = context
9
9
  @doc_ready = false
10
10
  block.call(naming_context)
11
+ define_elements!
11
12
  end
12
13
 
14
+ # Allows for the parsing of raw markup that doesn't come
15
+ # from the :from option.
13
16
  def parse!(markup)
14
17
  doc(markup)
15
18
  end
16
19
 
17
- def naming_context
18
- @naming_context ||= blank_context(:this => self) do
19
- def method_missing(sym, *args)
20
- @this.element_names[sym] = *args
20
+ # Returns a blank slate object that delegates to either an
21
+ # instance of Result or the original Nokogiri doc.
22
+ def dispatcher
23
+ @dispatcher ||= blank_context(:this => self) do
24
+ def method_missing(sym, *args, &block)
25
+ @this.doc_ready!
26
+ [@this, @this.doc].each do |context|
27
+ next unless context.respond_to?(sym)
28
+ return context.send(sym, *args, &block)
29
+ end
30
+ super # raise NoMethodError if no context can handle
21
31
  end
22
32
  end
23
33
  end
24
34
 
25
- def dispatcher
26
- @dispatcher ||= blank_context(:this => self) do
27
- def method_missing(sym, *args, &block)
28
- @this.doc_ready = true
29
- @this.try(sym, *args, &block) || @this.doc.try(sym, *args, &block) || super
35
+ # The list of name/selector pairs you specify in the
36
+ # elements block.
37
+ def element_names
38
+ @element_names ||= { }
39
+ end
40
+
41
+ # Returns the raw Nokogiri doc once a method has been called
42
+ # on the dispatcher. Up until that point, returns nil.
43
+ def doc(markup=nil)
44
+ if html = markup || content
45
+ @doc = nil if markup
46
+ @doc ||= Nokogiri(html)
47
+ end
48
+ end
49
+
50
+ # Indicates whether or not the dispatcher has received messages,
51
+ # meaning the content method can be called.
52
+ def doc_ready?
53
+ @doc_ready
54
+ end
55
+
56
+ def doc_ready!
57
+ @doc_ready = true
58
+ end
59
+
60
+ private
61
+
62
+ # Blank slate context for defining element names.
63
+ def naming_context
64
+ @naming_context ||= blank_context(:this => self) do
65
+ def method_missing(sym, *args)
66
+ @this.element_names[sym] = *args
30
67
  end
31
68
  end
32
69
  end
33
-
70
+
71
+ # Takes element names and defines methods that return ElementSet
72
+ # objects with can be chained and filtered.
34
73
  def define_elements!
35
74
  element_names.each do |name, selector|
36
- meta_def(name) do |*filters|
37
- set = ElementSet.new scope(filters).search(selector)
38
- set.result = self
39
- set.selector = selector
40
- filters.empty? ? set : filters.inject(set) { |result, fn| fn[result] }
41
- end
75
+ metaclass.class_eval(<<-END, __FILE__, __LINE__)
76
+ def #{name}(*filters, &block)
77
+ make_element_set(#{name.inspect}, #{selector.inspect}, *filters, &block)
78
+ end
79
+ END
42
80
  end
43
81
  end
44
82
 
83
+ def make_element_set(name, selector, *filters, &block)
84
+ set = ElementSet.new scope(filters).search(selector)
85
+ set.result = self
86
+ set.selector = selector
87
+ set = filters.empty? ? set : filters.inject(set) { |result, fn| fn[result] }
88
+ set = block.call(set) if block_given?
89
+ set
90
+ end
91
+
92
+ # Enables the chaining of element selector methods to only search
93
+ # within the scope of a certain ElementSet.
45
94
  def scope(filters)
46
95
  scope = filters.first.is_a?(Proc) ? nil : filters.shift
47
96
  scope || doc
48
97
  end
49
98
 
50
- def element_names
51
- @element_names ||= { }
52
- end
53
-
54
- def doc(markup=nil)
55
- if html = markup || content
56
- @doc = nil if markup
57
- @doc ||= Nokogiri(html)
58
- end
59
- end
60
-
61
99
  def content
62
100
  return unless doc_ready?
63
101
  @content ||= context.send(opts[:from] || :body)
64
102
  end
65
-
66
- def doc_ready?
67
- @doc_ready
68
- end
69
103
  end
70
104
  end
@@ -7,11 +7,15 @@ module Spec
7
7
  self
8
8
  end
9
9
 
10
+ alias_method :text, :with_text
11
+
10
12
  def with_attrs(options={})
11
13
  @args ||= []
12
14
  @args << proc { |set| set.with_attrs(options) }
13
15
  self
14
16
  end
17
+
18
+ alias_method :attrs, :with_attrs
15
19
  end
16
20
  end
17
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elementor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Nakajima
@@ -37,6 +37,7 @@ files:
37
37
  - lib/elementor/element_set.rb
38
38
  - lib/elementor/result.rb
39
39
  - lib/core_ext
40
+ - lib/core_ext/array.rb
40
41
  - lib/core_ext/object.rb
41
42
  - lib/core_ext/kernel.rb
42
43
  - lib/core_ext/symbol.rb
@@ -62,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
63
  requirements: []
63
64
 
64
65
  rubyforge_project:
65
- rubygems_version: 1.3.0
66
+ rubygems_version: 1.3.1
66
67
  signing_key:
67
68
  specification_version: 2
68
69
  summary: Prettier element traversal with Nokogiri