wordnik 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ module Wordnik
2
+
3
+ class Endpoint
4
+ require 'active_model'
5
+ include ActiveModel::Validations
6
+ include ActiveModel::Conversion
7
+ extend ActiveModel::Naming
8
+
9
+ attr_accessor :path, :description, :operations
10
+
11
+ validates_presence_of :path, :description, :operations
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |name, value|
15
+ send("#{name.to_s.underscore.to_sym}=", value)
16
+ end
17
+
18
+ # Generate Operations instances from JSON
19
+ if self.operations
20
+ self.operations = self.operations.map do |operationData|
21
+ Operation.new(operationData)
22
+ end
23
+ end
24
+ end
25
+
26
+ # It's an ActiveModel thing..
27
+ def persisted?
28
+ false
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,26 @@
1
+ # The methods below were snatch right outta Rails' ActiveSupport, so we don't have to depend on it..
2
+ # See http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-camelize
3
+
4
+ class String
5
+
6
+ # File activesupport/lib/active_support/inflector/methods.rb, line 48
7
+ def underscore(camel_cased_word)
8
+ word = camel_cased_word.to_s.dup
9
+ word.gsub!(/::/, '/')
10
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
11
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
12
+ word.tr!("-", "_")
13
+ word.downcase!
14
+ word
15
+ end
16
+
17
+ # File activesupport/lib/active_support/inflector/methods.rb, line 28
18
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
19
+ if first_letter_in_uppercase
20
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
21
+ else
22
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,45 @@
1
+ module Wordnik
2
+
3
+ class Operation
4
+ require 'active_model'
5
+ include ActiveModel::Validations
6
+ include ActiveModel::Conversion
7
+ extend ActiveModel::Naming
8
+
9
+ attr_accessor :http_method, :summary, :notes, :parameters, :response, :open
10
+
11
+ validates_presence_of :http_method, :summary, :notes, :parameters, :response, :open
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |name, value|
15
+ send("#{name.to_s.underscore.to_sym}=", value)
16
+ end
17
+
18
+ self.http_method = self.http_method.to_s.downcase
19
+
20
+ # Generate OperationParameter instances from JSON
21
+ if self.parameters
22
+ self.parameters = self.parameters.map do |parameterData|
23
+ OperationParameter.new(parameterData)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ def get?
30
+ self.http_method.downcase == "get"
31
+ end
32
+
33
+ # Can this operation be run in the sandbox?
34
+ def sandboxable?
35
+ self.get?
36
+ end
37
+
38
+ # It's an ActiveModel thing..
39
+ def persisted?
40
+ false
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,39 @@
1
+ module Wordnik
2
+
3
+ class OperationParameter
4
+ require 'active_model'
5
+ include ActiveModel::Validations
6
+ include ActiveModel::Conversion
7
+ extend ActiveModel::Naming
8
+
9
+ attr_accessor :name, :description, :required, :param_type, :default_value, :allowable_values
10
+
11
+ validates_presence_of :name, :description, :required, :param_type, :default_value, :allowable_values
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |name, value|
15
+ send("#{name.to_s.underscore.to_sym}=", value)
16
+ end
17
+ end
18
+
19
+ def human_name
20
+ return "request body" if self.param_type == 'body'
21
+ self.name
22
+ end
23
+
24
+ def has_allowable_array?
25
+ self.allowable_values.present? && self.allowable_values.include?(",")
26
+ end
27
+
28
+ def required?
29
+ self.required
30
+ end
31
+
32
+ # It's an ActiveModel thing..
33
+ def persisted?
34
+ false
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,168 @@
1
+ module Wordnik
2
+
3
+ class Request
4
+ require 'uri'
5
+ require 'addressable/uri'
6
+ require 'typhoeus'
7
+ require 'active_model'
8
+ include ActiveModel::Validations
9
+ include ActiveModel::Conversion
10
+ extend ActiveModel::Naming
11
+
12
+ attr_accessor :host, :port, :path, :format, :params, :body, :http_method, :headers
13
+
14
+ validates_presence_of :host, :path, :format, :http_method
15
+
16
+ def initialize(http_method, path, attributes={})
17
+ attributes[:format] ||= "json"
18
+ attributes[:host] ||= Wordnik.configuration.base_uri
19
+ attributes[:params] ||= {}
20
+
21
+ # Set default headers, but allow them to be overridden
22
+ default_headers = {
23
+ 'User-Agent' => "Wordnik Ruby Gem #{Wordnik::VERSION}",
24
+ 'Content-Type' => "application/#{attributes[:format].downcase}",
25
+ :api_key => Wordnik.configuration.api_key
26
+ }
27
+ attributes[:headers] = default_headers.merge(attributes[:headers] || {})
28
+
29
+ # If a blank/nil api_key was passed in, remove it from the headers
30
+ attributes[:headers].delete(:api_key) if attributes[:headers][:api_key].blank?
31
+
32
+ self.http_method = http_method.to_sym
33
+ self.path = path
34
+ attributes.each do |name, value|
35
+ send("#{name.to_s.underscore.to_sym}=", value)
36
+ end
37
+ end
38
+
39
+ # Construct a base URL
40
+ def url
41
+ u = Addressable::URI.new
42
+ u.host = self.host.sub(/\/$/, '')
43
+ u.port = self.port if self.port.present?
44
+ u.path = self.interpreted_path
45
+ u.scheme = "http" # For some reason this must be set _after_ host, otherwise Addressable gets upset
46
+ u.to_s
47
+ end
48
+
49
+ # Iterate over the params hash, injecting any path values into the path string
50
+ # e.g. /word.{format}/{word}/entries => /word.json/cat/entries
51
+ def interpreted_path
52
+ p = self.path
53
+ self.params.each_pair do |key, value|
54
+ p = p.gsub("{#{key}}", value.to_s)
55
+ end
56
+
57
+ # Stick a .{format} placeholder into the path if there isn't
58
+ # one already or an actual format like json or xml
59
+ # e.g. /words/blah => /words.{format}/blah
60
+ unless ['.json', '.xml', '{format}'].any? {|s| p.downcase.include? s }
61
+ p = p.sub(/^(\/?\w+)/, "\\1.#{format}")
62
+ end
63
+
64
+ p = p.sub("{format}", self.format)
65
+ URI.encode(p)
66
+ end
67
+
68
+ def interpreted_body
69
+ return unless self.body.present?
70
+ return self.body.to_json if self.body.is_a?(Hash)
71
+ self.body
72
+ end
73
+
74
+ # Iterate over all params,
75
+ # .. removing the ones that are part of the path itself.
76
+ # .. stringifying values so Addressable doesn't blow up.
77
+ # .. obfuscating the API key if needed.
78
+ def query_string_params(obfuscated=false)
79
+ qsp = {}
80
+ self.params.each_pair do |key, value|
81
+ next if self.path.include? "{#{key}}"
82
+ next if value.blank?
83
+ value = "YOUR_API_KEY" if key.to_sym == :api_key && obfuscated
84
+ qsp[key] = value.to_s
85
+ end
86
+ qsp
87
+ end
88
+
89
+ # Construct a query string from the query-string-type params
90
+ def query_string(options={})
91
+
92
+ # We don't want to end up with '?' as our query string
93
+ # if there aren't really any params
94
+ return "" if query_string_params.blank?
95
+
96
+ default_options = {:obfuscated => false}
97
+ options = default_options.merge(options)
98
+
99
+ qs = Addressable::URI.new
100
+ qs.query_values = self.query_string_params(options[:obfuscated])
101
+ qs.to_s
102
+ end
103
+
104
+ # Returns full request URL with query string included
105
+ def url_with_query_string(options={})
106
+ default_options = {:obfuscated => false}
107
+ options = default_options.merge(options)
108
+
109
+ [url, query_string(options)].join('')
110
+ end
111
+
112
+ def make
113
+ response = case self.http_method.to_sym
114
+ when :get
115
+ Typhoeus::Request.get(
116
+ self.url_with_query_string,
117
+ :headers => self.headers.stringify_keys
118
+ )
119
+
120
+ when :post
121
+ Typhoeus::Request.post(
122
+ self.url_with_query_string,
123
+ :body => self.interpreted_body,
124
+ :headers => self.headers.stringify_keys
125
+ )
126
+
127
+ when :put
128
+ Typhoeus::Request.put(
129
+ self.url_with_query_string,
130
+ :body => self.interpreted_body,
131
+ :headers => self.headers.stringify_keys
132
+ )
133
+
134
+ when :delete
135
+ Typhoeus::Request.delete(
136
+ self.url_with_query_string,
137
+ :body => self.interpreted_body,
138
+ :headers => self.headers.stringify_keys
139
+ )
140
+ end
141
+
142
+ @response_obj = Response.new(response)
143
+ end
144
+
145
+ # If the request has been made, return the existing response
146
+ # If not, make the request and return the response
147
+ def response
148
+ @response_obj || self.make
149
+ end
150
+
151
+ def response_code_pretty
152
+ return unless @response.present?
153
+ @response.code.to_s
154
+ end
155
+
156
+ def response_headers_pretty
157
+ return unless @response.present?
158
+ # JSON.pretty_generate(@response.headers).gsub(/\n/, '<br/>').html_safe # <- This was for RestClient
159
+ @response.headers.gsub(/\n/, '<br/>').html_safe # <- This is for Typhoeus
160
+ end
161
+
162
+ # It's an ActiveModel thing..
163
+ def persisted?
164
+ false
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,101 @@
1
+ # To jog the memory: Resource > Endpoint > Operation > OperationParameter
2
+
3
+ module Wordnik
4
+ class Resource
5
+ require 'active_model'
6
+ include ActiveModel::Validations
7
+ include ActiveModel::Conversion
8
+ extend ActiveModel::Naming
9
+
10
+ attr_accessor :name, :raw_data, :endpoints, :models
11
+
12
+ validates_presence_of :name, :raw_data, :endpoints, :models
13
+
14
+ def initialize(attributes = {})
15
+ attributes.each do |name, value|
16
+ send("#{name.to_s.underscore.to_sym}=", value)
17
+ end
18
+
19
+ # Generate Endpoint instances from JSON
20
+ if self.raw_data['endPoints']
21
+ self.endpoints = self.raw_data['endPoints'].map do |endpointData|
22
+ Endpoint.new(endpointData)
23
+ end
24
+ end
25
+ end
26
+
27
+ def operation_nickname_pairs
28
+ return @pairs if @pairs
29
+ return unless self.endpoints.present?
30
+ @pairs = {}
31
+ self.endpoints.map do |endpoint|
32
+ endpoint.operations.map do |operation|
33
+ nickname_parts = []
34
+ nickname_parts << operation.http_method
35
+ nickname_parts << endpoint.path.gsub(/\{\w+\}/, "").tr("/", "_").tr(' .', '').underscore
36
+ nickname = nickname_parts.
37
+ join("_").
38
+ gsub(/_+/, "_").
39
+ gsub("_#{self.name.to_s.underscore}", "").
40
+ gsub(/_$/, "")
41
+ @pairs[nickname] = endpoint.path
42
+ end
43
+ end
44
+ @pairs
45
+ end
46
+
47
+ # Uses the received method name and arguments to dynamically construct a Request
48
+ # If the method name is prefixed with 'build_', then the 'unmade' Request
49
+ # object will be returned instead of the Request Response's body
50
+ #
51
+ # Wordnik.word.get('dingo')
52
+ # Wordnik.word.get_definitions('dingo', :limit => 20, )
53
+ def method_missing(sym, *args, &block)
54
+ nickname = sym.to_s
55
+
56
+ # If method nickname starts with 'build_',
57
+ # then just build the request but don't run it.
58
+ if nickname =~ /^build_/
59
+ nickname.gsub!('build_', '')
60
+ build_only = true
61
+ end
62
+
63
+ # Extrapolate HTTP method from beginning of method name
64
+ # e.g. 'post_words' -> :post
65
+ http_method = nickname.split("_").first.to_sym
66
+
67
+ # Ruby turns all key-value arguments at the end into a single hash
68
+ # e.g. Wordnik.word.get_examples('dingo', :limit => 10, :part_of_speech => 'verb')
69
+ # becomes {:limit => 10, :part_of_speech => 'verb'}
70
+ params = args.last.is_a?(Hash) ? args.pop : {}
71
+
72
+ # Find the path that corresponds to this method nickname
73
+ # e.g. post_words -> "/wordList.{format}/{wordListId}/words"
74
+ path = operation_nickname_pairs[nickname]
75
+
76
+ # Take the '.{format}' portion out of the string so it doesn't interfere with
77
+ # the interpolation we're going to do on the path.
78
+ path.gsub!('.{format}', '')
79
+
80
+ # Stick the remaining (required) arguments into the path string
81
+ args.each do |arg|
82
+ path.sub!(/\{\w+\}/, arg)
83
+ end
84
+
85
+ request = Wordnik::Request.new(http_method, path, :params => params)
86
+
87
+ if build_only
88
+ request
89
+ else
90
+ request.response.body
91
+ end
92
+ end
93
+
94
+ # It's an ActiveModel thing..
95
+ def persisted?
96
+ false
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,74 @@
1
+ module Wordnik
2
+
3
+ class Response
4
+ require 'active_model'
5
+ require 'json'
6
+ include ActiveModel::Validations
7
+ include ActiveModel::Conversion
8
+ extend ActiveModel::Naming
9
+
10
+ attr_accessor :raw
11
+
12
+ validates_presence_of :raw
13
+
14
+ def initialize(raw)
15
+ self.raw = raw
16
+ end
17
+
18
+ def code
19
+ raw.code
20
+ end
21
+
22
+ # If body is JSON, parse it
23
+ # TODO: If body is XML, parse it
24
+ # Otherwise return raw string
25
+ def body
26
+ JSON.parse(raw.body)
27
+ rescue
28
+ raw.body
29
+ end
30
+
31
+ def headers
32
+ h = {}
33
+ raw.headers_hash.each {|k,v| h[k] = v }
34
+ h
35
+ end
36
+
37
+ # Extract the response format from the header hash
38
+ # e.g. {'Content-Type' => 'application/json'}
39
+ def format
40
+ headers['Content-Type'].split("/").last.to_sym
41
+ end
42
+
43
+ def json?
44
+ format == :json
45
+ end
46
+
47
+ def xml?
48
+ format == :xml
49
+ end
50
+
51
+ def pretty_body
52
+ return unless body.present?
53
+ case format
54
+ when :json
55
+ JSON.pretty_generate(body).gsub(/\n/, '<br/>').html_safe
56
+ when :xml
57
+ xsl = Nokogiri::XSLT(File.open(Rails.root.join("config", "pretty_print.xsl")))
58
+ xml = Nokogiri(body)
59
+ coder = HTMLEntities.new
60
+ coder.encode(xsl.apply_to(xml).to_s)
61
+ end
62
+ end
63
+
64
+ def pretty_headers
65
+ JSON.pretty_generate(headers).gsub(/\n/, '<br/>').html_safe
66
+ end
67
+
68
+ # It's an ActiveModel thing..
69
+ def persisted?
70
+ false
71
+ end
72
+
73
+ end
74
+ end