swa 0.8.6 → 1.0.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.gitignore +18 -0
  3. data/.beads/issues.jsonl +5 -0
  4. data/.rubocop.yml +39 -0
  5. data/AGENTS.md +541 -0
  6. data/CLAUDE.md +5 -0
  7. data/Gemfile +9 -1
  8. data/README.md +0 -5
  9. data/Rakefile +2 -0
  10. data/bin/console +1 -0
  11. data/exe/swa +2 -1
  12. data/lib/swa/athena/catalog.rb +4 -0
  13. data/lib/swa/athena/database.rb +4 -0
  14. data/lib/swa/athena/query_execution.rb +4 -0
  15. data/lib/swa/athena/work_group.rb +4 -0
  16. data/lib/swa/cli/athena_command.rb +93 -42
  17. data/lib/swa/cli/base_command.rb +22 -14
  18. data/lib/swa/cli/cloud_formation_command.rb +11 -4
  19. data/lib/swa/cli/cloudtrail_command.rb +130 -0
  20. data/lib/swa/cli/collection_behaviour.rb +4 -2
  21. data/lib/swa/cli/data_output.rb +12 -11
  22. data/lib/swa/cli/ec2_command.rb +21 -37
  23. data/lib/swa/cli/elb_command.rb +5 -3
  24. data/lib/swa/cli/filter_options.rb +6 -1
  25. data/lib/swa/cli/glue_command.rb +84 -36
  26. data/lib/swa/cli/iam_command.rb +42 -33
  27. data/lib/swa/cli/item_behaviour.rb +4 -2
  28. data/lib/swa/cli/kms_command.rb +26 -5
  29. data/lib/swa/cli/lake_formation_command.rb +72 -11
  30. data/lib/swa/cli/main_command.rb +19 -9
  31. data/lib/swa/cli/s3_command.rb +32 -24
  32. data/lib/swa/cli/selector.rb +4 -0
  33. data/lib/swa/cli/tag_filter_options.rb +6 -2
  34. data/lib/swa/cloud_formation/stack.rb +5 -1
  35. data/lib/swa/cloud_trail/event.rb +49 -0
  36. data/lib/swa/data_presentation.rb +23 -22
  37. data/lib/swa/ec2/image.rb +7 -5
  38. data/lib/swa/ec2/instance.rb +5 -1
  39. data/lib/swa/ec2/key_pair.rb +4 -0
  40. data/lib/swa/ec2/security_group.rb +5 -3
  41. data/lib/swa/ec2/snapshot.rb +6 -4
  42. data/lib/swa/ec2/subnet.rb +5 -3
  43. data/lib/swa/ec2/tagged_resource.rb +4 -0
  44. data/lib/swa/ec2/volume.rb +7 -5
  45. data/lib/swa/ec2/vpc.rb +5 -3
  46. data/lib/swa/elb/load_balancer.rb +4 -0
  47. data/lib/swa/glue/crawl.rb +5 -1
  48. data/lib/swa/glue/crawler.rb +5 -1
  49. data/lib/swa/glue/database.rb +5 -0
  50. data/lib/swa/glue/job.rb +4 -0
  51. data/lib/swa/glue/job_bookmark_entry.rb +4 -0
  52. data/lib/swa/glue/job_run.rb +5 -1
  53. data/lib/swa/glue/partition.rb +5 -1
  54. data/lib/swa/glue/table.rb +4 -0
  55. data/lib/swa/iam/credentials.rb +7 -7
  56. data/lib/swa/iam/group.rb +5 -3
  57. data/lib/swa/iam/instance_profile.rb +5 -3
  58. data/lib/swa/iam/policy.rb +5 -3
  59. data/lib/swa/iam/role.rb +10 -3
  60. data/lib/swa/iam/role_policy.rb +5 -3
  61. data/lib/swa/iam/user.rb +5 -3
  62. data/lib/swa/kms/alias.rb +4 -0
  63. data/lib/swa/kms/key.rb +4 -0
  64. data/lib/swa/lake_formation/data_lake_settings.rb +15 -0
  65. data/lib/swa/lake_formation/permission.rb +5 -1
  66. data/lib/swa/lake_formation/resource_info.rb +4 -0
  67. data/lib/swa/lake_formation/tag.rb +25 -0
  68. data/lib/swa/polyfill.rb +3 -1
  69. data/lib/swa/record.rb +5 -3
  70. data/lib/swa/resource.rb +3 -1
  71. data/lib/swa/s3/bucket.rb +5 -3
  72. data/lib/swa/s3/object.rb +7 -5
  73. data/lib/swa/s3/object_list_entry.rb +4 -0
  74. data/lib/swa/s3/object_version.rb +13 -7
  75. data/lib/swa/version.rb +5 -1
  76. data/lib/swa.rb +2 -0
  77. data/swa.gemspec +29 -25
  78. metadata +63 -29
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "swa/record"
2
4
 
