howcast 0.7.4 → 0.7.15
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/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +38 -0
- data/{README.markdown → README.md} +44 -13
- data/Rakefile +6 -39
- data/VERSION +1 -1
- data/fixtures/categories.xml +2230 -0
- data/fixtures/category.1585.xml +24 -0
- data/fixtures/homepage.staff_videos.xml +315 -0
- data/fixtures/invalid.api_key.xml +6 -0
- data/fixtures/playlist.4566.xml +484 -0
- data/fixtures/users.someone.profile.videos.xml +440 -0
- data/fixtures/video.233.generated.xml +1563 -0
- data/fixtures/video.233.xml +830 -0
- data/howcast.gemspec +31 -90
- data/lib/howcast.rb +1 -1
- data/lib/howcast/client.rb +1 -1
- data/lib/howcast/client/base.rb +232 -188
- data/lib/howcast/client/category.rb +31 -13
- data/lib/howcast/client/homepage.rb +10 -8
- data/lib/howcast/client/marker.rb +2 -0
- data/lib/howcast/client/playlist.rb +2 -0
- data/lib/howcast/client/search.rb +11 -11
- data/lib/howcast/client/type.rb +48 -0
- data/lib/howcast/client/user.rb +2 -0
- data/lib/howcast/client/utils.rb +53 -0
- data/lib/howcast/client/video.rb +39 -25
- data/lib/howcast/ext/string.rb +8 -0
- data/lib/howcast/hpricot/elements.rb +22 -0
- data/lib/howcast/version.rb +3 -0
- data/script/github-test.rb +24 -0
- data/spec/howcast/client/base_spec.rb +2 -2
- data/spec/howcast/client/category_spec.rb +41 -2
- data/spec/howcast/client/homepage_spec.rb +8 -8
- data/spec/howcast/client/playlist_spec.rb +6 -4
- data/spec/howcast/client/search_spec.rb +7 -8
- data/spec/howcast/client/user_spec.rb +9 -7
- data/spec/howcast/client/video_spec.rb +106 -22
- data/spec/spec_helper.rb +4 -7
- data/spec/xml_fixtures_helper.rb +20 -2895
- metadata +94 -28
- data/CHANGELOG +0 -108
- data/Manifest +0 -19
- data/howcast-0.7.3.gem +0 -0
- data/spec/string_matchers_helper.rb +0 -22
@@ -24,6 +24,8 @@
|
|
24
24
|
class Howcast::Client
|
25
25
|
class Category
|
26
26
|
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
27
29
|
attr_accessor :id, :name, :parent_id, :parents, :permalink
|
28
30
|
|
29
31
|
# Creates a new Category object which is used to encapsulate all the attributes available
|
@@ -84,21 +86,37 @@ class Howcast::Client
|
|
84
86
|
# Get the top level Howcast categories
|
85
87
|
# Howcast::Client.new.categories
|
86
88
|
def categories(options = {})
|
87
|
-
|
88
|
-
establish_connection(uri).at('categories').children_of_type('category').inject([]){ |r, i| r << parse_single_category_xml(i)}
|
89
|
+
fetch_categories.at('categories').children_of_type('category').inject([]){ |r, i| r << parse_single_category_xml(i) }
|
89
90
|
end
|
90
91
|
|
92
|
+
def category_id_for key
|
93
|
+
fetch_categories do |categories|
|
94
|
+
node = (categories/"//category/name[text-downcase()='#{key.downcase}']/../permalink")
|
95
|
+
node = (categories/"//category/permalink[text-downcase()*='#{key.downcase}']") if node.empty?
|
96
|
+
node.text.split("/").last unless node.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
91
100
|
private
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
node_name = attribute.to_s.gsub("_", "-") # xml schema uses hyphens for spaces, but ruby uses underscores
|
99
|
-
hash[attribute] = !xml.at(node_name).nil? ? xml.at(node_name).inner_text.strip : ""
|
100
|
-
end
|
101
|
-
hash[:parents] = (xml.at('parents')/:category).map{ |c| {:id => c.at('id').inner_text.strip, :name => c.at('name').inner_text.strip, :permalink => c.at('permalink').inner_text.strip }} unless xml.at('parents').nil?
|
102
|
-
hash.values.all?{|v| v==""} ? nil : Category.new(hash)
|
101
|
+
def fetch_categories
|
102
|
+
@categories ||= establish_connection("categories.xml")
|
103
|
+
if block_given?
|
104
|
+
yield @categories
|
105
|
+
else
|
106
|
+
@categories
|
103
107
|
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Exception here to parse the <parents> tag in a category, will set a category.parents variable
|
111
|
+
# which is an array of parent metadata hases
|
112
|
+
# [{:id => '123', :name => "root"}, {:id => "1234", :name => "parent"}]
|
113
|
+
def parse_single_category_xml(xml)
|
114
|
+
hash = {}
|
115
|
+
Category.attr_accessors.each do |attribute|
|
116
|
+
node_name = attribute.to_s.gsub("_", "-") # xml schema uses hyphens for spaces, but ruby uses underscores
|
117
|
+
hash[attribute] = !xml.at(node_name).nil? ? xml.at(node_name).inner_text.strip : ""
|
118
|
+
end
|
119
|
+
hash[:parents] = (xml.at('parents')/:category).map{ |c| {:id => c.at('id').inner_text.strip, :name => c.at('name').inner_text.strip, :permalink => c.at('permalink').inner_text.strip }} unless xml.at('parents').nil?
|
120
|
+
hash.values.all?{|v| v==""} ? nil : Category.new(hash)
|
121
|
+
end
|
104
122
|
end
|
@@ -24,6 +24,8 @@
|
|
24
24
|
class Howcast::Client
|
25
25
|
class Homepage
|
26
26
|
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
27
29
|
attr_accessor :videos, :playlists
|
28
30
|
|
29
31
|
# Creates a new Homepage object which is used to encapsulate all the attributes available
|
@@ -65,12 +67,12 @@ class Howcast::Client
|
|
65
67
|
end
|
66
68
|
|
67
69
|
private
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
70
|
+
def parse_playlists(xml)
|
71
|
+
playlists = []
|
72
|
+
node = xml.at('playlists')
|
73
|
+
node.children_of_type('playlist').each do |child|
|
74
|
+
playlists << parse_single_xml(child, Playlist)
|
75
|
+
end unless node.nil?
|
76
|
+
playlists
|
77
|
+
end
|
76
78
|
end
|
@@ -24,6 +24,8 @@
|
|
24
24
|
class Howcast::Client
|
25
25
|
class Marker
|
26
26
|
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
27
29
|
attr_accessor :id, :position, :timemarker, :type, :thumbnail_url, :title, :textile_text, :text
|
28
30
|
|
29
31
|
# Creates a new Marker object which is used to encapsulate all the attributes available
|
@@ -24,6 +24,8 @@
|
|
24
24
|
class Howcast::Client
|
25
25
|
class Playlist
|
26
26
|
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
27
29
|
attr_accessor :id, :title, :description, :playlist_thumbnail_url, :videos
|
28
30
|
|
29
31
|
# Creates a new Playlist object which is used to encapsulate all the attributes available
|
@@ -71,16 +71,16 @@ class Howcast::Client
|
|
71
71
|
end
|
72
72
|
|
73
73
|
private
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
def do_search params
|
75
|
+
uri = search_url params
|
76
|
+
(establish_connection(uri)/:video).inject([]){ |r, i| r << parse_single_xml(i, Video)}
|
77
|
+
end
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
def search_url params
|
80
|
+
uri = "search.xml?"
|
81
|
+
params = params.inject({}){ |h, (k, v)| h[k] = CGI::escape(v.to_s); h }
|
82
|
+
uri += hash_to_params params
|
83
|
+
uri += uri_suffix(params.merge(:use_ampersand => true)) unless params[:page]
|
84
|
+
uri
|
85
|
+
end
|
86
86
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 Howcast Media Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Howcast::Client
|
25
|
+
class Type
|
26
|
+
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
29
|
+
attr_accessor :kind, :status, :name
|
30
|
+
|
31
|
+
# Creates a new Type object which is used to encapsulate all the attributes available
|
32
|
+
# from the Howcast Video API.
|
33
|
+
#
|
34
|
+
# === Inputs
|
35
|
+
#
|
36
|
+
# * <tt>attributes</tt> -- A hash to set the various attributes of the marker object
|
37
|
+
#
|
38
|
+
# === Examples
|
39
|
+
#
|
40
|
+
# Initialize a type
|
41
|
+
# Type.new :name => "HowcastGuide", :kind => "HowcastOriginalGuide", :status => "proprietary"
|
42
|
+
def initialize(attributes={})
|
43
|
+
attributes.each do |k, v|
|
44
|
+
self.send("#{k}=", v) if self.respond_to?(k)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/howcast/client/user.rb
CHANGED
@@ -24,6 +24,8 @@
|
|
24
24
|
class Howcast::Client
|
25
25
|
class User
|
26
26
|
extend WatchAttrAccessors
|
27
|
+
include XmlMethods
|
28
|
+
|
27
29
|
attr_accessor :login, :firstname, :lastname, :thumbnail_url, :views, :count, :videos
|
28
30
|
|
29
31
|
# Creates a new User object which is used to encapsulate all the attributes available
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# utility modules for models (eg: video, marker, etc)
|
2
|
+
|
3
|
+
module WatchAttrAccessors
|
4
|
+
def attr_accessor(*args)
|
5
|
+
super(*args)
|
6
|
+
@attr_accessors ||= []
|
7
|
+
@attr_accessors += args
|
8
|
+
end
|
9
|
+
|
10
|
+
def attr_accessors
|
11
|
+
@attr_accessors || []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module XmlMethods
|
16
|
+
def to_doc
|
17
|
+
doc = Nokogiri::XML::Document.new
|
18
|
+
node = Nokogiri::XML::Node.new(self.class.to_s.sub(/([a-z]+::)+/i, '').downcase, doc)
|
19
|
+
root = doc.add_child node
|
20
|
+
self.class.attr_accessors.each do |attr|
|
21
|
+
node = Nokogiri::XML::Node.new("#{attr}", doc)
|
22
|
+
value = self.send(attr)
|
23
|
+
if value.instance_of? String
|
24
|
+
value = inflect(attr, value) if respond_to? :inflect
|
25
|
+
if value.instance_of? String
|
26
|
+
node.content = value
|
27
|
+
else
|
28
|
+
node.add_child value
|
29
|
+
end
|
30
|
+
root.add_child node
|
31
|
+
elsif value.respond_to? :to_doc
|
32
|
+
node = value.to_doc.root
|
33
|
+
root.add_child node
|
34
|
+
elsif value.instance_of? Array
|
35
|
+
value.each do |v|
|
36
|
+
if v.respond_to? :to_doc
|
37
|
+
node.add_child v.to_doc.root
|
38
|
+
else
|
39
|
+
child = Nokogiri::XML::Node.new("#{attr}".singularize, doc)
|
40
|
+
child.content = v.to_s
|
41
|
+
node.add_child child
|
42
|
+
end
|
43
|
+
end
|
44
|
+
root.add_child node
|
45
|
+
end
|
46
|
+
end
|
47
|
+
doc
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_xml
|
51
|
+
to_doc.to_xml
|
52
|
+
end
|
53
|
+
end
|
data/lib/howcast/client/video.rb
CHANGED
@@ -22,24 +22,14 @@
|
|
22
22
|
#++
|
23
23
|
|
24
24
|
class Howcast::Client
|
25
|
-
module WatchAttrAccessors
|
26
|
-
def attr_accessor(*args)
|
27
|
-
super(*args)
|
28
|
-
@attr_accessors ||= []
|
29
|
-
@attr_accessors += args
|
30
|
-
end
|
31
|
-
|
32
|
-
def attr_accessors
|
33
|
-
@attr_accessors || []
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
25
|
class Video
|
38
26
|
extend WatchAttrAccessors
|
39
|
-
|
27
|
+
include XmlMethods
|
28
|
+
|
29
|
+
attr_accessor :id, :title, :permalink, :thumbnail_url, :category_id,
|
40
30
|
:views, :username, :duration, :created_at, :rating, :description, :width, :height,
|
41
31
|
:badges, :easy_steps, :embed, :category_hierarchy, :ingredients, :markers, :related_videos,
|
42
|
-
:filename
|
32
|
+
:filename, :mature_content, :ads_allowed, :playlist_memberships, :type
|
43
33
|
|
44
34
|
# Creates a new Video object which is used to encapsulate all the attributes available
|
45
35
|
# from the Howcast Video API
|
@@ -57,11 +47,34 @@ class Howcast::Client
|
|
57
47
|
self.send("#{k}=", v) if self.respond_to?(k)
|
58
48
|
end
|
59
49
|
end
|
60
|
-
|
50
|
+
|
61
51
|
# Return true if the video contains easy step by step directions, else false
|
62
52
|
def easy_steps?
|
63
53
|
easy_steps == "true"
|
64
54
|
end
|
55
|
+
|
56
|
+
def mature_content?
|
57
|
+
mature_content == "true"
|
58
|
+
end
|
59
|
+
|
60
|
+
def ads_allowed?
|
61
|
+
ads_allowed == "true"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_param
|
65
|
+
id
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
def inflect attr, value
|
70
|
+
if %w{ ads_allowed mature_content easy_steps }.include? "#{attr}"
|
71
|
+
(value.empty? ? false : value).to_s
|
72
|
+
elsif "#{attr}" == "embed"
|
73
|
+
Nokogiri::XML(value).root
|
74
|
+
else
|
75
|
+
value.to_s
|
76
|
+
end
|
77
|
+
end
|
65
78
|
end
|
66
79
|
|
67
80
|
# Provides access to the Howcast video API.
|
@@ -108,15 +121,16 @@ class Howcast::Client
|
|
108
121
|
# Howcast::Client.new.videos
|
109
122
|
# Get the third page of top favorites which are featured
|
110
123
|
# Howcast::Client.new.videos(:page => 3, :sort => "top_favorites", :filter => "top_rated")
|
111
|
-
|
124
|
+
def videos(options = {})
|
112
125
|
uri = videos_url(options)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
126
|
+
doc = establish_connection(uri)
|
127
|
+
(doc/:video).inject([]){ |r, i| r << parse_single_xml(i, Video) }
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
def videos_url(options={})
|
132
|
+
defaults = {:page => nil, :sort => "most_recent", :filter => "howcast_studios", :category_id => nil}
|
133
|
+
options = defaults.update(options)
|
134
|
+
"videos/#{options[:sort]}/#{options[:filter]}#{"/#{options[:category_id]}" if options[:category_id]}#{"/#{options[:page]}" if options[:page]}.xml"
|
135
|
+
end
|
122
136
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hpricot
|
2
|
+
module Traverse
|
3
|
+
oper_procs =
|
4
|
+
{'=' => proc{ |a,b| a == b },
|
5
|
+
'!=' => proc{ |a,b| a != b },
|
6
|
+
'~=' => proc{ |a,b| a.split(/\s+/).include?(b) },
|
7
|
+
'|=' => proc{ |a,b| a =~ /^#{Regexp::quote b}(-|$)/ },
|
8
|
+
'^=' => proc{ |a,b| a.index(b) == 0 },
|
9
|
+
'$=' => proc{ |a,b| a =~ /#{Regexp::quote b}$/ },
|
10
|
+
'*=' => proc{ |a,b| idx = a.index(b) }}
|
11
|
+
|
12
|
+
pred_n = 'text-downcase()'
|
13
|
+
pred_f = proc{ |ele, *_| ele.inner_text.strip.downcase }
|
14
|
+
|
15
|
+
oper_procs.each do |oper_n, oper_f|
|
16
|
+
filter "#{pred_n}#{oper_n}" do |*a|
|
17
|
+
qual = pred_f[self, *a]
|
18
|
+
oper_f[qual, a[-2]] if qual
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
if ARGV.size < 1
|
5
|
+
puts "Usage: github-test.rb my-project.gemspec"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'rubygems/specification'
|
11
|
+
|
12
|
+
data = File.read(ARGV[0])
|
13
|
+
spec = nil
|
14
|
+
|
15
|
+
if data !~ %r{!ruby/object:Gem::Specification}
|
16
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
17
|
+
else
|
18
|
+
spec = YAML.load(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.validate
|
22
|
+
|
23
|
+
puts spec
|
24
|
+
puts "OK"
|
@@ -18,8 +18,8 @@ describe Howcast::Client, "base_uri" do
|
|
18
18
|
Howcast::Client.instance_variable_set :@base_uri, nil
|
19
19
|
end
|
20
20
|
|
21
|
-
it "should use a default base_uri of
|
22
|
-
Howcast::Client.base_uri.to_s.should == "http://
|
21
|
+
it "should use a default base_uri of api.howcast.com" do
|
22
|
+
Howcast::Client.base_uri.to_s.should == "http://api.howcast.com"
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should allow easily setting the base_url" do
|
@@ -20,7 +20,7 @@ describe Howcast::Client, "category" do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should establish a connection with the correct category url" do
|
23
|
-
@hc.should_receive(:open).with(
|
23
|
+
@hc.should_receive(:open).with(URI.parse "http://api.howcast.com/categories/2.xml?api_key=myapikey").and_return(category_xml)
|
24
24
|
@hc.category(2)
|
25
25
|
end
|
26
26
|
|
@@ -54,7 +54,7 @@ describe Howcast::Client, "categories" do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
it "should establish a connection with categories.xml" do
|
57
|
-
@hc.should_receive(:open).with(
|
57
|
+
@hc.should_receive(:open).with(URI.parse "http://api.howcast.com/categories.xml?api_key=myapikey").and_return(categories_xml)
|
58
58
|
@hc.categories
|
59
59
|
end
|
60
60
|
|
@@ -66,3 +66,42 @@ describe Howcast::Client, "categories" do
|
|
66
66
|
categories.last.name.should == 'Travel'
|
67
67
|
end
|
68
68
|
end
|
69
|
+
|
70
|
+
describe Howcast::Client, "category_id_for" do
|
71
|
+
before :each do
|
72
|
+
@hc = Howcast::Client.new(:key => "myapikey")
|
73
|
+
@hc.stub!(:open).and_return(categories_xml)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be able to fetch the category id of a top-level category" do
|
77
|
+
@hc.category_id_for("Arts & Media").should == "1-Arts-and-Media"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should be able to fetch the category id of a top-level category in a case-insensitive manner" do
|
81
|
+
@hc.category_id_for("arts & media").should == "1-Arts-and-Media"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should be able to fetch the category id of a subcategory" do
|
85
|
+
@hc.category_id_for("Animation Techniques").should == "6-Animation-Techniques"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should be able to fetch the category id of a subcategory in a case-insensitive manner" do
|
89
|
+
@hc.category_id_for("animation techniques").should == "6-Animation-Techniques"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should be able to fetch the category id given a permalink id" do
|
93
|
+
@hc.category_id_for("375-Asian-Cooking").should == "375-Asian-Cooking"
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be able to fetch the category id given a permalink id in a case-insensitive manner" do
|
97
|
+
@hc.category_id_for("375-asian-cooking").should == "375-Asian-Cooking"
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return nothing if the category name does not exist" do
|
101
|
+
@hc.category_id_for("This Category Does Not Exist").should be nil
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should return nothing if the permalink id does not exist" do
|
105
|
+
@hc.category_id_for("99999-This-Category-Does-Not-Exist").should be nil
|
106
|
+
end
|
107
|
+
end
|