usps-imis-api 0.10.3 → 0.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a699891bdb0679741aafb3754e7939bfcd7f854b51e6cab76c25f6d9fc69995
4
- data.tar.gz: 97ff09a0b7d9c1981b9170c96e3885e57f82465e08fa8a8be4c1b20cf7c8f822
3
+ metadata.gz: 6a938a3621c6e575ac4d205495bbdccf39b4579a986a4431730d366bee510824
4
+ data.tar.gz: 7cfbacbf135e1f9899510fc54ff05aa079bf70f24b1c93b81de0bcd78fc26218
5
5
  SHA512:
6
- metadata.gz: 49f73bb0e65581cb15cb8004170ed22963e025693e8fb62f33a09b6c2afa5ca548a9a37354867679a9562205ff752a18b812bcca085b1b575c3c5bc48ec45564
7
- data.tar.gz: 5f455a1a8a0231e08d6b83297ebcfcfea0c423dc6396ae4e1ec312d6da52e111b6a25babc978f232631242ac86da14499cfc2d6ac246c90779e41be6049b3fb7
6
+ metadata.gz: 679178e131a5fa45915738be179b82173fa8383c0109ba6fe5e2d73b9f85c1ecdb6bc160e94ed385866371ed10e465de17a05409b55de64e015f8d44d4b12c0a
7
+ data.tar.gz: 66466bcfca8c602e00a04a3f52599cf96f0bae5d64065542e615bbb8acb9cab27fd5b44552e9367ecdb8ef3004ac9a40e3417a5ec8bc6a46b8ffb04cff7aed96
data/Readme.md CHANGED
@@ -39,380 +39,9 @@ Usps::Imis.configure do |config|
39
39
  end