3
5
  module Swa
6
+
4
7
  module Athena
5
8
 
6
9
  class Catalog < Record
@@ -21,4 +24,5 @@ module Swa
21
24
  end
22
25
 
23
26
  end
27
+
24
28
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "swa/record"
2
4
 
3
5
  module Swa
6
+
4
7
  module Athena
5
8
 
6
9
  class Database < Record
@@ -18,4 +21,5 @@ module Swa
18
21
  end
19
22
 
20
23
  end
24
+
21
25
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "swa/record"
2
4
 
3
5
  module Swa
6
+
4
7
  module Athena
5
8
 
6
9
  class QueryExecution < Record
@@ -25,4 +28,5 @@ module Swa
25
28
  end
26
29
 
27
30
  end
31
+
28
32
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "swa/record"
2
4
 
3
5
  module Swa
6
+
4
7
  module Athena
5
8
 
6
9
  class WorkGroup < Record
@@ -20,4 +23,5 @@ module Swa
20
23
  end
21
24
 
22
25
  end
26
+
23
27
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "aws-sdk-athena"
4
+ require "bytesize"
2
5
  require "csv"
3
6
  require "swa/cli/base_command"
4
7
  require "swa/cli/collection_behaviour"
@@ -9,12 +12,13 @@ require "swa/athena/query_execution"
9
12
  require "swa/athena/work_group"
10
13
 
11
14
  module Swa
15
+
12
16
  module CLI
13
17
 
14
18
  class AthenaCommand < BaseCommand
15
19
 
16
20
  option "--catalog", "NAME", "Data catalog name", default: "AwsDataCatalog"
17
- option %w(--workgroup -W), "NAME", "Workgroup name"
21
+ option %w[--workgroup -W], "NAME", "Workgroup name"
18
22
 
19
23
  subcommand "catalogs", "Show catalogs" do
20
24
 
@@ -34,8 +38,6 @@ module Swa
34
38
 
35
39
  include ItemBehaviour
36
40
 
37
- private
38
-
39
41
  def item
