mofo 0.1.2 → 0.2

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.
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 0.2
2
+ - Minor refactoring
3
+ - Added Geo and Adr [Andrew Turner]
4
+ - Added XFN [Steve Ivy, Christian Carter]
5
+ - Added hResume [Grant Rodgers]
6
+ - Added include-pattern support
7
+ - occurances => occurences
8
+
1
9
  = 0.1.2
2
10
  - Upgraded to test/spec 0.3
3
11
  - Fixed so tests run as Rails plugin
data/README CHANGED
@@ -145,17 +145,15 @@
145
145
  - hCalendar [ upcoming.org ]
146
146
  - hReview [ cork'd reviews ]
147
147
  - hEntry [ err the blog posts ]
148
+ - hResume [ linkedin.com ]
148
149
  - xoxo [ chowhound.com ]
150
+ - geo [ upcoming.org ]
151
+ - adr [ upcoming.org ]
152
+ - xfn [ linkedin.com ]
149
153
 
150
154
  patterns:
151
155
  - rel-tag
152
156
  - rel-bookmark
153
-
154
- Here are the microformats we want soon soon soon:
155
- - geo
156
- - hResume
157
-
158
- patterns:
159
157
  - include-pattern
160
158
 
161
159
  = Ruby on Rails
@@ -185,3 +183,15 @@ them in a session.
185
183
  => http://rubyforge.org/projects/scrapi/
186
184
  >> uformats
187
185
  => http://rubyforge.org/projects/uformats
186
+
187
+ = Contributors
188
+
189
+ >> Steve Ivy
190
+ >> Olle Jonsson
191
+ >> Christian Carter
192
+ >> Grant Rodgers
193
+
194
+ = Author
195
+
196
+ >> Chris Wanstrath
197
+ => chris[at]ozmm[dot]org
@@ -1,5 +1,5 @@
1
- %w[rubygems hpricot microformat/string open-uri ostruct timeout].each { |f| require f }
2
- require_gem 'hpricot', '>= 0.4.59'
1
+ %w(rubygems hpricot microformat/string microformat/array open-uri ostruct timeout).each { |f| require f }
2
+ gem 'hpricot', '>= 0.4.59'
3
3
 
4
4
  class Microformat
5
5
  module Base
@@ -8,12 +8,12 @@ class Microformat
8
8
  #
9
9
  def find(*args)
10
10
  target, @options = args
11
- @options ||= Hash === target ? target : {}
11
+ @options ||= target.is_a?(Hash) ? target : {}
12
12
  [:first, :all].each { |key| target = @options[key] if @options[key] }
13
13
 
14
- doc = build_doc(@options[:text] ? @options : target)
14
+ @doc = build_doc(@options[:text] ? @options : target)
15
15
 
16
- microformats = find_occurances(doc)
16
+ microformats = find_occurences(@doc)
17
17
  raise MicroformatNotFound if @options[:strict] && microformats.empty?
18
18
  return @options[:first] ? nil : [] if microformats.empty?
19
19
 
@@ -29,6 +29,22 @@ class Microformat
29
29
  end
30
30
  end
31
31
 
32
+ names_and_keys = proc do |attributes|
33
+ attributes.map do |att|
34
+ att.respond_to?(:keys) ? att.keys.first : att
35
+ end
36
+ end
37
+
38
+ define_method :attributes do
39
+ names_and_keys[@attributes[:many] + @attributes[:one]]
40
+ end
41
+
42
+ %w(many one).each do |type|
43
+ define_method("#{type}s") do
44
+ names_and_keys[@attributes[type.intern]]
45
+ end
46
+ end
47
+
32
48
  protected
33
49
  ##
34
50
  # DSL Related
@@ -41,7 +57,7 @@ class Microformat
41
57
  def collector
42
58
  collector = Hash.new([])
43
59
  def collector.method_missing(method, *classes)
44
- super unless %w[one many].include? method.to_s
60
+ super unless %w(one many).include? method.to_s
45
61
  self[method] += Microformat.send(:break_out_hashes, classes)
46
62
  end
47
63
  collector
@@ -52,7 +68,7 @@ class Microformat
52
68
  end
53
69
 
54
70
  def method_missing(method, *args, &block)
55
- super unless %w[one many].include? method.to_s
71
+ super unless %w(one many).include? method.to_s
56
72
  (collected = collector).instance_eval(&block) if block_given?
57
73
  classes = block_given? ? [args.first => collected] : break_out_hashes(args)
58
74
  @attributes[method] += classes
@@ -60,7 +76,7 @@ class Microformat
60
76
 
61
77
  def break_out_hashes(array)
62
78
  array.inject([]) do |memo, element|
63
- memo + (Hash === element ? [element.map { |k,v| { k => v } }].flatten : [element])
79
+ memo + (element.is_a?(Hash) ? [element.map { |k,v| { k => v } }].flatten : [element])
64
80
  end
65
81
  end
66
82
 
@@ -96,7 +112,7 @@ class Microformat
96
112
  end
97
113
  end
98
114
 
99
- def find_occurances(doc)
115
+ def find_occurences(doc)
100
116
  doc/".#{@container}"
101
117
  end
102
118
 
@@ -116,7 +132,7 @@ class Microformat
116
132
 
117
133
  [:one, :many].each do |name|
118
134
  attributes[name].each do |attribute|
119
- is_hash = Hash === attribute
135
+ is_hash = attribute.is_a? Hash
120
136
  key = is_hash ? attribute.keys.first : attribute
121
137
 
122
138
  # rel="bookmark" pattern
@@ -133,7 +149,7 @@ class Microformat
133
149
  raise InvalidMicroformat if found.empty? && key.to_s =~ /!/
134
150
  next if found.empty?
135
151
 
136
- if is_hash && Hash === attribute[key]
152
+ if is_hash && attribute[key].is_a?(Hash)
137
153
  built_hash = build_hash(found, attribute[key])
138
154
  key = key.no_bang
139
155
  if built_hash.size.zero? && found.size.nonzero?
@@ -152,15 +168,31 @@ class Microformat
152
168
  hash[key] = parse_element(found.first, target)
153
169
  end
154
170
  end
155
- hash[key] = hash[key].first if Array === hash[key] && hash[key].size == 1
171
+ hash[key] = hash[key].first if hash[key].is_a?(Array) && hash[key].size == 1
156
172
  end
157
173
  end
158
174
 
159
- hash
175
+ hash.merge extract_includes(doc)
176
+ end
177
+
178
+ def extract_includes(doc)
179
+ @includes ||= {}
180
+
181
+ doc.search(".include").inject({}) do |hash, element|
182
+ target = element.attributes['data'] || element.attributes['href']
183
+
184
+ return @includes[target] if @includes[target]
185
+
186
+ unless (includes = @doc/target).empty?
187
+ hash.merge @includes[target] = build_hash(includes)
188
+ else
189
+ hash
190
+ end
191
+ end
160
192
  end
161
193
 
162
194
  def extract_bookmark(doc)
163
- bookmark = doc.search("[@rel=bookmark]").first rescue nil
195
+ bookmark = doc.at("[@rel=bookmark]") rescue nil
164
196
  bookmark.attributes['href'] if bookmark.respond_to? :attributes
165
197
  end
166
198
 
@@ -176,24 +208,31 @@ class Microformat
176
208
  when 'a' then element['href']
177
209
  when 'object' then element['value']
178
210
  end
179
- elsif Class === target
211
+ elsif target.is_a? Array
212
+ target.inject(nil) do |found, klass|
213
+ klass = klass.respond_to?(:find) ? klass : nil
214
+
215
+ found || parse_element(element, klass)
216
+ end
217
+ elsif target.is_a? Class
180
218
  target.find(:first => element)
181
219
  else
182
- case element.name
220
+ value = case element.name
183
221
  when 'abbr' then element['title']
184
222
  when 'img' then element['alt']
185
- else element.innerHTML
186
- end.strip.strip_html.coerce
223
+ end || ''
224
+
225
+ (value.empty? ? element.innerHTML : value).strip.strip_html.coerce
187
226
  end
188
227
  end
189
228
 
190
229
  def prepare_value(value)
191
- Hash === value ? OpenStruct.new(value) : value
230
+ value.is_a?(Hash) ? OpenStruct.new(value) : value
192
231
  end
193
232
  end
194
233
 
195
234
  def method_missing(method, *args, &block)
196
- return super(method, *args, &block) unless method == :properties || @properties.include?(method.to_s)
235
+ return super unless method == :properties || @properties.include?(method.to_s)
197
236
  self.class.class_eval { define_method(method) { instance_variable_get("@#{method}") } }
198
237
  instance_variable_get("@#{method}")
199
238
  end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def first_or_self
3
+ size > 1 ? self : first
4
+ end
5
+ end
@@ -4,29 +4,27 @@ class Microformat
4
4
  class Simple < String
5
5
  extend Microformat::Base
6
6
 
7
- class << self
8
- def find_first(doc)
9
- find_every(doc).first
10
- end
7
+ def self.find_first(doc)
8
+ find_every(doc).first
9
+ end
11
10
 
12
- def find_occurances(doc)
13
- @from ? doc/from_as_xpath : super(doc)
14
- end
11
+ def self.find_occurences(doc)
12
+ @from ? doc/from_as_xpath : super(doc)
13
+ end
15
14
 
16
- def build_class(tag)
17
- new(tag.innerText)
18
- end
15
+ def self.build_class(tag)
16
+ new(tag.innerText)
17
+ end
19
18
 
20
- def from(options)
21
- @from ||= []
22
- options.each do |tag, value|
23
- @from << "@#{tag}=#{value}"
24
- end
19
+ def self.from(options)
20
+ @from ||= []
21
+ options.each do |tag, value|
22
+ @from << "@#{tag}=#{value}"
25
23
  end
24
+ end
26
25
 
27
- def from_as_xpath
28
- "[#{@from.to_a * "|"}]"
29
- end
26
+ def self.from_as_xpath
27
+ "[#{@from.to_a * "|"}]"
30
28
  end
31
29
  end
32
30
  end
@@ -10,7 +10,7 @@ class String
10
10
  attempt = coercions.next
11
11
  break if !attempt.nil?
12
12
  end
13
- %w[@coercions @generator].each { |i| remove_instance_variable i }
13
+ %w(@coercions @generator).each { |i| remove_instance_variable i }
14
14
  attempt.nil? ? self : attempt
15
15
  end
16
16
 
@@ -1,3 +1,3 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__), "lib"), File.join(File.dirname(__FILE__))
2
2
 
