wordnik 0.2.1 → 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.
@@ -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