syncwise_api 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/syncwise_api/client.rb +47 -0
- data/lib/syncwise_api/errors.rb +93 -0
- data/lib/syncwise_api/ext/core_ext.rb +176 -0
- data/lib/syncwise_api/ext/inflections.rb +66 -0
- data/lib/syncwise_api/ext/inflector/inflections.rb +177 -0
- data/lib/syncwise_api/ext/inflector_methods.rb +62 -0
- data/lib/syncwise_api/ext/string_encoding.rb +12 -0
- data/lib/syncwise_api/mixins/mixins.rb +1 -0
- data/lib/syncwise_api/mixins/request_builder.rb +23 -0
- data/lib/syncwise_api/mixins/request_sender.rb +11 -0
- data/lib/syncwise_api/mixins/request_signer.rb +20 -0
- data/lib/syncwise_api/requests/V1_0/assign_device_create.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/base.rb +115 -0
- data/lib/syncwise_api/requests/V1_0/device_details.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/device_list.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/device_settings_details.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/device_settings_edit.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/device_subscribe.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/device_unsubscribe.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/driver_speed_report.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/obd_vehicle_details.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/trip_event_details.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/user_login.rb +26 -0
- data/lib/syncwise_api/requests/V1_0/user_subscribe_settings_details.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/vehicle_gauge_information.rb +19 -0
- data/lib/syncwise_api/requests/V1_0/vehicle_idling_history.rb +19 -0
- data/lib/syncwise_api/requests/requests.rb +2 -0
- data/lib/syncwise_api/responses/V1_0/base.rb +35 -0
- data/lib/syncwise_api/responses/V1_0/standard.rb +9 -0
- data/lib/syncwise_api/responses/responses.rb +1 -0
- data/lib/syncwise_api/service_utils/crypto/hmac_sha256.rb +15 -0
- data/lib/syncwise_api/service_utils/encoders/base64.rb +16 -0
- data/lib/syncwise_api/service_utils/encoders/json.rb +21 -0
- data/lib/syncwise_api/service_utils/http.rb +34 -0
- data/lib/syncwise_api/service_utils/parsers/json.rb +55 -0
- data/lib/syncwise_api/service_utils/service_utils.rb +2 -0
- data/lib/syncwise_api/service_utils/time_stamper.rb +13 -0
- data/lib/syncwise_api/version.rb +3 -0
- data/lib/syncwise_api.rb +30 -0
- data/syncwise_api.gemspec +27 -0
- data/tester.rb +10 -0
- metadata +162 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: d5ab37738b62b0ea99540366cc22ebe5352a6758
|
|
4
|
+
data.tar.gz: 50b3e3a806577539e51e8dbb7c0d7d64bea67530
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 460840491191c2bcb26381782446c568ac45126a24a4f2c1a24c80e401b9b19d0874c204706c64007ea7a2504eedfacfc35e18d0a3cd0c824cb31575bad56e61
|
|
7
|
+
data.tar.gz: 1a4992ebb5800beb11b5b12c9a00f86c4e759ec68bea7cbe1f0e9208957ef276d4a8325b62e34fc957f84a82caef384d05e9e7c69a64e095e79e423ecef6a2f1
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
syncwise_api
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-2.0.0-p0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Brendten Eickstaedt
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SyncwiseApi
|
|
2
|
+
|
|
3
|
+
TODO: Write a gem description
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'syncwise_api'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install syncwise_api
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
1. Fork it
|
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module SyncwiseApi
|
|
2
|
+
class Client
|
|
3
|
+
|
|
4
|
+
def initialize(username, password)
|
|
5
|
+
@username = username
|
|
6
|
+
@password = password
|
|
7
|
+
login
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def make_request(action_code, params)
|
|
11
|
+
if action_code == :user_login
|
|
12
|
+
puts 'Login is done automatically when a new Client is created. Ignoring this call.'
|
|
13
|
+
else
|
|
14
|
+
action_code = action_code.camelize
|
|
15
|
+
if SyncwiseApi::Requests::V1_0.const_defined?(action_code)
|
|
16
|
+
params[:'Action Code'] = action_code
|
|
17
|
+
params[:'API Key'] = @username
|
|
18
|
+
params[:secretKey] = @secret_key
|
|
19
|
+
params[:accountId] = @account_id
|
|
20
|
+
request_class = SyncwiseApi::Requests::V1_0.const_get(action_code)
|
|
21
|
+
request = request_class.new(params)
|
|
22
|
+
response = request.send_request
|
|
23
|
+
else
|
|
24
|
+
puts 'Sorry, that\'s not a valid request type.'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def login
|
|
32
|
+
begin
|
|
33
|
+
request = SyncwiseApi::Requests::V1_0::UserLogin.new({:'API Key' => @username, :password => @password, :secretKey => '', :accountId => ''})
|
|
34
|
+
response = request.send_request
|
|
35
|
+
if response.valid?
|
|
36
|
+
@secret_key = response.body_hash[:secret_key]
|
|
37
|
+
@account_id = response.body_hash[:account_id]
|
|
38
|
+
else
|
|
39
|
+
fail SyncwiseApi::Errors::ApiErrorCode.new(response, request, response.header_hash, response.body_hash)
|
|
40
|
+
end
|
|
41
|
+
rescue => e
|
|
42
|
+
SyncwiseApi::LOGGER.error e
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module SyncwiseApi
|
|
2
|
+
module Errors
|
|
3
|
+
|
|
4
|
+
class SyncwiseError < StandardError
|
|
5
|
+
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class MissingInstanceVariable < SyncwiseError
|
|
9
|
+
def initialize(variable, mixin, receiver)
|
|
10
|
+
@variable = variable
|
|
11
|
+
@mixin = mixin
|
|
12
|
+
@receiver_inspect = receiver.inspect
|
|
13
|
+
super("Mixin #{@mixin} tried to use undefined instance variable #{@variable} in #{@receiver_inspect}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :variable, :mixin, :receiver_inspect
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class InvalidParameters < SyncwiseError
|
|
20
|
+
def initialize(request_type, passed_params, required_params)
|
|
21
|
+
@request_type = request_type
|
|
22
|
+
@passed_params = passed_params
|
|
23
|
+
@required_params = required_params
|
|
24
|
+
super("#{@request_type} was called with params #{@passed_params}, but requires params #{@required_params}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :request_type, :passed_params, :required_params
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class JSONParseError < SyncwiseError
|
|
31
|
+
def initialize(error)
|
|
32
|
+
@error = error
|
|
33
|
+
super("Error parsing JSON: #{@error}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class JSONEncodeError < SyncwiseError
|
|
40
|
+
def initialize(object)
|
|
41
|
+
@object = object
|
|
42
|
+
super("Error encoding object into JSON. Object: #{@object.inspect}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_reader :object
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class HttpError < SyncwiseError
|
|
49
|
+
def initialize(error, request)
|
|
50
|
+
@error = error
|
|
51
|
+
@request = request
|
|
52
|
+
super("Http Error when trying to make request. Object: #{@error.inspect}, request: #{@request}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :error, :request
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class SyncwiseApi::Errors::EmptyResponseBody < SyncwiseError
|
|
59
|
+
def initialize(resp, request, header)
|
|
60
|
+
@resp = resp
|
|
61
|
+
@request = request
|
|
62
|
+
@header = header
|
|
63
|
+
super("Api call returned with empty body. Response: #{@resp}, request: #{@request}, header: #{@header}")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attr_reader :resp, :request, :header
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class SyncwiseApi::Errors::InvalidContentType < SyncwiseError
|
|
70
|
+
def initialize(resp, request, header)
|
|
71
|
+
@resp = resp
|
|
72
|
+
@request = request
|
|
73
|
+
@header = header
|
|
74
|
+
super("Api call returned an invalid Content-Type. Response: #{@resp}, request: #{@request}, header: #{@header}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attr_reader :resp, :request, :header
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class SyncwiseApi::Errors::ApiErrorCode < SyncwiseError
|
|
81
|
+
def initialize(resp, request, header, body)
|
|
82
|
+
@resp = resp
|
|
83
|
+
@request = request
|
|
84
|
+
@header = header
|
|
85
|
+
@body = body
|
|
86
|
+
super("Api call returned a Syncwise Error Code. Response: #{@resp}, request: #{@request}, header: #{@header}, body: #{@body}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
attr_reader :resp, :request, :header, :body
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require_relative 'string_encoding'
|
|
3
|
+
require_relative 'inflector_methods'
|
|
4
|
+
|
|
5
|
+
# so there are some things from Rails I love, and have a hard time living without when using PORO.
|
|
6
|
+
# i'm adding them back here. thanks, Rails Core team ;)
|
|
7
|
+
class Hash
|
|
8
|
+
def deep_symbolize_keys
|
|
9
|
+
result = {}
|
|
10
|
+
each do |key, value|
|
|
11
|
+
result[(key.underscore.dehumanize.to_sym rescue key)] = value.is_a?(Hash) ? value.deep_symbolize_keys : value
|
|
12
|
+
end
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias_method :symbolize_keys, :deep_symbolize_keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Object
|
|
20
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
|
21
|
+
# For example, "", " ", +nil+, [], and {} are all blank.
|
|
22
|
+
#
|
|
23
|
+
# This simplifies:
|
|
24
|
+
#
|
|
25
|
+
# if address.nil? || address.empty?
|
|
26
|
+
#
|
|
27
|
+
# ...to:
|
|
28
|
+
#
|
|
29
|
+
# if address.blank?
|
|
30
|
+
def blank?
|
|
31
|
+
respond_to?(:empty?) ? empty? : !self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# An object is present if it's not <tt>blank?</tt>.
|
|
35
|
+
def present?
|
|
36
|
+
!blank?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns object if it's <tt>present?</tt> otherwise returns +nil+.
|
|
40
|
+
# <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
|
|
41
|
+
#
|
|
42
|
+
# This is handy for any representation of objects where blank is the same
|
|
43
|
+
# as not present at all. For example, this simplifies a common check for
|
|
44
|
+
# HTTP POST/query parameters:
|
|
45
|
+
#
|
|
46
|
+
# state = params[:state] if params[:state].present?
|
|
47
|
+
# country = params[:country] if params[:country].present?
|
|
48
|
+
# region = state || country || 'US'
|
|
49
|
+
#
|
|
50
|
+
# ...becomes:
|
|
51
|
+
#
|
|
52
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
|
53
|
+
def presence
|
|
54
|
+
self if present?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns just the class name as a String, without namespace/modules
|
|
58
|
+
# For example, this method called on class This::That::AndTheOtherThing will
|
|
59
|
+
# return AndTheOtherThing
|
|
60
|
+
def unqualified_class
|
|
61
|
+
self.class.to_s.split('::').last
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class NilClass
|
|
66
|
+
# +nil+ is blank:
|
|
67
|
+
#
|
|
68
|
+
# nil.blank? # => true
|
|
69
|
+
#
|
|
70
|
+
def blank?
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class FalseClass
|
|
76
|
+
# +false+ is blank:
|
|
77
|
+
#
|
|
78
|
+
# false.blank? # => true
|
|
79
|
+
#
|
|
80
|
+
def blank?
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class TrueClass
|
|
86
|
+
# +true+ is not blank:
|
|
87
|
+
#
|
|
88
|
+
# true.blank? # => false
|
|
89
|
+
#
|
|
90
|
+
def blank?
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Array
|
|
96
|
+
# An array is blank if it's empty:
|
|
97
|
+
#
|
|
98
|
+
# [].blank? # => true
|
|
99
|
+
# [1,2,3].blank? # => false
|
|
100
|
+
#
|
|
101
|
+
alias_method :blank?, :empty?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Hash
|
|
105
|
+
# A hash is blank if it's empty:
|
|
106
|
+
#
|
|
107
|
+
# {}.blank? # => true
|
|
108
|
+
# {:key => 'value'}.blank? # => false
|
|
109
|
+
#
|
|
110
|
+
alias_method :blank?, :empty?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class String
|
|
114
|
+
# 0x3000: fullwidth whitespace
|
|
115
|
+
NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
|
|
116
|
+
|
|
117
|
+
# A string is blank if it's empty or contains whitespaces only:
|
|
118
|
+
#
|
|
119
|
+
# "".blank? # => true
|
|
120
|
+
# " ".blank? # => true
|
|
121
|
+
# " ".blank? # => true
|
|
122
|
+
# " something here ".blank? # => false
|
|
123
|
+
#
|
|
124
|
+
def blank?
|
|
125
|
+
# 1.8 does not takes [:space:] properly
|
|
126
|
+
if encoding_aware?
|
|
127
|
+
self !~ /[^[:space:]]/
|
|
128
|
+
else
|
|
129
|
+
self !~ NON_WHITESPACE_REGEXP
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def dehumanize
|
|
134
|
+
SyncwiseApi::Inflector.dehumanize(self)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def underscore
|
|
138
|
+
SyncwiseApi::Inflector.underscore(self)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
|
|
143
|
+
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
|
|
144
|
+
#
|
|
145
|
+
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
|
|
146
|
+
#
|
|
147
|
+
# "active_record".camelize # => "ActiveRecord"
|
|
148
|
+
# "active_record".camelize(:lower) # => "activeRecord"
|
|
149
|
+
# "active_record/errors".camelize # => "ActiveRecord::Errors"
|
|
150
|
+
# "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
|
|
151
|
+
def camelize(first_letter = :upper)
|
|
152
|
+
case first_letter
|
|
153
|
+
when :upper then SyncwiseApi::Inflector.camelize(self, true)
|
|
154
|
+
when :lower then SyncwiseApi::Inflector.camelize(self, false)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
alias_method :camelcase, :camelize
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class Numeric #:nodoc:
|
|
162
|
+
# No number is blank:
|
|
163
|
+
#
|
|
164
|
+
# 1.blank? # => false
|
|
165
|
+
# 0.blank? # => false
|
|
166
|
+
#
|
|
167
|
+
def blank?
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class Symbol
|
|
173
|
+
def camelize
|
|
174
|
+
self.to_s.camelize
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# taken lock, stock and 2 smoking barrels from rails / activesupport / lib / active_support / inflections.rb
|
|
2
|
+
# thanks again, rails team!
|
|
3
|
+
|
|
4
|
+
require_relative 'inflector/inflections'
|
|
5
|
+
|
|
6
|
+
module SyncwiseApi
|
|
7
|
+
Inflector.inflections do |inflect|
|
|
8
|
+
inflect.plural(/$/, 's')
|
|
9
|
+
inflect.plural(/s$/i, 's')
|
|
10
|
+
inflect.plural(/(ax|test)is$/i, '\1es')
|
|
11
|
+
inflect.plural(/(octop|vir)us$/i, '\1i')
|
|
12
|
+
inflect.plural(/(octop|vir)i$/i, '\1i')
|
|
13
|
+
inflect.plural(/(alias|status)$/i, '\1es')
|
|
14
|
+
inflect.plural(/(bu)s$/i, '\1ses')
|
|
15
|
+
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
|
|
16
|
+
inflect.plural(/([ti])um$/i, '\1a')
|
|
17
|
+
inflect.plural(/([ti])a$/i, '\1a')
|
|
18
|
+
inflect.plural(/sis$/i, 'ses')
|
|
19
|
+
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
|
|
20
|
+
inflect.plural(/(hive)$/i, '\1s')
|
|
21
|
+
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
|
|
22
|
+
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
|
|
23
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
|
|
24
|
+
inflect.plural(/(m|l)ouse$/i, '\1ice')
|
|
25
|
+
inflect.plural(/(m|l)ice$/i, '\1ice')
|
|
26
|
+
inflect.plural(/^(ox)$/i, '\1en')
|
|
27
|
+
inflect.plural(/^(oxen)$/i, '\1')
|
|
28
|
+
inflect.plural(/(quiz)$/i, '\1zes')
|
|
29
|
+
|
|
30
|
+
inflect.singular(/s$/i, '')
|
|
31
|
+
inflect.singular(/(n)ews$/i, '\1ews')
|
|
32
|
+
inflect.singular(/([ti])a$/i, '\1um')
|
|
33
|
+
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
|
|
34
|
+
inflect.singular(/(^analy)ses$/i, '\1sis')
|
|
35
|
+
inflect.singular(/([^f])ves$/i, '\1fe')
|
|
36
|
+
inflect.singular(/(hive)s$/i, '\1')
|
|
37
|
+
inflect.singular(/(tive)s$/i, '\1')
|
|
38
|
+
inflect.singular(/([lr])ves$/i, '\1f')
|
|
39
|
+
inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
|
|
40
|
+
inflect.singular(/(s)eries$/i, '\1eries')
|
|
41
|
+
inflect.singular(/(m)ovies$/i, '\1ovie')
|
|
42
|
+
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
|
|
43
|
+
inflect.singular(/(m|l)ice$/i, '\1ouse')
|
|
44
|
+
inflect.singular(/(bus)es$/i, '\1')
|
|
45
|
+
inflect.singular(/(o)es$/i, '\1')
|
|
46
|
+
inflect.singular(/(shoe)s$/i, '\1')
|
|
47
|
+
inflect.singular(/(cris|ax|test)es$/i, '\1is')
|
|
48
|
+
inflect.singular(/(octop|vir)i$/i, '\1us')
|
|
49
|
+
inflect.singular(/(alias|status)es$/i, '\1')
|
|
50
|
+
inflect.singular(/^(ox)en/i, '\1')
|
|
51
|
+
inflect.singular(/(vert|ind)ices$/i, '\1ex')
|
|
52
|
+
inflect.singular(/(matr)ices$/i, '\1ix')
|
|
53
|
+
inflect.singular(/(quiz)zes$/i, '\1')
|
|
54
|
+
inflect.singular(/(database)s$/i, '\1')
|
|
55
|
+
|
|
56
|
+
inflect.irregular('person', 'people')
|
|
57
|
+
inflect.irregular('man', 'men')
|
|
58
|
+
inflect.irregular('child', 'children')
|
|
59
|
+
inflect.irregular('sex', 'sexes')
|
|
60
|
+
inflect.irregular('move', 'moves')
|
|
61
|
+
inflect.irregular('cow', 'kine')
|
|
62
|
+
inflect.irregular('zombie', 'zombies')
|
|
63
|
+
|
|
64
|
+
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# taken lock, stock and 2 smoking barrels from rails / activesupport / lib / active_support / inflector / inflections.rb
|
|
2
|
+
# thanks again, rails team!
|
|
3
|
+
|
|
4
|
+
module SyncwiseApi
|
|
5
|
+
module Inflector
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
|
|
9
|
+
# inflection rules. Examples:
|
|
10
|
+
#
|
|
11
|
+
# ActiveSupport::Inflector.inflections do |inflect|
|
|
12
|
+
# inflect.plural /^(ox)$/i, '\1\2en'
|
|
13
|
+
# inflect.singular /^(ox)en/i, '\1'
|
|
14
|
+
#
|
|
15
|
+
# inflect.irregular 'octopus', 'octopi'
|
|
16
|
+
#
|
|
17
|
+
# inflect.uncountable "equipment"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
|
|
21
|
+
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
|
|
22
|
+
# already have been loaded.
|
|
23
|
+
class Inflections
|
|
24
|
+
def self.instance
|
|
25
|
+
@__instance__ ||= new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
|
|
35
|
+
# string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
|
|
36
|
+
# A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
|
|
37
|
+
# convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
|
|
38
|
+
#
|
|
39
|
+
# Examples:
|
|
40
|
+
# acronym 'HTML'
|
|
41
|
+
# titleize 'html' #=> 'HTML'
|
|
42
|
+
# camelize 'html' #=> 'HTML'
|
|
43
|
+
# underscore 'MyHTML' #=> 'my_html'
|
|
44
|
+
#
|
|
45
|
+
# The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
|
|
46
|
+
#
|
|
47
|
+
# acronym 'HTTP'
|
|
48
|
+
# camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
|
|
49
|
+
# camelize 'https' #=> 'Https', not 'HTTPs'
|
|
50
|
+
# underscore 'HTTPS' #=> 'http_s', not 'https'
|
|
51
|
+
#
|
|
52
|
+
# acronym 'HTTPS'
|
|
53
|
+
# camelize 'https' #=> 'HTTPS'
|
|
54
|
+
# underscore 'HTTPS' #=> 'https'
|
|
55
|
+
#
|
|
56
|
+
# Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as
|
|
57
|
+
# a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
|
|
58
|
+
# acronym as well:
|
|
59
|
+
#
|
|
60
|
+
# acronym 'API'
|
|
61
|
+
# camelize(pluralize('api')) #=> 'Apis'
|
|
62
|
+
#
|
|
63
|
+
# acronym 'APIs'
|
|
64
|
+
# camelize(pluralize('api')) #=> 'APIs'
|
|
65
|
+
#
|
|
66
|
+
# `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
|
|
67
|
+
# capitalization. The only restriction is that the word must begin with a capital letter.
|
|
68
|
+
#
|
|
69
|
+
# Examples:
|
|
70
|
+
# acronym 'RESTful'
|
|
71
|
+
# underscore 'RESTful' #=> 'restful'
|
|
72
|
+
# underscore 'RESTfulController' #=> 'restful_controller'
|
|
73
|
+
# titleize 'RESTfulController' #=> 'RESTful Controller'
|
|
74
|
+
# camelize 'restful' #=> 'RESTful'
|
|
75
|
+
# camelize 'restful_controller' #=> 'RESTfulController'
|
|
76
|
+
#
|
|
77
|
+
# acronym 'McDonald'
|
|
78
|
+
# underscore 'McDonald' #=> 'mcdonald'
|
|
79
|
+
# camelize 'mcdonald' #=> 'McDonald'
|
|
80
|
+
def acronym(word)
|
|
81
|
+
@acronyms[word.downcase] = word
|
|
82
|
+
@acronym_regex = /#{@acronyms.values.join("|")}/
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
|
|
86
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
|
87
|
+
def plural(rule, replacement)
|
|
88
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
|
89
|
+
@uncountables.delete(replacement)
|
|
90
|
+
@plurals.insert(0, [rule, replacement])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
|
|
94
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
|
95
|
+
def singular(rule, replacement)
|
|
96
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
|
97
|
+
@uncountables.delete(replacement)
|
|
98
|
+
@singulars.insert(0, [rule, replacement])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
|
|
102
|
+
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
|
|
103
|
+
#
|
|
104
|
+
# Examples:
|
|
105
|
+
# irregular 'octopus', 'octopi'
|
|
106
|
+
# irregular 'person', 'people'
|
|
107
|
+
def irregular(singular, plural)
|
|
108
|
+
@uncountables.delete(singular)
|
|
109
|
+
@uncountables.delete(plural)
|
|
110
|
+
if singular[0,1].upcase == plural[0,1].upcase
|
|
111
|
+
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
|
|
112
|
+
plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
|
|
113
|
+
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
|
|
114
|
+
else
|
|
115
|
+
plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
|
116
|
+
plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
|
117
|
+
plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
|
118
|
+
plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
|
119
|
+
singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
|
|
120
|
+
singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Add uncountable words that shouldn't be attempted inflected.
|
|
125
|
+
#
|
|
126
|
+
# Examples:
|
|
127
|
+
# uncountable "money"
|
|
128
|
+
# uncountable "money", "information"
|
|
129
|
+
# uncountable %w( money information rice )
|
|
130
|
+
def uncountable(*words)
|
|
131
|
+
(@uncountables << words).flatten!
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Specifies a humanized form of a string by a regular expression rule or by a string mapping.
|
|
135
|
+
# When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
|
|
136
|
+
# When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
|
|
137
|
+
#
|
|
138
|
+
# Examples:
|
|
139
|
+
# human /_cnt$/i, '\1_count'
|
|
140
|
+
# human "legacy_col_person_name", "Name"
|
|
141
|
+
def human(rule, replacement)
|
|
142
|
+
@humans.insert(0, [rule, replacement])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
|
|
146
|
+
# Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
|
|
147
|
+
# <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
|
|
148
|
+
#
|
|
149
|
+
# Examples:
|
|
150
|
+
# clear :all
|
|
151
|
+
# clear :plurals
|
|
152
|
+
def clear(scope = :all)
|
|
153
|
+
case scope
|
|
154
|
+
when :all
|
|
155
|
+
@plurals, @singulars, @uncountables, @humans = [], [], [], []
|
|
156
|
+
else
|
|
157
|
+
instance_variable_set "@#{scope}", []
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Yields a singleton instance of Inflector::Inflections so you can specify additional
|
|
163
|
+
# inflector rules.
|
|
164
|
+
#
|
|
165
|
+
# Example:
|
|
166
|
+
# ActiveSupport::Inflector.inflections do |inflect|
|
|
167
|
+
# inflect.uncountable "rails"
|
|
168
|
+
# end
|
|
169
|
+
def inflections
|
|
170
|
+
if block_given?
|
|
171
|
+
yield Inflections.instance
|
|
172
|
+
else
|
|
173
|
+
Inflections.instance
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|