40
42
  Swa::Athena::Database.new(
41
43
  athena_client.get_database(catalog_name: catalog, database_name: database).database
@@ -48,22 +50,56 @@ module Swa
48
50
 
49
51
  include CollectionBehaviour
50
52
 
51
- private
52
-
53
53
  def collection
54
54
  query_for(:list_databases, :database_list, Swa::Athena::Database, catalog_name: catalog)
55
55
  end
56
56
 
57
57
  end
58
58
 
59
+ module CanOutputResults
60
+
61
+ extend Clamp::Option::Declaration
62
+
63
+ option ["--text", "-1"], :flag, "output first column as text"
64
+
65
+ def display_query_results(query_results)
66
+ output_rows(
67
+ query_results.lazy.flat_map do |results|
68
+ results.result_set.rows
69
+ end
70
+ )
71
+ end
72
+
73
+ def output_rows(rows)
74
+ if text?
75
+ output_rows_as_text(rows)
76
+ else
77
+ output_rows_as_csv(rows)
78
+ end
79
+ end
80
+
81
+ def output_rows_as_csv(rows)
82
+ CSV($stdout.dup) do |csv|
83
+ rows.each do |row|
84
+ csv << row.data.map(&:var_char_value)
85
+ end
86
+ end
87
+ end
88
+
89
+ def output_rows_as_text(rows)
90
+ rows.drop(1).each do |row|
91
+ puts row.data.first.var_char_value
92
+ end
93
+ end
94
+
95
+ end
96
+
59
97
  subcommand ["execution", "query-execution"], "Inspect query execution" do
60
98
 
61
99
  parameter "ID", "execution ID", attribute_name: :execution_id
62
100
 
63
101
  include ItemBehaviour
64
102
 
65
- private
66
-
67
103
  def item
68
104
  Swa::Athena::QueryExecution.new(
69
105
  athena_client.get_query_execution(query_execution_id: execution_id).query_execution
@@ -72,9 +108,10 @@ module Swa
72
108
 
73
109
  subcommand "results", "Show results" do
74
110
 
111
+ include CanOutputResults
112
+
75
113
  def execute
76
- query_results = athena_client.get_query_results(query_execution_id: execution_id)
77
- output_results_as_csv(query_results.result_set)
114
+ display_query_results(athena_client.get_query_results(query_execution_id: execution_id))
78
115
  end
79
116
 
80
117
  end
@@ -95,10 +132,28 @@ module Swa
95
132
 
96
133
  option ["--database", "-D"], "NAME", "Database name"
97
134
  option ["--output-location", "-O"], "S3_URL", "S3 output location for query results"
135
+ option ["--explain", "-E"], :flag, "Explain query"
136
+ option ["--timeout"], "SECONDS", "Time to wait for completion", default: 120, &method(:Integer)
98
137
 
99
- parameter "[QUERY]", "SQL query", :default => "STDIN"
138
+ include CanOutputResults
139
+
140
+ parameter "[QUERY]", "SQL query", default: "STDIN"
100
141
 
101
142
  def execute
143
+ if explain?
144
+ self.query = "EXPLAIN #{query}"
145
+ self.text = true
146
+ end
147
+ results, statistics = execute_query(query)
148
+ show_statistics(statistics)
149
+ display_query_results(results)
150
+ end
151
+
152
+ def default_query
153
+ $stdin.read
154
+ end
155
+
156
+ def execute_query(query)
102
157
  start_query_response = athena_client.start_query_execution(
103
158
  query_execution_context: {
104
159
  catalog: catalog,
@@ -110,31 +165,36 @@ module Swa
110
165
  },
111
166
  work_group: workgroup
112
167
  )
113
- wait_for_query(start_query_response.query_execution_id)
114
- query_results = athena_client.get_query_results(query_execution_id: start_query_response.query_execution_id)
115
- if query =~ /\AEXPLAIN/
116
- output_explain_result(query_results.result_set)
117
- else
118
- output_results_as_csv(query_results.result_set)
168
+ query_execution_id = start_query_response.query_execution_id
169
+ logger.debug "query_execution_id = #{query_execution_id}"
170
+ query_still_running = true
171
+ query_execution_output = wait_for_query(query_execution_id)
172
+ query_still_running = false
173
+ results = athena_client.get_query_results(query_execution_id: query_execution_id)
174
+ [results, query_execution_output.query_execution.statistics]
175
+ rescue Aws::Waiters::Errors::FailureStateError => e
176
+ query_still_running = false
177
+ signal_error e.response.query_execution.status.state_change_reason
178
+ ensure
179
+ if query_still_running
180
+ logger.warn "Cancelling query #{query_execution_id}"
181
+ athena_client.stop_query_execution(query_execution_id: query_execution_id)
119
182
  end
120
183
  end
121
184
 
122
- private
123
-
124
- def default_query
125
- $stdin.read
126
- end
127
-
128
185
  def wait_for_query(query_execution_id)
129
- QueryCompletionWaiter.new(client: athena_client).wait(query_execution_id: query_execution_id)
130
- rescue Aws::Waiters::Errors::FailureStateError => error
131
- signal_error error.response.query_execution.status.state_change_reason
186
+ poll_interval = 5
187
+ max_attempts = timeout / poll_interval
188
+ QueryCompletionWaiter.new(
189
+ client: athena_client,
190
+ max_attempts: max_attempts,
191
+ delay: poll_interval
192
+ ).wait(query_execution_id: query_execution_id)
132
193
  end
133
194
 
134
- def output_explain_result(result_set)
135
- result_set.rows.drop(1).each do |row|
136
- puts row.data.first.var_char_value
137
- end
195
+ def show_statistics(statistics)
196
+ logger.debug "Total execution time = #{statistics.total_execution_time_in_millis} ms"
197
+ logger.debug "Data scanned = #{ByteSize.bytes(statistics.data_scanned_in_bytes)}"
138
198
  end
139
199
 
140
200
  end
@@ -143,8 +203,6 @@ module Swa
143
203
 
144
204
  include CollectionBehaviour
145
205
 
146
- private
147
-
148
206
  def collection
149
207
  query_for(:list_work_groups, :work_groups, Swa::Athena::WorkGroup)
150
208
  end
@@ -161,14 +219,6 @@ module Swa
161
219
  model.list_from_query(athena_client, query_method, response_key, **query_args)
162
220
  end
163
221
 
164
- def output_results_as_csv(result_set)
165
- CSV($stdout.dup) do |csv|
166
- result_set.rows.each do |row|
167
- csv << row.data.map(&:var_char_value)
168
- end
169
- end
170
- end
171
-
172
222
  class QueryCompletionWaiter
173
223
 
174
224
  def initialize(options)
@@ -183,19 +233,19 @@ module Swa
183
233
  "matcher" => "path",
184
234
  "argument" => "query_execution.status.state",
185
235
  "expected" => "SUCCEEDED",
186
- "state" => "success",
236
+ "state" => "success"
187
237
  },
188
238
  {
189
239
  "matcher" => "path",
190
240
  "argument" => "query_execution.status.state",
191
241
  "expected" => "FAILED",
192
- "state" => "failure",
242
+ "state" => "failure"
193
243
  },
194
244
  {
195
245
  "matcher" => "path",
196
246
  "argument" => "query_execution.status.state",
197
247
  "expected" => "CANCELLED",
198
- "state" => "error",
248
+ "state" => "error"
199
249
  }
200
250
  ]
