smartfm 0.3.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.
Files changed (40) hide show
  1. data/ChangeLog +38 -0
  2. data/README +45 -0
  3. data/Rakefile +155 -0
  4. data/examples/pure_ruby.rb +118 -0
  5. data/lib/ext/hash.rb +52 -0
  6. data/lib/smartfm.rb +16 -0
  7. data/lib/smartfm/core.rb +3 -0
  8. data/lib/smartfm/core/auth.rb +39 -0
  9. data/lib/smartfm/core/config.rb +51 -0
  10. data/lib/smartfm/core/version.rb +14 -0
  11. data/lib/smartfm/model.rb +5 -0
  12. data/lib/smartfm/model/base.rb +26 -0
  13. data/lib/smartfm/model/item.rb +174 -0
  14. data/lib/smartfm/model/list.rb +138 -0
  15. data/lib/smartfm/model/sentence.rb +118 -0
  16. data/lib/smartfm/model/user.rb +108 -0
  17. data/lib/smartfm/rest_client.rb +8 -0
  18. data/lib/smartfm/rest_client/base.rb +173 -0
  19. data/lib/smartfm/rest_client/item.rb +14 -0
  20. data/lib/smartfm/rest_client/list.rb +15 -0
  21. data/lib/smartfm/rest_client/sentence.rb +12 -0
  22. data/lib/smartfm/rest_client/user.rb +13 -0
  23. data/spec/ext/hash_spec.rb +11 -0
  24. data/spec/smartfm/core/auth_spec.rb +39 -0
  25. data/spec/smartfm/core/config_spec.rb +34 -0
  26. data/spec/smartfm/core/version_spec.rb +19 -0
  27. data/spec/smartfm/model/base_spec.rb +40 -0
  28. data/spec/smartfm/model/item_spec.rb +41 -0
  29. data/spec/smartfm/model/list_spec.rb +7 -0
  30. data/spec/smartfm/model/sentence_spec.rb +7 -0
  31. data/spec/smartfm/model/user_spec.rb +90 -0
  32. data/spec/smartfm/rest_client/base_spec.rb +9 -0
  33. data/spec/smartfm/rest_client/item_spec.rb +7 -0
  34. data/spec/smartfm/rest_client/list_spec.rb +7 -0
  35. data/spec/smartfm/rest_client/sentence_spec.rb +7 -0
  36. data/spec/smartfm/rest_client/user_spec.rb +7 -0
  37. data/spec/spec_helper.rb +18 -0
  38. data/test/smartfm_test.rb +8 -0
  39. data/test/test_helper.rb +3 -0
  40. metadata +132 -0