3
- %w[hentry hreview hcalendar rel_tag hcard xoxo].each { |format| require "mofo/#{format}" }
3
+ Dir['mofo/*.rb'].each { |format| require "mofo/#{format}" }
@@ -0,0 +1,6 @@
1
+ class Adr < Microformat
2
+ one :post_office_box, :extended_address, :street_address,
3
+ :locality, :region, :postal_code, :country_name, :value
4
+
5
+ many :type
6
+ end
@@ -0,0 +1,3 @@
1
+ class Geo < Microformat
2
+ one :latitude, :longitude
3
+ end
@@ -1,12 +1,15 @@
1
1
  # => http://microformats.org/wiki/hcalendar
2
2
  require 'microformat'
3
+ require 'mofo/hcard'
4
+ require 'mofo/adr'
5
+ require 'mofo/geo'
3
6
 
4
7
  class HCalendar < Microformat
5
8
  container :vevent
6
9
 
7
10
  one :class, :description, :dtend, :dtstamp, :dtstart,
8
- :duration, :location, :status, :summary, :uid,
9
- :last_modified, :url => :url
11
+ :duration, :status, :summary, :uid, :last_modified,
12
+ :url => :url, :location => [ HCard, Adr, Geo, String ]
10
13
 
11
14
  many :category