201
251
  )
@@ -211,4 +261,5 @@ module Swa
211
261
  end
212
262
 
213
263
  end
264
+
214
265
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "chronic"
2
4
  require "clamp"
3
5
  require "console_logger"
@@ -7,26 +9,29 @@ require "swa/cli/data_output"
7
9
  require "yaml"
8
10
 
9
11
  module Swa
12
+
10
13
  module CLI
11
14
 
12
15
  class BaseCommand < Clamp::Command
13
16
 
14
17
  option "--region", "REGION", "AWS region"
15
18
  option "--access-key", "KEY", "AWS access key",
16
- :attribute_name => :access_key_id
19
+ attribute_name: :access_key_id
17
20
  option "--secret-key", "KEY", "AWS secret key",
18
- :attribute_name => :secret_access_key
21
+ attribute_name: :secret_access_key
19
22
  option "--session-token", "KEY", "AWS security token",
20
- :attribute_name => :session_token
23
+ attribute_name: :session_token
21
24
 
22
25
  include DataOutput
23
26
 
24
27
  option ["--debug"], :flag, "enable debugging"
25
28
 
26
29
  def run(arguments)
27
- super(arguments)
30
+ super
28
31
  rescue Aws::Errors::MissingCredentialsError
29
32
  signal_error "no credentials provided"
33
+ rescue Aws::Errors::InvalidProcessCredentialsPayload => e
34
+ signal_error e.message
30
35
  rescue Aws::Errors::MissingRegionError, Aws::Errors::InvalidRegionError => e
31
36
  signal_error e.message
32
37
  rescue Aws::Errors::ServiceError => e
@@ -37,6 +42,7 @@ module Swa
37
42
 
38
43
  def parse_subcommand
39
44
  return false unless self.class.has_subcommands?
45
+
40
46
  request_help if subcommand_name == "?"
41
47
  super
42
48
  end
@@ -47,28 +53,30 @@ module Swa
47
53
 
48
54
  def aws_config
49
55
  {
50
- :access_key_id => access_key_id,
51
- :secret_access_key => secret_access_key,
52
- :session_token => session_token,
53
- :region => region,
54
- :logger => logger, :log_level => :debug
55
- }.reject { |_k, v| v.nil? }
56
+ access_key_id: access_key_id,
57
+ secret_access_key: secret_access_key,
58
+ session_token: session_token,
59
+ region: region,
60
+ logger: logger, log_level: :debug
61
+ }.compact
56
62
  end
57
63
 
58
64
  def parse(arguments)
59
- if arguments.first =~ /^(\w+)-[0-9a-f]+$/
60
- arguments = [$1] + arguments if self.class.find_subcommand($1)
65
+ if (arguments.first =~ /^(\w+)-[0-9a-f]+$/) && self.class.find_subcommand(::Regexp.last_match(1))
66
+ arguments = [::Regexp.last_match(1)] + arguments
61
67
  end
62
- super(arguments)
68
+ super
63
69
  end
64
70
 
65
71
  def parse_datetime(datetime_string)
66
- result = Chronic.parse(datetime_string, :guess => false, :endian_precedence => :little)
72
+ result = Chronic.parse(datetime_string, guess: false, endian_precedence: :little)
67
73
  raise ArgumentError, "unrecognised date/time #{datetime_string.inspect}" unless result
74
+
68
75
  result
69
76
  end