@@ -0,0 +1,51 @@
1
+ require 'singleton'
2
+
3
+ class Smartfm::Config
4
+ include Singleton
5
+ ATTRIBUTES = [ :protocol, :host, :port, :api_protocol, :api_host, :api_port, :api_key, :timeout,
6
+ :oauth_consumer_key, :oauth_consumer_secret, :oauth_http_method, :oauth_scheme,
7
+ :user_agent, :application_name, :application_version, :application_url ]
8
+ attr_accessor *ATTRIBUTES
9
+
10
+ def self.init(&block)
11
+ conf = Smartfm::Config.instance
12
+ { :protocol => 'http',
13
+ :host => 'smart.fm',
14
+ :port => 80,
15
+ :api_protocol => 'http',
16
+ :api_host => 'api.smart.fm',
17
+ :api_port => 80,
18
+ :api_key => '',
19
+ :timeout => 30,
20
+ :oauth_consumer_key => '',
21
+ :oauth_consumer_secret => '',
22
+ :oauth_http_method => :post,
23
+ :oauth_scheme => :header,
24
+ :user_agent => 'default',
25
+ :application_name => 'smart.fm gem',
26
+ :application_version => Smartfm::Version.to_version,
27
+ :application_url => 'http://github.com/nov/smartfm'
28
+ }.each do |key, value| conf.send("#{key}=", value) end
29
+ yield conf if block_given?
30
+ conf
31
+ end
32
+
33
+ def base_url
34
+ port = self.port==80 ? nil : ":#{self.port}"
35
+ "#{self.protocol}://#{self.host}#{port}"
36
+ end
37
+
38
+ def api_base_url
39
+ port = self.api_port==80 ? nil : ":#{self.api_port}"
40
+ "#{self.api_protocol}://#{self.api_host}#{port}"
41
+ end
42
+
43
+ # hack: Object.timeout is already defined..
44
+ def self.timeout
45
+ instance.timeout
46
+ end
47
+
48
+ def self.method_missing(method, *args)
49
+ Smartfm::Config.instance.send(method, *args)
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ module Smartfm::Version
2
+ MAJOR = 0
3
+ MINOR = 3
4
+ REVISION = 0
5
+ class << self
6
+ def to_version
7
+ "#{MAJOR}.#{MINOR}.#{REVISION}"
8
+ end
9
+
10
+ def to_name
11
+ "#{MAJOR}_#{MINOR}_#{REVISION}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ require 'smartfm/model/base'
2
+ require 'smartfm/model/user'
3
+ require 'smartfm/model/list'
4
+ require 'smartfm/model/item'
5
+ require 'smartfm/model/sentence'
@@ -0,0 +1,26 @@
1
+ class Smartfm::Base
2
+
3
+ def self.attributes; self::ATTRIBUTES end
4
+
5
+ def attributes; self.class.attributes end
6
+
7
+ def self.deserialize(hash, params = {})
8
+ return nil if hash.nil?
9
+
10
+ klass = params[:as] ? params[:as] : self
11
+ if hash.is_a?(Array)
12
+ hash.inject([]) { |results, hash|
13
+ hash.symbolize_keys!
14
+ results << klass.new(hash)
15
+ }
16
+ else
17
+ hash.symbolize_keys!
18
+ klass.new(hash)
19
+ end
20
+ end
21
+
22
+ def deserialize(hash, params = {})
23
+ self.class.deserialize(hash, params)
24
+ end
25
+
26
+ end
@@ -0,0 +1,174 @@
1
+ class Smartfm::Item < Smartfm::Base
2
+ ATTRIBUTES = [:sentences, :responses, :cue, :id, :list]
3
+ READONLY_ATTRIBUTES = [:sentences, :responses, :cue, :id]
4
+ attr_accessor *(ATTRIBUTES - READONLY_ATTRIBUTES)
5
+ attr_reader *READONLY_ATTRIBUTES
6
+
7
+ class Response < Smartfm::Base
8
+ ATTRIBUTES = [:text, :text_with_character, :type, :language]
9
+ READONLY_ATTRIBUTES = [:type]
10
+ attr_accessor *(ATTRIBUTES - READONLY_ATTRIBUTES)
11
+ attr_reader *READONLY_ATTRIBUTES
12
+
13
+ def initialize(params = {})
14
+ @text = params[:text]
15
+ @type = params[:type]
16
+ @language = params[:language]
17
+ end
18
+ end
19
+
20
+ class Cue < Smartfm::Base
21
+ ATTRIBUTES = [:type, :text, :image, :sound, :part_of_speech, :language, :transliterations]
22
+ READONLY_ATTRIBUTES = [:sound]
23
+ attr_accessor *(ATTRIBUTES - READONLY_ATTRIBUTES)
24
+ attr_reader *READONLY_ATTRIBUTES
25
+
26
+ def initialize(params = {})
27
+ @type = params[:type]
28
+ @text = params[:text]
29
+ @image = params[:image]
30
+ @sound = params[:sound]
31
+ @part_of_speech = params[:part_of_speech]
32
+ @language = params[:language]
33
+ @transliterations = params[:transliterations]
34
+ end
35
+ end
36
+
37
+ def self.recent(params = {})
38
+ hash = Smartfm::RestClient::Item.recent(params)
39
+ self.deserialize(hash) || []
40
+ end
41
+
42
+ def self.find(item_id, params = {})
43
+ params[:id] = item_id
44
+ hash = Smartfm::RestClient::Item.find(params)
45
+ self.deserialize(hash)
46
+ end
47
+
48
+ def self.matching(keyword, params = {})
49
+ params[:keyword] = keyword
50
+ hash = Smartfm::RestClient::Item.matching(params)
51
+ self.deserialize(hash) || []
52
+ end
53
+
54
+ def self.extract(text, params = {})
55
+ params[:text] = text
56
+ hash = Smartfm::RestClient::Item.extract(params)
57
+ self.deserialize(hash) || []
58
+ end
59
+
60
+ def self.create(auth, params = {})
61
+ self.new(params).save(auth)
62
+ end
63
+
64
+ def initialize(params = {})
65
+ params[:responses] = [params[:response]] if params[:response]
66
+ @id = params[:id].to_i
67
+ @list = params[:list]
68
+ @cue = self.deserialize(params[:cue], :as => Smartfm::Item::Cue)
69
+ @responses = self.deserialize(params[:responses], :as => Smartfm::Item::Response)
70
+ @sentences = self.deserialize(params[:sentences], :as => Smartfm::Sentence)
71
+ end
72
+
73
+ def save(auth)
74
+ begin
75
+ item_id = Smartfm::RestClient::Item.create(auth, self.to_post_data)
76
+ rescue
77
+ return false
78
+ end
79
+ Smartfm::Item.find(item_id)
80
+ end
81
+
82
+ def add_image(auth, params)
83
+ post_params = if params.is_a?(String)
84
+ { 'image[url]' => params }
85
+ else
86
+ image_params = {
87
+ 'image[url]' => params[:url],
88
+ 'image[list_id]' => params[:list_id]
89
+ }
90
+ if params[:attribution]
91
+ attribution_params = {
92
+ 'attribution[media_entity]' => params[:attribution][:media_entity],
93
+ 'attribution[author]' => params[:attribution][:media_entity],
94
+ 'attribution[author_url]' => params[:attribution][:media_entity],
95
+ 'attribution[attribution_license_id]' => params[:attribution][:media_entity]
96
+ }
97
+ image_params.merge(attribution_params)
98
+ else
99
+ image_params
100
+ end
101
+ end
102
+ Smartfm::RestClient::Item.add_image(auth, post_params.merge(:id => self.id))
103
+ end
104
+
105
+ def add_sound(auth, params)
106
+ post_params = if params.is_a?(String)
107
+ { 'sound[url]' => params }
108
+ else
109
+ sound_params = {
110
+ 'sound[url]' => params[:url],
111
+ 'sound[list_id]' => params[:list_id]
112
+ }
113
+ if params[:attribution]
114
+ attribution_params = {
115
+ 'attribution[media_entity]' => params[:attribution][:media_entity],
116
+ 'attribution[author]' => params[:attribution][:media_entity],
117
+ 'attribution[author_url]' => params[:attribution][:media_entity],
118
+ 'attribution[attribution_license_id]' => params[:attribution][:media_entity]
119
+ }
120
+ sound_params.merge(attribution_params)
121
+ else
122
+ sound_params
123
+ end
124
+ end
125
+ Smartfm::RestClient::Item.add_sound(auth, post_params.merge(:id => self.id))
126
+ end
127
+
128
+ def add_tags(auth, *tags)
129
+ post_params = {}
130
+ tags.each_with_index do |tag, idx|
131
+ if tag.is_a?(String)
132
+ post_params["semantic_tags[#{idx}][name]"] = tag
133
+ else
134
+ post_params["semantic_tags[#{idx}][name]"] = tag[:name]
135
+ post_params["semantic_tags[#{idx}][disambiguation]"] = tag[:disambiguation]
136
+ end
137
+ end
138
+ Smartfm::RestClient::Item.add_tags(auth, post_params.merge(:id => self.id))
139
+ end
140
+
141
+ protected
142
+
143
+ def to_post_data
144
+ self.validate
145
+ post_data = {
146
+ 'cue[text]' => self.cue.text,
147
+ 'cue[language]' => self.cue.language,
148
+ 'cue[part_of_speech]' => self.cue.part_of_speech,
149
+ 'response[text]' => self.response.text,
150
+ 'response[language]' => self.response.language
151
+ }
152
+ # Optional attributes
153
+ if self.list
154
+ post_data['item[list_id]'] = self.list.id
155
+ end
156
+ if response.text_with_character
157
+ post_data['character_response[text]'] = self.response.character_text
158
+ end
159
+ post_data
160
+ end
161
+
162
+ def validate
163
+ raise ArgumentError.new("Item cue[text] is required.") if self.cue.text.nil? or self.cue.text.empty?
164
+ raise ArgumentError.new("Item cue[language] is required.") if self.cue.language.nil? or self.cue.language.empty?
165
+ raise ArgumentError.new("Item cue[part_of_speech] is required.") if self.cue.part_of_speech.nil? or self.cue.part_of_speech.empty?
166
+ raise ArgumentError.new("Item response[text] is required.") if self.response.text.nil? or self.response.text.empty?
167
+ raise ArgumentError.new("Item response[language] is required.") if self.response.language.nil? or self.response.language.empty?
168
+ end
169
+
170
+ def response
171
+ self.responses.first
172
+ end
173
+
174
+ end
@@ -0,0 +1,138 @@
1
+ class Smartfm::List < Smartfm::Base
2
+ ATTRIBUTES = [:id, :title, :description, :icon, :square_icon, :item_count, :user_count, :iknow, :dictation, :brainspeed,
3
+ :language, :translation_language, :list_type, :transcript, :embed,
4
+ :tags, :media_entry, :author, :author_id, :author_url, :attribution_license_id,
5
+ :items, :sentences]
6
+ READONLY_ATTRIBUTES = [:id, :icon, :item_count, :user_count, :iknow, :dictation, :brainspeed]
7
+ attr_accessor *(ATTRIBUTES - READONLY_ATTRIBUTES)
8
+ attr_reader *READONLY_ATTRIBUTES
9
+
10
+ class Application < Smartfm::Base
11
+ ATTRIBUTES = [:application, :list_id, :lang]
12
+ attr_reader *ATTRIBUTES
13
+
14
+ def initialize(params = {})
15
+ @application = params[:application]
16
+ @list_id = params[:list_id]
17
+ @lang = params[:lang]
18
+ end
19
+ def url
20
+ "http://smart.fm/flash?swf=#{self.name}&course_id=#{self.list_id}&lang=#{self.lang}"
21
+ end
22
+ end
23
+
24
+ def self.recent(params = {})
25
+ hash = Smartfm::RestClient::List.recent(params)
26
+ self.deserialize(hash) || []
27
+ end
28
+
29
+ def self.find(list_id, params = {})
30
+ params[:id] = list_id
31
+ hash = Smartfm::RestClient::List.find(params)
32
+ self.deserialize(hash)
33
+ end
34
+
35
+ def self.matching(keyword, params = {})
36
+ params[:keyword] = keyword
37
+ hash = Smartfm::RestClient::List.matching(params)
38
+ self.deserialize(hash) || []
39
+ end
40
+
41
+ def self.create(auth, params = {})
42
+ self.new(params).save(auth)
43
+ end
44
+
45
+ def self.delete(list_id)
46
+ self.find(list_id).delete
47
+ end
48
+
49
+ def initialize(params = {})
50
+ @id = (params[:id].to_i rescue nil)
51
+ @title = params[:title]
52
+ @description = params[:description]
53
+ @icon = params[:icon]
54
+ @square_icon = params[:square_icon]
55
+ @item_count = (params[:item_count].to_i rescue nil)
56
+ @user_count = (params[:user_count].to_i rescue nil)
57
+ @language = params[:language]
58
+ @translation_language = params[:translation_language]
59
+ if @list_id and @translation_language
60
+ common_settings = {:list_id => @id, :lang => @translation_language}
61
+ @iknow = Application.new(common_settings.merge(:application => 'iknow')) if params[:iknow]
62
+ @dictation = Application.new(common_settings.merge(:application => 'dictation')) if params[:dictation]
63
+ @brainspeed = Application.new(common_settings.merge(:application => 'brainspeed')) if params[:brainspeed]
64
+ end
65
+ @author = params[:author] # display_name or username
66
+ @author_id = params[:author_id] # username
67
+ @author_url = params[:author_url]
68
+ @list_type = params[:list_type] # for list creation
69
+ @transcript = params[:transcript] # for list creation
70
+ @embed = params[:embed] # for list creation
71
+ @tags = params[:tags] # for list creation
72
+ @media_entry = params[:media_entry] # for list creation
73
+ @attribution_license_id = params[:attribution_license_id] # for list creation
74
+ @items = self.deserialize(params[:items], :as => Smartfm::Item)
75
+ @sentences = self.deserialize(params[:sentences], :as => Smartfm::Sentence)
76
+ end
77
+
78
+ def items(params = {})
79
+ hash = Smartfm::RestClient::List.items(params.merge(:id => self.id))
80
+ self.deserialize(hash, :as => Smartfm::Item) || []
81
+ end
82
+
83
+ def sentences(params = {})
84
+ hash = Smartfm::RestClient::List.sentences(params.merge(:id => self.id))
85
+ self.deserialize(hash, :as => Smartfm::Sentence) || []
86
+ end
87
+
88
+ def save(auth)
89
+ begin
90
+ list_id = Smartfm::RestClient::List.create(auth, self.to_post_data)
91
+ rescue
92
+ return false
93
+ end
94
+ Smartfm::List.find(list_id)
95
+ end
96
+
97
+ def delete(auth)
98
+ Smartfm::RestClient::List.delete(auth, {:id => self.id})
99
+ end
100
+ alias_method :destroy, :delete
101
+
102
+ def add_item(auth, item)
103
+ Smartfm::RestClient::List.add_item(auth, {:list_id => self.id, :id => item.id})
104
+ end
105
+
106
+ def delete_item(auth, item)
107
+ Smartfm::RestClient::List.delete_item(auth, {:list_id => self.id, :id => item.id})
108
+ end
109
+
110
+ protected
111
+
112
+ def to_post_data
113
+ self.validate
114
+ post_data = {
115
+ 'list[name]' => self.title,
116
+ 'list[description]' => self.description,
117
+ 'list[language]' => self.language || 'en',
118
+ 'list[translation_language]' => self.translation_language || 'ja'
119
+ }
120
+ # Optional attributes
121
+ if self.list_type
122
+ post_data['list[type]'] = self.list_type
123
+ end
124
+ [ :transcript, :embed, :tags, :media_entry,
125
+ :author, :author_url, :attribution_license_id ].each do |key|
126
+ if self.send("#{key}")
127
+ post_data["list[#{key}]"] = self.send("#{key}")
128
+ end
129
+ end
130
+ post_data
131
+ end
132
+
133
+ def validate
134
+ raise ArgumentError.new("List title is required.") if self.title.nil? or self.title.empty?
135
+ raise ArgumentError.new("List description is required.") if self.description.nil? or self.description.empty?
136
+ end
137
+
138
+ end
@@ -0,0 +1,118 @@
1
+ class Smartfm::Sentence < Smartfm::Base
2
+ ATTRIBUTES = [:sound, :image, :square_image, :text, :language, :id, :transliterations, :translations, :item, :list]
3
+ READONLY_ATTRIBUTES = [:id]
4
+ attr_accessor *(ATTRIBUTES - READONLY_ATTRIBUTES)
5
+ attr_reader *READONLY_ATTRIBUTES
6
+
7
+ def self.recent(params = {})
8
+ hash = Smartfm::RestClient::Sentence.recent(params)
9
+ self.deserialize(hash) || []
10
+ end
11
+
12
+ def self.find(sentence_id, params = {})
13
+ params[:id] = sentence_id
14
+ hash = Smartfm::RestClient::Sentence.find(params)
15
+ self.deserialize(hash)
16
+ end
17
+
18
+ def self.matching(keyword, params = {})
19
+ params[:keyword] = keyword
20
+ hash = Smartfm::RestClient::Sentence.matching(params)
21
+ self.deserialize(hash) || []
22
+ end
23
+
24
+ def self.create(auth, params = {})
25
+ self.new(params).save(auth)
26
+ end
27
+
28
+ def initialize(params = {})
29
+ params[:translations] = [params[:translation]] if params[:translation]
30
+ params[:transliterations] = [params[:transliteration]] if params[:transliteration]
31
+ @id = params[:id]
32
+ @item = params[:item]
33
+ @list = params[:list]
34
+ @sound = params[:sound]
35
+ @image = params[:image]
36
+ @square_image = params[:square_image]
37
+ @text = params[:text]
38
+ @language = params[:language]
39
+ @transliterations = params[:transliterations]
40
+ @translations = self.deserialize(params[:translations], :as => Smartfm::Sentence)
41
+ end
42
+
43
+ def save(auth)
44
+ begin
45
+ sentence_id = Smartfm::RestClient::Sentence.create(auth, self.to_post_data)
46
+ rescue
47
+ return false
48
+ end
49
+ Smartfm::Sentence.find(sentence_id)
50
+ end
51
+
52
+ def add_image(auth, params)
53
+ post_params = if params.is_a?(String)
54
+ {'image[url]' => params}
55
+ else
56
+ {'image[url]' => params[:url], 'image[list_id]' => params[:list_id]}.merge(attribution_params(params[:attribution]))
57
+ end
58
+ Smartfm::RestClient::Sentence.add_image(auth, post_params.merge(:id => self.id))
59
+ end
60
+
61
+ def add_sound(auth, params)
62
+ post_params = if params.is_a?(String)
63
+ {'sound[url]' => params}
64
+ else
65
+ {'sound[url]' => params[:url], 'sound[list_id]' => params[:list_id]}.merge(attribution_params(params[:attribution]))
66
+ end
67
+ Smartfm::RestClient::Sentence.add_sound(auth, post_params.merge(:id => self.id))
68
+ end
69
+
70
+ protected
71
+
72
+ def to_post_data
73
+ self.validate
74
+ post_data = {
75
+ 'item_id' => self.item.id,
76
+ 'sentence[text]' => self.text
77
+ }
78
+ # Optional attributes
79
+ if self.list
80
+ post_data['sentence[list_id]'] = self.list.id
81
+ end
82
+ [:language, :transliteration].each do |key|
83
+ if self.send("#{key}")
84
+ post_data["sentence[#{key}]"] = self.send("#{key}")
85
+ end
86
+ end
87
+ if self.translation
88
+ [:text, :language, :transliteration].each do |key|
89
+ if self.translation.send("#{key}")
90
+ post_data["translation[#{key}]"] = self.translation.send("#{key}")
91
+ end
92
+ end
93
+ end
94
+ post_data
95
+ end
96
+
97
+ def validate
98
+ raise ArgumentError.new("Item is required.") unless self.item
99
+ raise ArgumentError.new("Sentence text is required.") if self.text.nil? or self.text.empty?
100
+ end
101
+
102
+ def translation
103
+ self.translations.first rescue nil
104
+ end
105
+
106
+ def transliteration
107
+ self.transliterations.first rescue nil
108
+ end
109
+
110
+ def attribution_params(attr_params)
111
+ return {} unless attr_params
112
+ {'attribution[medias_entity]' => attr_params[:media_entity],
113
+ 'attribution[author]' => attr_params[:author],
114
+ 'attribution[author_url]' => attr_params[:author_url],
115
+ 'attributions[attribution_license_id]' => attr_params[:attribution_license_id] }
116
+ end
117
+
118
+ end