12
15
  end
@@ -1,13 +1,18 @@
1
1
  # => http://microformats.org/wiki/hcard
2
2
  require 'microformat'
3
+ require 'mofo/geo'
4
+ require 'mofo/adr'
3
5
 
4
6
  class HCard < Microformat
5
7
  container :vcard
6
8
 
7
- one :fn, :bday, :tz, :sort_string, :uid, :class
9
+ one :fn, :bday, :tz, :sort_string, :uid, :class,
10
+ :geo => Geo
11
+
8
12
  many :label, :sound, :title, :role, :key,
9
13
  :mailer, :rev, :nickname, :category, :note,
10
- :logo => :url, :url => :url, :photo => :url
14
+ :logo => :url, :url => :url, :photo => :url,
15
+ :adr => Adr
11
16
 
12
17
  one :n do
13
18
  one :family_name, :given_name, :additional_name
@@ -24,16 +29,6 @@ class HCard < Microformat
24
29
  many :value
25
30
  end
26
31
 
27
- many :adr do
28
- one :post_office_box, :extended_address, :street_address,
29
- :locality, :region, :postal_code, :country_name, :value
30
- many :type
31
- end
32
-
33
- one :geo do
34
- one :latitude, :longitude
35
- end
36
-
37
32
  many :org do