40
40
  ```
41
41
 
42
- When using `bin/console`, this configuration will be run by default.
43
-
44
- Instantiate the API object:
45
-
46
- ```ruby
47
- api = Usps::Imis::Api.new
48
- ```
49
-
50
- If you already have an iMIS ID to work with, you can pass that in immediately:
51
-
52
- ```ruby
53
- api = Usps::Imis::Api.new(imis_id: imis_id)
54
- ```
55
-
56
- ### Authentication
57
-
58
- If a token is not available, this will automatically fetch one when needed. As long as that token
59
- should still be valid, it will reuse the same token. If the token should expire, this will
60
- automatically request a new token.
61
-
62
42
  ## Usage
63
43
 
64
- ### iMIS ID
65
-
66
- To act on member data, you need to have the iMIS ID. If you already have access to that from the
67
- database, you can skip this step.
68
-
69
- To convert a member's certificate number into their iMIS ID, run the following method:
70
-
71
- ```ruby
72
- api.imis_id_for(certificate)
73
- ```
74
-
75
- This will both return the ID, and store it for use with other requests. If you need to change which
76
- member you are working with, just run this method again with the new certificate number.
77
-
78
- You can also manually set the current ID, if you already have it for a given member
79
-
80
- ```ruby
81
- api.imis_id = imis_id
82
- ```
83
-
84
- #### Without an iMIS ID
85
-
86
- Running requests without an iMIS ID set will raise an exception.
87
-
88
- If you want to query the entire Business Object, use the query interface instead:
89
-
90
- ```ruby
91
- api = Usps::Imis::Api.new # No iMIS ID set
92
-
93
- # These requests are identical:
94
-
95
- Usps::Imis::Query.new(api, 'ABC_ASC_Individual_Demog')
96
-
97
- api.on('ABC_ASC_Individual_Demog').query
98
-
99
- api.query('ABC_ASC_Individual_Demog')
100
- ```
101
-
102
- ### Business Object and Panel Actions
103
-
104
- Business Objects and Panels support the following actions.
105
-
106
- Panels require passing in the ordinal identifier as an argument, except for `POST`.
107
-
108
- #### GET
109
-
110
- To fetch member data, run e.g.:
111
-
112
- ```ruby
113
- data = api.on('ABC_ASC_Individual_Demog').get
114
- ```
115
-
116
- You can also pass in specific field names to filter the returned member data, e.g.:
117
-
118
- ```ruby
119
- data = api.on('ABC_ASC_Individual_Demog').get('TotMMS', 'MMS_Updated')
120
- ```
121
-
122
- The response from `get` behaves like a Hash, but directly accesses property values by name.
123
- If you need to access the rest of the underlying data, use the `raw` method:
124
-
125
- ```ruby
126
- data = api.on('ABC_ASC_Individual_Demog').get
127
- data['TotMMS']
128
- data.raw['EntityTypeName']
129
- ```
130
-
131
- Alias: `read`
132
-
133
- #### GET Field
134
-
135
- To fetch a specific field from member data, run e.g.:
136
-
137
- ```ruby
138
- tot_mms = api.on('ABC_ASC_Individual_Demog').get_field('TotMMS')
139
- ```
140
-
141
- You can also access fields directly on the Business Object or Panel like a Hash:
142
-
143
- ```ruby
144
- tot_mms = api.on('ABC_ASC_Individual_Demog')['TotMMS']
145
- ```
146
-
147
- Alias: `fetch`
148
-
149
- #### GET Fields
150
-
151
- To fetch multiple specific fields from member data, run e.g.:
152
-
153
- ```ruby
154
- data = api.on('ABC_ASC_Individual_Demog').get_fields('TotMMS', 'MMS_Updated')
155
- ```
156
-
157
- Alias: `fetch_all`
158
-
159
- #### PUT Fields
160
-
161
- To update member data, run e.g.:
162
-
163
- ```ruby
164
- data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
165
- update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
166
- ```
167
-
168
- This method fetches the current data structure, and filters it down to just what you want to
169
- update, to reduce the likelihood of update collisions or type validation failures.
170
-
171
- Alias: `patch`
172
-
173
- #### PUT
174
-
175
- To update member data, run e.g.:
176
-
177
- ```ruby
178
- update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
179
- ```
180
-
181
- This method requires a complete iMIS data structure. However, any properties not included will be
182
- left unmodified (meaning this also effectively handles `PATCH`, though iMIS does not accept that
183
- HTTP verb).
184
-
185
- Alias: `update`
186
-
187
- #### POST
188
-
189
- To create new member data, run e.g.:
190
-
191
- ```ruby
192
- created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
193
- ```
194
-
195
- This method requires a complete iMIS data structure.
196
-
197
- Alias: `create`
198
-
199
- #### DELETE
200
-
201
- To remove member data, run e.g.:
202
-
203
- ```ruby
204
- api.on('ABC_ASC_Individual_Demog').delete
205
- ```
206
-
207
- Alias: `destroy`
208
-
209
- ### QUERY
210
-
211
- Run an IQA Query
212
-
213
- `query_params` is a hash of shape: `{ param_name => param_value }`
214
-
215
- ```ruby
216
- query = api.query(query_name, query_params)
217
-
218
- query.each do |item|
219
- # Download all pages of the query, then iterate on the results
220
- end
221
-
222
- query.find_each do |item|
223
- # Iterate one page at a time, fetching new pages automatically
224
- end
225
- ```
226
-
227
- ### Field Mapper
228
-
229
- For fields that have already been mapped between the ITCom database and iMIS, you can use the
230
- Mapper class to further simplify the `fetch` / `update` interfaces:
231
-
232
- ```ruby
233
- mm = api.mapper.fetch(:mm)
234
- mm = api.mapper[:mm]
235
- ```
236
-
237
- ```ruby
238
- api.mapper.update(mm: 15)
239
- ```
240
-
241
- For simplicity, you can also call `fetch` (or simply use Hash access syntax) and `update` on the
242
- `Api` class directly:
243
-
244
- ```ruby
245
- api.fetch(:mm)
246
- api[:mm]
247
- ```
248
-
249
- ```ruby
250
- api.update(mm: 15)
251
- api[:mm] = 15
252
- ```
253
-
254
- If there is no known mapping for the requested field, the Mapper will give up, but will provide
255
- you with the standard API call syntax, and will suggest you inform ITCom leadership of the new
256
- mapping you need.
257
-
258
- ### Panels
259
-
260
- For supported panels (usually, business objects with composite identity keys), you can interact
261
- with them in the same general way:
262
-
263
- ```ruby
264
- vsc = Usps::Imis::Panels::Vsc.new(imis_id: 6374)
265
-
266
- vsc.get(1417)
267
-
268
- # All of these options are identical
269
- #
270
- vsc.get(1417, 'Quantity').first
271
- vsc.get(1417)['Quantity']
272
- vsc[1417, 'Quantity']
273
- vsc.get(1417).raw['Properties']['$values'].find { it['Name'] == 'Quantity' }['Value']['$value']
274
- vsc.get_field(1417, 'Quantity')
275
-
276
- created = vsc.create(certificate: 'E136924', year: 2024, count: 42)
277
-
278
- # Get the Ordinal identifier from the response
279
- #
280
- # All of these options are identical
281
- #
282
- ordinal = created.ordinal
283
- ordinal = created['Ordinal']
284
- ordinal = created.raw['Properties']['$values'].find { it['Name'] == 'Ordinal' }['Value']['$value']
285
- ordinal = created.raw['Identity']['IdentityElements']['$values'][1].to_i # Value is duplicated here
286
-
287
- vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
288
-
289
- vsc.put_fields(ordinal, 'Quantity' => 44)
290
- vsc[ordinal, 'Quantity'] = 44
291
-
292
- vsc.destroy(ordinal)
293
- ```
294
-
295
- If you already have an iMIS ID to work with, you can pass that in immediately:
296
-
297
- ```ruby
298
- vsc = Usps::Imis::Panels::Vsc.new(imis_id: imis_id)
299
- ```
300
-
301
- Panels are also accessible directly from the API object:
302
-
303
- ```ruby
304
- api.panels.vsc.get(1417)
305
- ```
306
-
307
- ### DSL Mode
308
-
309
- Instead of manually setting the current iMIS ID, then running individual queries, you can instead
310
- run queries in DSL mode. This specifies the iMIS ID for the scope of the block, then reverts to the
311
- previous value.
312
-
313
- ```ruby
314
- api.with(31092) do
315
- # These requests are identical:
316
-
317
- on('ABC_ASC_Individual_Demog') { put_fields('TotMMS' => 15) }
318
-
319
- on('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)
320
-
321
- mapper.update(mm: 15)
322
-
323
- update(mm: 15)
324
-
325
- mapper[:mm] = 15
326
- end
327
-
328
- # This request fetches the same data, but leaves the iMIS ID selected
329
- api.with(31092)[:mm] = 15
330
- ```
331
-
332
- ```ruby
333
- api.with(6374) do
334
- panels.vsc.get(1417)
335
- end
336
- ```
337
-
338
- ```ruby
339
- api.with(31092) do
340
- # These requests are identical:
341
-
342
- on('ABC_ASC_Individual_Demog') do
343
- get.raw['Properties']['$values'].find { it['Name'] == 'TotMMS' }['Value']['$value']
344
-
345
- get['TotMMS']
346
-
347
- get_field('TotMMS')
348
-
349
- get_fields('TotMMS').first
350
- end
351
-
352
- on('ABC_ASC_Individual_Demog').get_field('TotMMS')
353
-
354
- on('ABC_ASC_Individual_Demog')['TotMMS']
355
- end
356
-
357
- # These requests fetch the same data, but leave the iMIS ID selected
358
- api.with(31092).on('ABC_ASC_Individual_Demog').get_field('TotMMS')
359
- api.with(31092).on('ABC_ASC_Individual_Demog')['TotMMS']
360
- ```
361
-
362
- ### Data Methods
363
-
364
- Data responses from the API can be handled as a standard Hash using the `raw` method.
365
-
366
- If you need to access all of the property values, you can use the `properties` method.
367
- By default, this will exclude the `ID` and `Ordinal` properties; they can be included with
368
- `properties(include_ids: true)`.
369
-
370
- ## Test Data Mocking
371
-
372
- You can use the provided Business Object Mock to generate stub data for rspec:
373
-
374
- ```ruby
375
- allow(api).to(
376
- receive(:on).with('ABC_ASC_Individual_Demog').and_return(
377
- Usps::Imis::Mocks::BusinessObject.new(TotMMS: 2)
378
- )
379
- )
380
- ```
381
-
382
- ### VCR
383
-
384
- If you would like to use the included VCR config, make sure your Gemfile includes the required gems:
385
-
386
- ```ruby
387
- gem 'cgi' # 2025-10-27 - Currently required for Ruby 3.5
388
- gem 'vcr'
389
- gem 'webmock'
390
- ```
391
-
392
- In `spec_helper.rb`, add the following:
393
-
394
- ```ruby
395
- require 'usps/vcr'
396
- Usps::Vcr::Config.configure!
397
- ```
398
-
399
- You can also pass additional VCR config options through:
400
-
401
- ```ruby
402
- Usps::Vcr::Config.configure! do |config|
403
- config.ignore_localhost = true
404
- end
405
- ```
406
-
407
- If you have any tests that are dependent on ordering to (re-)generate cassettes, a helper is also
408
- available to support adding example metadata, which will use a fixed order when the environment
409
- varialbe `VCR=all` is set:
410
-
411
- ```ruby
412
- describe 'examples that care about order', order: Usps::Vcr::Config.vcr_record_ordered do
413
- # ...
414
- end
415
- ```
44
+ For more details and examples, refer to the [Wiki](https://github.com/unitedstatespowersquadrons/imis-api-ruby/wiki).
416
45
 
417
46
  ## Exception Handling
418
47
 
@@ -39,6 +39,18 @@ module Usps
39
39
  #
40
40
  def query = api.query(business_object_name)
41
41
 
42
+ # Support passthrough for Api#with
43
+ #
44
+ def with(imis_id, &)
45
+ # Bring into local scope
46
+ wrapper_business_object_name = business_object_name
47
+ wrapper_ordinal = ordinal
48
+
49
+ api.with(imis_id) do
50
+ on(wrapper_business_object_name, ordinal: wrapper_ordinal, &)
51
+ end
52
+ end
53
+
42
54
  # Get a business object for the current member
43
55
  #
44
56
  # If +fields+ is provided, will return only those field values
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module CommandLine
6
+ # Command line interface for the Api
7
+ #
8
+ class Interface
9
+ NAME = 'USPS iMIS API - Ruby'
10
+
11
+ attr_reader :options
12
+
13
+ def self.run(...) = new(...).run
14
+
15
+ def initialize(**)
16
+ @options = input_options.merge(**)
17
+ validate_options!
18
+ configure! if options[:config]
19
+ logging!
20
+ end
21
+
22
+ def run
23
+ set_member
24
+
25
+ result = simplify(perform!)
26
+
27
+ output { result }
28
+
29
+ result
30
+ end
31
+
32
+ private
33
+
34
+ # :nocov:
35
+ def input_options
36
+ if ENV.fetch('CI', false)
37
+ defaults = {
38
+ raw: false,
39
+ quiet: false,
40
+ log: false,
41
+ log_level: 'info',
42
+ version: false,
43
+ help: false
44
+ }
45
+ return defaults
46
+ end
47
+
48
+ OptionsParser.new.options
49
+ end
50
+ # :nocov:
51
+
52
+ def api
53
+ @api ||= Usps::Imis::Api.new
54
+ end
55
+
56
+ def set_member
57
+ case options
58
+ in certificate: then api.imis_id_for(certificate)
59
+ in id: then api.imis_id = id
60
+ else
61
+ # Query
62
+ end
63
+ end
64
+
65
+ # rubocop:disable Metrics
66
+ def perform!
67
+ case options
68
+ in map:, data: then api.mapper[map.to_sym] = data
69
+ in map: then api.mapper[map.to_sym]
70
+
71
+ in on:, delete: true then api.on(on).delete
72
+ in on:, data:, post: true then api.on(on).post(data)
73
+ in on:, data:, field: then api.on(on).put_field(field, data)
74
+ in on:, data: then api.on(on).put_fields(data)
75
+ in on:, fields: then api.on(on).get_fields(*fields)
76
+ in on:, field: then api.on(on).get_field(field)
77
+ in on: then api.on(on).get
78
+
79
+ in panel:, ordinal:, delete: true then api.panels.public_send(panel).delete(ordinal)
80
+ in panel:, data:, post: true then api.panels.public_send(panel).post(data)
81
+ in panel:, ordinal:, data:, field: then api.panels.public_send(panel).put_field(ordinal, field, data)
82
+ in panel:, ordinal:, data: then api.panels.public_send(panel).put_fields(ordinal, data)
83
+ in panel:, ordinal:, fields: then api.panels.public_send(panel).get_fields(ordinal, *fields)
84
+ in panel:, ordinal:, field: then api.panels.public_send(panel).get_field(ordinal, field)
85
+ in panel:, ordinal: then api.panels.public_send(panel).get(ordinal)
86
+
87
+ in query:, data: then api.query(query, data)
88
+ in query: then api.query(query)
89
+ end
90
+ rescue NoMatchingPatternError => e
91
+ raise Errors::CommandLineError, "Unable to match pattern from options: #{e.message}"
92
+ end
93
+ # rubocop:enable Metrics
94
+
95
+ def simplify(data)
96
+ return data.to_a if data.is_a?(Query)
97
+ return data if options[:raw]
98
+ return data unless data.is_a?(Hash) # Includes Usps::Imis::Data
99
+
100
+ data.properties(include_ids: options[:include_ids])
101
+ end
102
+
103
+ # :nocov:
104
+ def output(&block)
105
+ return if ENV.fetch('SUPPRESS_OUTPUT', false)
106
+
107
+ puts JSON.dump(block.call) if block_given?
108
+ end
109
+ # :nocov:
110
+
111
+ def configure!
112
+ json_config = JSON.parse(File.read(options[:config]))
113
+
114
+ Usps::Imis.configure do |config|
115
+ json_config.each do |key, value|
116
+ config.public_send("#{key}=", value)
117
+ end
118
+ end
119
+ end
120
+
121
+ def logging!
122
+ Usps::Imis.configure do |config|
123
+ # :nocov:
124
+ if options[:quiet] || ENV.fetch('SUPPRESS_OUTPUT', false)
125
+ config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
126
+ elsif options[:log]
127
+ # Use default
128
+ else
129
+ config.logger = ActiveSupport::TaggedLogging.new(Logger.new($stderr))
130
+ end
131
+ # :nocov:
132
+
133
+ config.logger.level = options[:log_level]
134
+ end
135
+ end
136
+
137
+ def validate_options!
138
+ if options[:log_level] == 'info' && options.except(:log_level, :quiet).values.none?
139
+ raise Errors::CommandLineError, 'No options provided'
140
+ end
141
+
142
+ return if options[:query].nil? || options[:query].start_with?('$/')
143
+
144
+ raise Errors::CommandLineError, 'Invalid IQA Query name'
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module CommandLine
6
+ # Command line options parser
7
+ #
8
+ class OptionsParser
9
+ OPTIONS = {
10
+ certificate: ['Member certificate number', { type: :string }],
11
+ id: ['Member iMIS ID', { type: :integer }],
12
+ on: ['Business Object name', { type: :string }],
13
+ panel: ['Panel name', { type: :string }],
14
+ ordinal: ['Ordinal ID within a Panel', { type: :integer }],
15
+ query: ['IQA Query name', { type: :string, short: :Q }],
16
+ post: ["Send a #{'POST'.cyan} request", { short: :P }],
17
+ delete: ["Send a #{'DELETE'.cyan} request", { short: :D }],
18
+ field: ['Specific field to return or update', { type: :string }],
19
+ fields: ['Specific field(s) to return', { type: :strings, short: :F }],
20
+ map: ['Mapped field name to return or update', { type: :string }],
21
+ data: ['JSON string input', { type: :string }],
22
+ config: ['Path to the JSON config file to use', { type: :string, short: :C }],
23
+ raw: ['Return raw JSON output, rather than simplified data', { short: :R }],
24
+ include_ids: ['Include ID properties in returned data'],
25
+ quiet: ['Suppress logging to STDERR'],
26
+ log: ['Redirect logging to STDOUT'],
27
+ log_level: ['Set the logging level', { type: :string, default: 'info', short: :L }]
28
+ }.freeze
29
+
30
+ CONFLICTING_OPTION_GROUPS = [
31
+ %i[certificate id],
32
+ %i[on panel query map],
33
+ %i[field fields map query]
34
+ ].freeze
35
+
36
+ attr_reader :arguments, :options
37
+
38
+ def self.banner_contents
39
+ <<~BANNER
40
+ Usage:
41
+ imis.rb #{'[options]'.gray}
42
+
43
+ The default HTTP verb is #{'GET'.cyan}.
44
+ If #{'--data'.green}/#{'-d'.green} is provided, the default HTTP verb is #{'PUT'.cyan}.
45
+
46
+ #{'--data'.green}/#{'-d'.green} is used to provide field(s) data for #{'PUT'.cyan}
47
+ requests and mapper updates, object data for #{'POST'.cyan},
48
+ and to provide any query params.
49
+
50
+ Configuration can be specified with #{'--config'.green}/#{'-C'.green}, or by setting
51
+ the following environment variables:
52
+ #{'IMIS_ENVIRONMENT'.yellow}
53
+ #{'IMIS_USERNAME'.yellow}
54
+ #{'IMIS_PASSWORD'.yellow}
55
+ #{'IMIS_ID_QUERY_NAME'.yellow}
56
+
57
+ Options:
58
+ BANNER
59
+ end
60
+
61
+ def initialize
62
+ @options = parse_options.compact
63
+ @arguments = ARGV # Not currently used
64
+
65
+ # :nocov:
66
+ @options[:data] = JSON.parse(read_stdin) if stdin?
67
+ # :nocov:
68
+ end
69
+
70
+ private
71
+
72
+ def parse_options
73
+ Optimist.options do
74
+ version "#{Interface::NAME} (v#{Usps::Imis::VERSION})"
75
+
76
+ banner "#{version.bold.blue}\n \n"
77
+ banner OptionsParser.banner_contents
78
+
79
+ OPTIONS.each { |option, data| opt(option, *data) }
80
+ CONFLICTING_OPTION_GROUPS.each { |group| conflicts(*group) }
81
+
82
+ educate_on_error
83
+ end
84
+ end
85
+
86
+ # :nocov:
87
+ def stdin? = $stdin.wait_readable(0)
88
+ def read_stdin = $stdin.read.chomp
89
+ # :nocov:
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optimist'
4
+ require 'colorize'
5
+
6
+ module Usps
7
+ module Imis
8
+ # Wrapper for the command line interface
9
+ #
10
+ module CommandLine; end
11
+ end
12
+ end
13
+
14
+ require_relative 'command_line/options_parser'
15
+ require_relative 'command_line/interface'
@@ -12,7 +12,7 @@ module Usps
12
12
  attr_reader :environment, :logger, :logger_level
13
13
 
14
14
  def initialize
15
- @environment = defined?(::Rails) ? ::Rails.env : ActiveSupport::StringInquirer.new('development')
15
+ @environment = default_environment
16
16
  @imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
17
17
  @username = ENV.fetch('IMIS_USERNAME', nil)
18
18
  @password = ENV.fetch('IMIS_PASSWORD', nil)
@@ -53,6 +53,14 @@ module Usps
53
53
  # Parameters to filter out of logging
54
54
  #
55
55
  def filtered_parameters = %i[password]
56
+
57
+ private
58
+
59
+ def default_environment
60
+ return ::Rails.env if defined?(::Rails)
61
+
62
+ ActiveSupport::StringInquirer.new(ENV.fetch('IMIS_ENVIRONMENT', 'development'))
63
+ end
56
64
  end
57
65
  end
58
66
  end
@@ -36,7 +36,7 @@ module Usps
36
36
  return if property.nil?
37
37
 
38
38
  value = property['Value']
39
- value.is_a?(String) ? value : value['$value']
39
+ value.nil? || value.is_a?(String) ? value : value['$value']
40
40
  end
41
41
 
42
42
  # Hash of all property names to values
@@ -52,3 +52,4 @@ require_relative 'errors/not_found_error'
52
52
  require_relative 'errors/response_error'
53
53
  require_relative 'errors/panel_unimplemented_error'
54
54
  require_relative 'errors/unexpected_property_type_error'
55
+ require_relative 'errors/command_line_error'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised by the command line interface
7
+ #
8
+ class CommandLineError < Error; end
9
+ end
10
+ end
11
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- VERSION = '0.10.3'
5
+ VERSION = '0.11.0'
6
6
  end
7
7
  end
data/lib/usps/imis.rb CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # Core dependencies
@@ -20,6 +21,7 @@ require_relative 'imis/error'
20
21
  require_relative 'imis/api'
21
22
  require_relative 'imis/properties'
22
23
  require_relative 'imis/panels'
24
+ require_relative 'imis/command_line'
23
25
  require_relative 'imis/mocks'
24
26
  require_relative 'imis/version'
25
27
 
@@ -56,3 +58,9 @@ module Usps
56
58
  end
57
59
  end
58
60
  end
61
+
62
+ # Invoke CLI, only if invoked from the command line
63
+ #
64
+ # :nocov:
65
+ Usps::Imis::CommandLine::Interface.run if __FILE__ == $PROGRAM_NAME
66
+ # :nocov:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: usps-imis-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Fiander
@@ -23,6 +23,34 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: colorize
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.1.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: optimist
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 3.2.1
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 3.2.1
26
54
  description: A wrapper for the iMIS API.
27
55
  email: jsfiander@gmail.com
28
56
  executables: []
@@ -33,10 +61,14 @@ files:
33
61
  - lib/usps/imis.rb
34
62
  - lib/usps/imis/api.rb
35
63
  - lib/usps/imis/business_object.rb
64
+ - lib/usps/imis/command_line.rb
65
+ - lib/usps/imis/command_line/interface.rb
66
+ - lib/usps/imis/command_line/options_parser.rb
36
67
  - lib/usps/imis/config.rb
37
68
  - lib/usps/imis/data.rb
38
69
  - lib/usps/imis/error.rb
39
70
  - lib/usps/imis/errors/api_error.rb
71
+ - lib/usps/imis/errors/command_line_error.rb
40
72
  - lib/usps/imis/errors/config_error.rb
41
73
  - lib/usps/imis/errors/locked_id_error.rb
42
74
  - lib/usps/imis/errors/mapper_error.rb