howkast 0.0.0
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/.document +5 -0
- data/.rspec +1 -0
- data/GPL-LICENSE +278 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +50 -0
- data/MIT-LICENSE +18 -0
- data/README.md +272 -0
- data/RELEASENOTES +2 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/features/howkast.feature +9 -0
- data/features/step_definitions/howkast_steps.rb +0 -0
- data/features/support/env.rb +13 -0
- data/howkast.gemspec +109 -0
- data/lib/howkast.rb +22 -0
- data/lib/howkast/api.rb +37 -0
- data/lib/howkast/base.rb +90 -0
- data/lib/howkast/configuration.rb +17 -0
- data/lib/howkast/errors.rb +18 -0
- data/lib/howkast/ext/icebox.rb +261 -0
- data/lib/howkast/ext/string.rb +27 -0
- data/lib/howkast/model.rb +36 -0
- data/lib/howkast/processors/base.rb +40 -0
- data/lib/howkast/processors/categories.rb +50 -0
- data/lib/howkast/processors/playlists.rb +53 -0
- data/lib/howkast/processors/search.rb +41 -0
- data/lib/howkast/processors/users.rb +60 -0
- data/lib/howkast/processors/videos.rb +100 -0
- data/sample/adhoc.rb +29 -0
- data/sample/quickstart.rb +69 -0
- data/spec/howkast/api_service_spec.rb +120 -0
- data/spec/howkast/api_spec.rb +24 -0
- data/spec/howkast/model_spec.rb +73 -0
- data/spec/howkast/processors/categories_spec.rb +27 -0
- data/spec/howkast/processors/playlists_spec.rb +26 -0
- data/spec/howkast/processors/search_spec.rb +26 -0
- data/spec/howkast/processors/users_spec.rb +51 -0
- data/spec/howkast/processors/videos_spec.rb +38 -0
- data/spec/howkast_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- metadata +199 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
class String
|
|
4
|
+
def modulize
|
|
5
|
+
gsub('__','/').
|
|
6
|
+
gsub(/\/(.?)/u){ "::#{$1.upcase}" }.
|
|
7
|
+
gsub(/(?:_+|-+)([a-z])/u){ $1.upcase }.
|
|
8
|
+
gsub(/(\A|\s)([a-z])/u){ $1 + $2.upcase }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def parse
|
|
12
|
+
rfc2822 = "(#{Time::RFC2822_DAY_NAME.join('|')}, )? \d{1,2} #{Time::RFC2822_MONTH_NAME.join('|')} \d{4,4} \d{2,2}:\d{2,2}:\d{2,2} [+-]\d{4,4}"
|
|
13
|
+
ansi = "#{Time::RFC2822_DAY_NAME.join('|')} #{Time::RFC2822_MONTH_NAME.join('|')} +\d{1,2} \d{2,2}:\d{2,2}:\d{2,2} [A-Z]{3,3} \d{4,4}"
|
|
14
|
+
case self
|
|
15
|
+
when /^[+-]?\d+$/u
|
|
16
|
+
to_i
|
|
17
|
+
when /^[+-]?\d+\.\d+/u
|
|
18
|
+
to_f
|
|
19
|
+
when /^(true|false)$/iu
|
|
20
|
+
self =~ /^true$/iu ? true : false
|
|
21
|
+
when /^#{rfc2822}|#{ansi}$/u
|
|
22
|
+
Time.parse(self) rescue self
|
|
23
|
+
else
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
class Howkast::Model
|
|
4
|
+
def self.synthesize name, data
|
|
5
|
+
classname = name.modulize
|
|
6
|
+
attrdef = Proc.new{ data.keys.each{ |field| attr_reader field } }
|
|
7
|
+
if const_defined? classname
|
|
8
|
+
klass = const_get classname
|
|
9
|
+
klass.class_eval &attrdef
|
|
10
|
+
klass
|
|
11
|
+
else
|
|
12
|
+
const_set classname, Class.new(self, &attrdef)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize processor, data, &block
|
|
17
|
+
data.each do |field, value|
|
|
18
|
+
value = if block and value.respond_to? :each
|
|
19
|
+
block[field, value]
|
|
20
|
+
elsif value.nil?
|
|
21
|
+
processor.default_for field
|
|
22
|
+
else
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
instance_variable_set :"@#{field}", self.class.parse(value)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def instance_attributes
|
|
30
|
+
instance_variables.map{ |name| "#{name}"[1 .. -1].to_sym }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.parse value
|
|
34
|
+
value.instance_of?(String) ? value.parse : value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Howkast::Processor
|
|
4
|
+
class Base
|
|
5
|
+
class << self
|
|
6
|
+
# The name of the processor dictates the path used to communicate to the
|
|
7
|
+
# API server. override to provide an alternate path.
|
|
8
|
+
def path
|
|
9
|
+
"#{self}".split('::').last.downcase unless "#{self}" =~ /::Base$/u
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Override to provide a value for field when field is nil.
|
|
13
|
+
def default_for field; end
|
|
14
|
+
|
|
15
|
+
# Override to massage args and options as needed.
|
|
16
|
+
def filter args, options; end
|
|
17
|
+
|
|
18
|
+
# Override to process data and restructure the returned value (eg: parse
|
|
19
|
+
# data and return a Howkast::Model::Video instance). By default it
|
|
20
|
+
# simply returns data in its 'raw' form (the Hash from HTTParty's parsed
|
|
21
|
+
# response)
|
|
22
|
+
def parse_element data
|
|
23
|
+
data
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
# Process an entry (identified by key) in data and return an Array of
|
|
28
|
+
# model. It assumes that data[key] represents a list of like data.
|
|
29
|
+
def parse_list key, data, model = nil, &block
|
|
30
|
+
list = (data || { })[key]
|
|
31
|
+
list = [list] if list.instance_of? Hash
|
|
32
|
+
list.map do |data|
|
|
33
|
+
name = (model || key)
|
|
34
|
+
klass = Howkast::Model.synthesize(name, data)
|
|
35
|
+
klass.new self, data, &block
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Category
|
|
4
|
+
# - id
|
|
5
|
+
# - name
|
|
6
|
+
# - parent_id
|
|
7
|
+
# - permalink
|
|
8
|
+
# - parents*
|
|
9
|
+
# - subcategories*
|
|
10
|
+
#
|
|
11
|
+
module Howkast::Processor
|
|
12
|
+
class Categories < Base
|
|
13
|
+
class << self
|
|
14
|
+
def filter args, options
|
|
15
|
+
args << options.delete(:id)
|
|
16
|
+
args.compact!
|
|
17
|
+
options = { }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_element data
|
|
21
|
+
expander = ->(key, value){ expand key, value }
|
|
22
|
+
if data.has_key? 'category'
|
|
23
|
+
data = data['category']
|
|
24
|
+
klass = Howkast::Model.synthesize('Category', data)
|
|
25
|
+
klass.new self, data, &expander
|
|
26
|
+
else
|
|
27
|
+
parse_list 'category', data['categories'], &expander
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default_for field
|
|
32
|
+
[] if %w{ parents categories subcategories }.include? field
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
def expand key, value
|
|
37
|
+
case key
|
|
38
|
+
when 'parents'
|
|
39
|
+
value['category'] = [value['category']] \
|
|
40
|
+
unless value['category'].instance_of? Array
|
|
41
|
+
parse_element 'categories' => value
|
|
42
|
+
when 'subcategories'
|
|
43
|
+
parse_element 'categories' => value
|
|
44
|
+
else
|
|
45
|
+
value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Playlist
|
|
4
|
+
# - created_at
|
|
5
|
+
# - description
|
|
6
|
+
# - id
|
|
7
|
+
# - permalink
|
|
8
|
+
# - playlist_thumbnail_url
|
|
9
|
+
# - rating
|
|
10
|
+
# - thumbnail_url
|
|
11
|
+
# - title
|
|
12
|
+
# - version
|
|
13
|
+
# - videos*
|
|
14
|
+
# - videos_count
|
|
15
|
+
# - views
|
|
16
|
+
#
|
|
17
|
+
module Howkast::Processor
|
|
18
|
+
class Playlists < Base
|
|
19
|
+
class << self
|
|
20
|
+
def filter args, options
|
|
21
|
+
args << options.delete(:id)
|
|
22
|
+
args.compact!
|
|
23
|
+
options = { }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_element data
|
|
27
|
+
expander = ->(key, value){ expand key, value }
|
|
28
|
+
if data.has_key? 'playlists'
|
|
29
|
+
parse_list 'playlist', data['playlists'], &expander
|
|
30
|
+
else
|
|
31
|
+
klass = Howkast::Model.synthesize('Playlist', data)
|
|
32
|
+
klass.new self, data, &expander
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def default_for field
|
|
37
|
+
[] if %w{ playlists videos }.include? field
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def expand key, value
|
|
42
|
+
case key
|
|
43
|
+
when 'playlists'
|
|
44
|
+
Playlists.parse_element 'playlists' => value
|
|
45
|
+
when 'videos'
|
|
46
|
+
Videos.parse_element 'videos' => value
|
|
47
|
+
else
|
|
48
|
+
value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Search
|
|
4
|
+
# - title
|
|
5
|
+
# - videos*
|
|
6
|
+
# - version
|
|
7
|
+
#
|
|
8
|
+
module Howkast::Processor
|
|
9
|
+
class Search < Base
|
|
10
|
+
class << self
|
|
11
|
+
def filter args, options
|
|
12
|
+
options[:q] = options.delete(:query)
|
|
13
|
+
# HACK: The Howcast API service responds with HTTP500 error if the
|
|
14
|
+
# :q parameter on search is an empty string - so we plug it with
|
|
15
|
+
# something that *might not* exist and hopefully it will return an
|
|
16
|
+
# empty list :-)
|
|
17
|
+
options[:q] = '6c6d1613339399efd0bc74fe12b14dd3' if options[:q].empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_element data
|
|
21
|
+
expander = ->(key, value){ expand key, value }
|
|
22
|
+
klass = Howkast::Model.synthesize('Search', data)
|
|
23
|
+
klass.new self, data, &expander
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def default_for field
|
|
27
|
+
[] if %w{ videos }.include? field
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
def expand key, value
|
|
32
|
+
case key
|
|
33
|
+
when 'videos'
|
|
34
|
+
Videos.parse_element 'videos' => value
|
|
35
|
+
else
|
|
36
|
+
value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# title
|
|
4
|
+
# login
|
|
5
|
+
# count
|
|
6
|
+
# views
|
|
7
|
+
# firstname
|
|
8
|
+
# lastname
|
|
9
|
+
# thumbnail_url
|
|
10
|
+
# videos
|
|
11
|
+
# version
|
|
12
|
+
#
|
|
13
|
+
#created_at
|
|
14
|
+
#description
|
|
15
|
+
#id
|
|
16
|
+
#permalink
|
|
17
|
+
#rating
|
|
18
|
+
#thumbnail_url
|
|
19
|
+
#title
|
|
20
|
+
#videos_count
|
|
21
|
+
#views
|
|
22
|
+
#
|
|
23
|
+
module Howkast::Processor
|
|
24
|
+
class Users < Base
|
|
25
|
+
class << self
|
|
26
|
+
def filter args, options
|
|
27
|
+
args << options.delete(:id)
|
|
28
|
+
args << :profile
|
|
29
|
+
args << Array(options.delete(:resource))
|
|
30
|
+
args.compact!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_element data
|
|
34
|
+
expander = ->(key, value){ expand key, value }
|
|
35
|
+
if data.has_key? 'records'
|
|
36
|
+
data['playlists'] = nil
|
|
37
|
+
data.delete 'records'
|
|
38
|
+
end
|
|
39
|
+
klass = Howkast::Model.synthesize('User', data)
|
|
40
|
+
klass.new self, data, &expander
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def default_for field
|
|
44
|
+
[] if %w{ playlists videos }.include? field
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
def expand key, value
|
|
49
|
+
case key
|
|
50
|
+
when 'playlists', 'records'
|
|
51
|
+
Playlists.parse_element 'playlists' => value
|
|
52
|
+
when 'videos'
|
|
53
|
+
Videos.parse_element 'videos' => value
|
|
54
|
+
else
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Video
|
|
4
|
+
# - badges
|
|
5
|
+
# - category_hierarchy*
|
|
6
|
+
# - category_id
|
|
7
|
+
# - comment_count
|
|
8
|
+
# - comments*
|
|
9
|
+
# - content_rating
|
|
10
|
+
# - created_at
|
|
11
|
+
# - description
|
|
12
|
+
# - duration
|
|
13
|
+
# - easy_steps
|
|
14
|
+
# - edit_url
|
|
15
|
+
# - embed
|
|
16
|
+
# - filename
|
|
17
|
+
# - filename
|
|
18
|
+
# - height
|
|
19
|
+
# - id
|
|
20
|
+
# - ingredients*
|
|
21
|
+
# - markers*
|
|
22
|
+
# - overlay*
|
|
23
|
+
# - permalink
|
|
24
|
+
# - rating
|
|
25
|
+
# - related_videos*
|
|
26
|
+
# - state
|
|
27
|
+
# - tags
|
|
28
|
+
# - thumbnail_url
|
|
29
|
+
# - title
|
|
30
|
+
# - type
|
|
31
|
+
# - username
|
|
32
|
+
# - views
|
|
33
|
+
# - width
|
|
34
|
+
#
|
|
35
|
+
# Marker
|
|
36
|
+
# - id
|
|
37
|
+
# - position
|
|
38
|
+
# - timemarker
|
|
39
|
+
# - type
|
|
40
|
+
# - thumbnail_url
|
|
41
|
+
# - title
|
|
42
|
+
# - textile_text
|
|
43
|
+
# - text
|
|
44
|
+
#
|
|
45
|
+
module Howkast::Processor
|
|
46
|
+
class Videos < Base
|
|
47
|
+
class << self
|
|
48
|
+
def filter args, options
|
|
49
|
+
args << options.delete(:id)
|
|
50
|
+
args << options.delete(:sort)
|
|
51
|
+
args << options.delete(:filter)
|
|
52
|
+
args << options.delete(:category)
|
|
53
|
+
args << options.delete(:page)
|
|
54
|
+
args.compact!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_element data
|
|
58
|
+
expander = ->(key, value){ expand key, value }
|
|
59
|
+
if data.has_key? 'video'
|
|
60
|
+
data = data['video']
|
|
61
|
+
klass = Howkast::Model.synthesize('Video', data)
|
|
62
|
+
klass.new self, data, &expander
|
|
63
|
+
else
|
|
64
|
+
parse_list 'video', data['videos'], &expander
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default_for field
|
|
69
|
+
[] if %w{ category_hierarchy ingredients markers related_videos }.include? field
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
def expand key, value
|
|
74
|
+
case key
|
|
75
|
+
when 'category_hierarchy'
|
|
76
|
+
value['category'] = case value['category']
|
|
77
|
+
when Array, String
|
|
78
|
+
Array(value['category']).map{ |category| { 'name' => category } }
|
|
79
|
+
when Hash
|
|
80
|
+
value['category']
|
|
81
|
+
end
|
|
82
|
+
Categories.parse_element 'categories' => value
|
|
83
|
+
when 'comments'
|
|
84
|
+
value['count']
|
|
85
|
+
when 'ingredients'
|
|
86
|
+
value['ingredient']
|
|
87
|
+
when 'markers'
|
|
88
|
+
parse_list 'marker', value
|
|
89
|
+
when 'related_videos'
|
|
90
|
+
parse_element 'videos' => value
|
|
91
|
+
when 'overlay'
|
|
92
|
+
klass = Howkast::Model.synthesize('Overlay', value)
|
|
93
|
+
klass.new self, value
|
|
94
|
+
else
|
|
95
|
+
value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/sample/adhoc.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
begin
|
|
4
|
+
require 'howkast'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
7
|
+
require 'howkast'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Howkast::configure YAML.load File.read('.howkast')
|
|
11
|
+
howcast = Howkast::API.new
|
|
12
|
+
video = howcast.video :id => 3907
|
|
13
|
+
puts video.category_hierarchy.first.name
|
|
14
|
+
|
|
15
|
+
video = howcast.video :id => 5072
|
|
16
|
+
puts video.category_hierarchy
|
|
17
|
+
puts video.category_hierarchy.first.name
|
|
18
|
+
|
|
19
|
+
#search = howcast.search :query => 'jujitus'
|
|
20
|
+
#puts search.videos.count
|
|
21
|
+
#
|
|
22
|
+
#search = howcast.search :query => 'jujitsu'
|
|
23
|
+
#puts search.videos.count
|
|
24
|
+
#
|
|
25
|
+
#search = howcast.search :query => ''
|
|
26
|
+
#puts search.videos.count
|
|
27
|
+
#search.videos.each do |video|
|
|
28
|
+
# puts video.title
|
|
29
|
+
#end
|