38
33
  one :organization_name, :organization_unit
39
34
  end
@@ -5,7 +5,7 @@ require 'mofo/rel_tag'
5
5
 
6
6
  class HEntry < Microformat
7
7
  one :entry_title!, :entry_summary, :updated, :published,
8
- :author => HCard
8
+ :author => HCard, :tags => RelTag
9
9
 
10
10
  many :entry_content
11
11
  end
@@ -0,0 +1,17 @@
1
+ # => http://microformats.org/wiki/hresume
2
+ require 'microformat'
3
+ require 'mofo/hcard'
4
+ require 'mofo/hcalendar'
5
+ require 'mofo/rel_tag'
6
+
7
+ class HResume < Microformat
8
+ container :hresume
9
+
10
+ one :summary, :contact => HCard
11
+
12
+ # TODO: linkedin uses a comma delimited skills list rather than the rel tags in the spec
13
+ many :skills
14
+
15
+ many :affiliation => HCard, :education => HCalendar,
16
+ :experience => HCalendar
17
+ end
@@ -0,0 +1,6 @@
1
+ # => http://microformats.org/wiki/rel-design-pattern
2
+ require 'microformat/simple'
3
+
4
+ class RelBookmark < Microformat::Simple
5
+ from :rel => :bookmark
6
+ end
@@ -0,0 +1,66 @@
1
+ $:.unshift 'lib'
2
+ require 'microformat'
3
+
4
+ class XFN < Microformat
5
+ class Link < OpenStruct
6
+ def initialize(*args)
7
+ super
8
+ def relation.has?(value)
9
+ is_a?(Array) ? include?(value) : self == value
10
+ end
11
+ end
12
+
13
+ def to_html
14
+ %[<a href="#{link}" rel="#{Array(relation) * ' '}">#{name}</a>]
15
+ end
16
+
17
+ def inspect
18
+ link.inspect
19
+ end
20
+
21
+ def to_s
22
+ link
23
+ end
24
+ end
25
+
26
+ attr_accessor :links
27
+
28
+ def self.find_occurences(doc)
29
+ @occurences ||= XFN.new(doc)
30
+ end
31
+
32
+ class << self
33
+ alias :find_first :find_occurences
34
+ alias :find_every :find_occurences
35
+ end
36
+
37
+ def initialize(doc)
38
+ @links = doc.search("a[@rel]").map do |rl|
39
+ relation = rl[:rel].include?(' ') ? rl[:rel].split(' ') : rl[:rel]
40
+ Link.new(:name => rl.innerHTML, :link => rl[:href], :relation => relation)
41
+ end
42
+ end
43
+
44
+ def relations
45
+ @relations ||= @links.map { |l| l.relation }
46
+ end
47
+
48
+ def [](*rels)
49
+ @links.select do |link|
50
+ relation = link.relation
51
+ relation.respond_to?(:all?) && rels.all? { |rel| relation.include? rel }
52
+ end.first_or_self
53
+ end
54
+
55
+ def method_missing(method, *args, &block)
56
+ method = method.to_s
57
+ if (rels = method.split(/_and_/)).size > 1
58
+ self[*rels]
59
+ elsif @links.class.public_instance_methods.include? method
60
+ @links.send(method, *args, &block)
61
+ else
62
+ check = args.first == true ? :== : :has?
63
+ @links.select { |link| link.relation.send(check, method) }.first_or_self
64
+ end
65
+ end
66
+ end