70
77
 
71
78
  end
72
79
 
73
80
  end
81
+
74
82
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "aws-sdk-cloudformation"
2
4
  require "swa/cli/base_command"
3
5
  require "swa/cli/collection_behaviour"
@@ -5,6 +7,7 @@ require "swa/cli/item_behaviour"
5
7
  require "swa/cloud_formation/stack"
6
8
 
7
9
  module Swa
10
+
8
11
  module CLI
9
12
 
10
13
  class CloudFormationCommand < BaseCommand
@@ -23,8 +26,13 @@ module Swa
23
26
 
24
27
  end
25
28
 
26
- %w(parameters outputs resources).each do |thing|
27
- class_eval <<-RUBY
29
+ %w[parameters outputs resources].each do |thing|
30
+ # subcommand "parameters", "Show parameters" do
31
+ # def execute
32
+ # display_data(stack.parameters)
33
+ # end
34
+ # end
35
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
28
36
  subcommand "#{thing}", "Show #{thing}" do
29
37
  def execute
30
38
  display_data(stack.#{thing})
@@ -47,8 +55,6 @@ module Swa
47
55
 
48
56
  include CollectionBehaviour
49
57
 
50
- private
51
-
52
58
  def collection
53
59
  query_for(:stacks, Swa::CloudFormation::Stack)
54
60
  end
@@ -69,4 +75,5 @@ module Swa
69
75
  end
70
76
 
71
77
  end
78
+
72
79
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-cloudtrail"
4
+ require "swa/cli/base_command"
5
+ require "swa/cli/collection_behaviour"
6
+ require "swa/cloud_trail/event"
7
+
8
+ module Swa
9
+
10
+ module CLI
11
+
12
+ class CloudtrailCommand < BaseCommand
13
+
14
+ subcommand "events", "CloudTrail events" do
15
+
16
+ include CollectionBehaviour
17
+
18
+ option ["-n", "--max"], "N", "number of events to return", default: 50 do |n|
19
+ Integer(n)
20
+ end
21
+
22
+ option "--source", "SERVICE", "filter by event source (e.g. kms)" do |value|
23
+ value = "#{value}.amazonaws.com" unless value.include?(".")
24
+ compile_pattern(value)
25
+ end
26
+
27
+ option "--name", "EVENT_NAME", "filter by event name (e.g. Decrypt)" do |value|
28
+ compile_pattern(value)
29
+ end
30
+
31
+ option %w[-a --after], "TIME", "filter events after this time" do |value|
32
+ parse_datetime(value).end
33
+ end
34
+
35
+ option %w[-b --before], "TIME", "filter events before this time" do |value|
36
+ parse_datetime(value).begin
37
+ end
38
+
39
+ option "--where", "FIELD=VALUE", "filter by field (can be specified multiple times)",
40
+ multivalued: true do |spec|
41
+ field, value = spec.split("=", 2)
42
+ raise ArgumentError, "invalid --where format, expected FIELD=VALUE" if field.nil? || value.nil?
43
+
44
+ { field: field, pattern: compile_pattern(value) }
45
+ end
46
+
47
+ private
48
+
49
+ def collection
50
+ query_args = {}
51
+
52
+ # CloudTrail API only supports ONE lookup attribute at a time
53
+ # Use API filter for exact matches (not patterns with wildcards)
54
+ if name.is_a?(String)
55
+ query_args[:lookup_attributes] = [{ attribute_key: "EventName", attribute_value: name }]
56
+ elsif source.is_a?(String)
57
+ query_args[:lookup_attributes] = [{ attribute_key: "EventSource", attribute_value: source }]
58
+ end
59
+
60
+ # Add time range filters
61
+ query_args[:start_time] = after if after
62
+ query_args[:end_time] = before if before
63
+
64
+ events = query_for(:lookup_events, :events, Swa::CloudTrail::Event, **query_args)
65
+
66
+ # Apply programmatic filters
67
+ events = events.select { |event| name === event.event_name } if name # rubocop:disable Style/CaseEquality
68
+ events = events.select { |event| source === event.event_source } if source # rubocop:disable Style/CaseEquality
69
+
70
+ # Apply --where filters
71
+ if where_list && !where_list.empty?
72
+ events = events.select do |event|
73
+ where_list.all? { |condition| matches_where_condition?(event, condition) }
74
+ end
75
+ end
76
+
77
+ events.take(max)
78
+ end
79
+
80
+ def compile_pattern(value)
81
+ if value.include?("*") || value.include?("?")
82
+ # Convert shell-style wildcards to regex
83
+ regex_pattern = Regexp.escape(value).gsub('\*', ".*").gsub('\?', ".")
84
+ Regexp.new("^#{regex_pattern}$", Regexp::IGNORECASE)
85
+ else
86
+ # Return as string for exact match and API filtering
87
+ value
88
+ end
89
+ end
90
+
91
+ def matches_where_condition?(event, condition)
92
+ field_path = condition[:field]
93
+ pattern = condition[:pattern]
94
+
95
+ # Get the event data as a hash
96
+ event_data = event.data
97
+
98
+ # Extract field value using JMESPath
99
+ begin
100
+ field_value = JMESPath.search(field_path, event_data)
101
+ rescue JMESPath::Errors::SyntaxError
102
+ signal_error("invalid field path in --where: #{field_path}")
103
+ end
104
+
105
+ # Convert field value to string for matching
106
+ return false if field_value.nil?
107
+
108
+ field_value_str = field_value.to_s
109
+
110
+ # Match using pattern (either string or regex)
111
+ pattern === field_value_str # rubocop:disable Style/CaseEquality
112
+ end
113
+
114
+ end
115
+
116
+ protected
117
+
118
+ def cloudtrail_client
119
+ ::Aws::CloudTrail::Client.new(aws_config)
120
+ end
121
+
122
+ def query_for(query_method, response_key, model, **query_args)
123
+ model.list_from_query(cloudtrail_client, query_method, response_key, **query_args)
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "swa/cli/selector"
2
4
 
3
5
  module Swa
6
+
4
7
  module CLI
5
8
 
6
9
  module CollectionBehaviour
7
10
 
8
11
  def self.included(target)
9
-
10
12
  target.default_subcommand = "summary"
11
13
 
12
14
  target.subcommand ["summary", "s"], "One-line summary" do
@@ -36,7 +38,6 @@ module Swa
36
38
  end
37
39
 
38
40
  end
39
-
40
41
  end
41
42
 
42
43
  def selector
@@ -50,4 +51,5 @@ module Swa
50
51
  end
51
52
 
52
53
  end
54
+
53
55
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "clamp"
2
4
 
3
5
  module Swa
6
+
4
7
  module CLI
5
8
 
6
9
  module DataOutput
@@ -8,9 +11,9 @@ module Swa
8
11
  extend Clamp::Option::Declaration
9
12
 
10
13
  option "--format", "FORMAT", "format for data output",
11
- :attribute_name => :output_format,
12
- :environment_variable => "SWA_OUTPUT_FORMAT",
13
- :default => "YAML"
14
+ attribute_name: :output_format,
15
+ environment_variable: "SWA_OUTPUT_FORMAT",
16
+ default: "YAML"
14
17
 
15
18
  option ["--json", "-J"], :flag, "output data in JSON format" do
16
19
  self.output_format = "JSON"
@@ -22,9 +25,8 @@ module Swa
22
25
 
23
26
  def output_format=(arg)
24
27
  arg = arg.upcase
25
- unless %w(JSON YAML).member?(arg)
26
- raise ArgumentError, "unrecognised data format: #{arg.inspect}"
27
- end
28
+ raise ArgumentError, "unrecognised data format: #{arg.inspect}" unless %w[JSON YAML].member?(arg)
29
+
28
30
  @output_format = arg
29
31
  end
30
32
 
@@ -33,7 +35,7 @@ module Swa
33
35
  def format_data(data)
34
36
  case output_format
35
37
  when "JSON"
36
- MultiJson.dump(data, :pretty => true)
38
+ MultiJson.dump(data, pretty: true)
37
39
  when "YAML"
38
40
  YAML.dump(data)
39
41
  else
@@ -42,15 +44,14 @@ module Swa
42
44
  end
43
45
 
44
46
  def display_data(data, jmespath_expression = nil)
45
- unless jmespath_expression.nil?
46
- data = JMESPath.search(jmespath_expression, data)
47
- end
47
+ data = JMESPath.search(jmespath_expression, data) unless jmespath_expression.nil?
48
48
  puts format_data(data)
49
- rescue JMESPath::Errors::SyntaxError => e
49
+ rescue JMESPath::Errors::SyntaxError
50
50
  signal_error("invalid JMESPath expression")
51
51
  end
52
52
 
53
53
  end
54
54
 
55
55
  end
56
+
56
57
  end