decombobulate 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a59dbe52a60fb02fb778321ca0c94e0f1f0f9dcb398b9ae4eb99d742b796ec84
4
- data.tar.gz: 398be19030a30753ee0edbbcd903eee8db90052c149b0867c64759eb1730f273
3
+ metadata.gz: e84472027fcfa0174c8c7da896340b34a3c7f15f4575df81c756e6eac38ae171
4
+ data.tar.gz: 39d3ec49fb48c092b7501f7bd158c9b63272b341632b0844f299cfae7e080882
5
5
  SHA512:
6
- metadata.gz: 35ff913019ae6af28e0dd76cfb70510318fd3e2dc62a696d8ebf47308ba19d097f33e1a05af4c9875e8067ed1b2214c3609d6227e12c60c1d47840c663a93f87
7
- data.tar.gz: 63531351bf5c51a5620439a6427a050f78bc541cf8cedf296d6018f92d5bdf9374673a9de39e220919ccb57a88ae1386b6691a3ca1df6050750ffe4d4f1b40d5
6
+ metadata.gz: 776e3e69329510030a4b25bddecdfe247dbbb55cc8812a04fce13aa706747ca5dc29440b88ee05baf3dc8e8dec81ea9e46b00092a46f4366415768f8c02a4d91
7
+ data.tar.gz: 42f98e4aef1abc545856536e84a686b64b6f17ac9d87c320309f9293991fce17f32c200fb669eaf2c8d7c1538e4353ab91ffe8651c2f70f6f542523a3892d5a2
data/.rubocop.yml CHANGED
@@ -1,35 +1,10 @@
1
- require:
2
- - rubocop-rails
3
- - rubocop-performance
4
-
5
- inherit_gem:
6
- rubocop-rails_config:
7
- - config/rails.yml
8
-
9
1
  AllCops:
10
2
  Exclude:
11
- - db/schema.rb
12
- - 'node_modules/**/*'
13
- - 'redis-stable/**/*'
14
3
  - 'bin/**/*'
15
- - 'vendor/**/*'
4
+ - 'pkg/**/*'
5
+ - 'sig/**/*'
16
6
  TargetRubyVersion: 3.0
17
7
 
18
- # Join tables don't really need timestamps
19
- Rails/CreateTableWithTimestamps:
20
- Exclude:
21
- # - 'db/migrate/20180128231930_create_organizations_and_events.rb'
22
-
23
- # Rails generates this file
24
- Style/BlockComments:
25
- Exclude:
26
- - 'db/seeds.rb'
27
-
28
- # The schema file is autogenerated
29
- Layout/EmptyLinesAroundBlockBody:
30
- Exclude:
31
- - 'db/schema.rb'
32
-
33
8
  # This sets us to use the standard Rails format instead of Rubocop's
34
9
  # opinionated Ruby style.
35
10
  Style/FrozenStringLiteralComment:
@@ -40,11 +15,6 @@ Style/FrozenStringLiteralComment:
40
15
  Style/ClassAndModuleChildren:
41
16
  Enabled: false
42
17
 
43
- # Rails generates this file
44
- Layout/IndentationStyle:
45
- Exclude:
46
- - 'db/seeds.rb'
47
-
48
18
  # Temporarily turn this off
49
19
  Metrics/AbcSize:
50
20
  Enabled: false
@@ -58,18 +28,9 @@ Lint/RescueException:
58
28
  Lint/Debugger:
59
29
  Enabled: true
60
30
 
61
- Rails/HasManyOrHasOneDependent:
62
- Enabled: true
63
-
64
- Rails/HasAndBelongsToMany:
65
- Enabled: true
66
-
67
31
  Style/NumericPredicate:
68
32
  Enabled: true
69
33
 
70
- Rails/RefuteMethods:
71
- Enabled: false
72
-
73
34
  # This sets us to use the standard Rails format instead of Rubocop's
74
35
  # opinionated Ruby style.
75
36
  Layout/EmptyLinesAroundAccessModifier:
data/Gemfile CHANGED
@@ -5,8 +5,4 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in decombobulate.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
9
-
10
- gem "minitest", "~> 5.0"
11
-
12
- gem "rubocop", "~> 1.21"
8
+ gem "rake", "~> 13.1"
data/Gemfile.lock CHANGED
@@ -1,70 +1,63 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decombobulate (0.1.1)
4
+ decombobulate (0.1.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (7.0.7)
9
+ activesupport (7.0.8)
10
10
  concurrent-ruby (~> 1.0, >= 1.0.2)
11
11
  i18n (>= 1.6, < 2)
12
12
  minitest (>= 5.1)
13
13
  tzinfo (~> 2.0)
14
14
  ast (2.4.2)
15
- base64 (0.1.1)
16
15
  concurrent-ruby (1.2.2)
17
16
  i18n (1.14.1)
18
17
  concurrent-ruby (~> 1.0)
19
- json (2.6.3)
18
+ json (2.7.0)
20
19
  language_server-protocol (3.17.0.3)
21
- minitest (5.19.0)
20
+ minitest (5.20.0)
22
21
  parallel (1.23.0)
23
- parser (3.2.2.3)
22
+ parser (3.2.2.4)
24
23
  ast (~> 2.4.1)
25
24
  racc
26
- racc (1.7.1)
27
- rack (3.0.8)
25
+ racc (1.7.3)
28
26
  rainbow (3.1.1)
29
- rake (13.0.6)
30
- regexp_parser (2.8.1)
27
+ rake (13.1.0)
28
+ regexp_parser (2.8.3)
31
29
  rexml (3.2.6)
32
- rubocop (1.56.0)
33
- base64 (~> 0.1.1)
30
+ rubocop (1.58.0)
34
31
  json (~> 2.3)
35
32
  language_server-protocol (>= 3.17.0)
36
33
  parallel (~> 1.10)
37
- parser (>= 3.2.2.3)
34
+ parser (>= 3.2.2.4)
38
35
  rainbow (>= 2.2.2, < 4.0)
39
36
  regexp_parser (>= 1.8, < 3.0)
40
37
  rexml (>= 3.2.5, < 4.0)
41
- rubocop-ast (>= 1.28.1, < 2.0)
38
+ rubocop-ast (>= 1.30.0, < 2.0)
42
39
  ruby-progressbar (~> 1.7)
43
40
  unicode-display_width (>= 2.4.0, < 3.0)
44
- rubocop-ast (1.29.0)
41
+ rubocop-ast (1.30.0)
45
42
  parser (>= 3.2.1.0)
46
- rubocop-performance (1.19.0)
43
+ rubocop-performance (1.19.1)
47
44
  rubocop (>= 1.7.0, < 2.0)
48
45
  rubocop-ast (>= 0.4.0)
49
- rubocop-rails (2.20.2)
50
- activesupport (>= 4.2.0)
51
- rack (>= 1.1)
52
- rubocop (>= 1.33.0, < 2.0)
53
46
  ruby-progressbar (1.13.0)
54
47
  tzinfo (2.0.6)
55
48
  concurrent-ruby (~> 1.0)
56
- unicode-display_width (2.4.2)
49
+ unicode-display_width (2.5.0)
57
50
 
58
51
  PLATFORMS
59
52
  arm64-darwin-22
60
53
 
61
54
  DEPENDENCIES
55
+ activesupport (= 7.0.8)
62
56
  decombobulate!
63
- minitest (~> 5.0)
64
- rake (~> 13.0)
65
- rubocop (~> 1.21)
57
+ minitest
58
+ rake (~> 13.1)
59
+ rubocop
66
60
  rubocop-performance
67
- rubocop-rails
68
61
 
69
62
  BUNDLED WITH
70
- 2.4.9
63
+ 2.4.22
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Decombobulate
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/decombobulate.rb CHANGED
@@ -1,30 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "decombobulate/version"
4
- require "JSON"
5
- require "CSV"
4
+ require "json"
5
+ require "csv"
6
+ require "debug"
6
7
 
7
8
  class Decombobulate
8
9
  class Error < StandardError; end
9
10
 
10
- attr_reader :headers, :rows
11
+ attr_reader :rows, :headers
11
12
 
12
13
  def initialize(json)
13
14
  # Parse the JSON if we need to
14
15
  json = Json.parse(json) if json.is_a?(String)
15
16
 
16
-
17
17
  # Yes, I tried a case/when statement but for some reason that doesn't work when looking at class types
18
- if json.is_a?(Array)
19
- @headers = parse_headers_from_json(json.first)
20
- @rows = json.map do |json_row|
21
- collapse_json_object_row_to_csv_row(json_row)
18
+ columns = json_to_csv(json)
19
+
20
+ @rows = []
21
+ if columns.is_a?(Array)
22
+ merged_columns = {}
23
+
24
+ # Iterate through the array, finding each key, adding it to the headers row if it's not there already
25
+ # then merge them
26
+
27
+ columns.each do |entry|
28
+ entry.keys.each do |key|
29
+ merged_columns[key] = [] unless merged_columns.has_key?(key)
30
+ merged_columns[key] << entry[key]
31
+ merged_columns[key] = merged_columns[key].flatten
32
+ end
22
33
  end
23
- elsif json.is_a?(Hash)
24
- @headers = parse_headers_from_json(json)
25
- @rows = [collapse_json_object_row_to_csv_row(json)]
26
- else
27
- raise "JSON object must have an array or object as the top level object."
34
+ columns = merged_columns
35
+ end
36
+ # 6.) Now we have a hash of @columns, we need to turn it into rows
37
+ # We're going to assume that all the @columns are the same length
38
+ # So we'll just iterate over the first column and add the values to the row
39
+ @headers = columns.keys.map(&:to_s)
40
+ columns[columns.keys.first].size.times do |index|
41
+ @rows << columns.keys.map { |key| columns[key][index] }
28
42
  end
29
43
  end
30
44
 
@@ -50,54 +64,169 @@ class Decombobulate
50
64
  csv_final
51
65
  end
52
66
 
53
- def parse_headers_from_json(json, parent_key = nil)
54
- @headers ||= []
67
+ private
55
68
 
56
- if json.is_a?(Hash)
57
- json.keys.each do |key|
58
- flattened_key = parent_key.nil? ? key.to_s : "#{parent_key}.#{key}"
69
+ def json_to_csv(json = {}, nested_name = nil, depth: 0)
70
+ @columns ||= {}
71
+ unless json.is_a?(Hash) || json.is_a?(Array)
72
+ @columns[nested_name] = [] unless @columns.has_key?(nested_name)
73
+ @columns[nested_name] << json
74
+ return @columns
75
+ end
59
76
 
60
- if json[key].is_a?(Hash) || json[key].is_a?(Array)
61
- parse_headers_from_json(json[key], flattened_key)
62
- else
63
- @headers << flattened_key
64
- end
77
+ if json.is_a?(Array)
78
+ json.each do |j|
79
+ json_to_csv(j, depth: depth + 1)
65
80
  end
66
- elsif json.is_a?(Array)
67
- json.each do |j_object|
68
- parse_headers_from_json(j_object, parent_key)
81
+ return @columns
82
+ # return @columns
83
+ end
84
+ # OK, so we need arbitrarily long fields so we're doing a different algorithm I literally came up
85
+ # with in five minutes one morning before falling back asleep, let's see if it works.
86
+ #
87
+ # Basically we're going to represent the @columns separately, and hope the row line up correctly
88
+ # This allows us to add new @columns and fill up the empty fields as needed
89
+ #
90
+ # 1. Make a hash of arrays
91
+ # This is already done in the function call
92
+
93
+ # 2.) Add all the keys in the JSON at the level to the hash
94
+ json.keys.each do |key|
95
+ column_name = nested_name.nil? ? key : "#{nested_name}.#{key}".to_sym
96
+ @columns[column_name] = [] unless @columns.has_key?(column_name)
97
+ end
98
+
99
+ # 3.) Go through each key, if it's a bare value just add it in
100
+ json.keys.each do |key|
101
+ value = json[key]
102
+ # 4.) If it's an array, start adding @columns in the format "key_1" "key_2" for the length of the array
103
+ if value.is_a?(Array)
104
+ column_name = nested_name.nil? ? key : "#{nested_name}.#{key}".to_sym
105
+ # We want to get rid of the key if it's an array, since we're going to be adding @columns
106
+ @columns.delete(column_name)
107
+
108
+ # Now add the values to the @columns
109
+ value.each_with_index do |array_value, index|
110
+ column_name = nested_name.nil? ? "#{key}.#{index + 1}".to_sym : "#{nested_name}.#{key}.#{index + 1}".to_sym
111
+ json_to_csv(array_value, column_name, depth: depth + 1)
112
+ end
113
+ elsif value.is_a?(Hash)
114
+ # 5.) If it's a hash, recursively call this function and add the @columns to the hash
115
+ column_name = nested_name.nil? ? key : "#{nested_name}.#{key}".to_sym
116
+ @columns.delete(column_name)
117
+ json_to_csv(value, column_name, depth: depth + 1)
118
+ else
119
+ column_name = nested_name.nil? ? key.to_sym : "#{nested_name}.#{key}".to_sym
120
+ @columns[column_name] = [] unless @columns.has_key?(column_name) || !@columns[column_name].nil?
121
+
122
+ # Fill the column with nils up to the current length of the other @columns
123
+ # get the longest column size
124
+ begin
125
+ longest_column_size = @columns.values.map(&:size).max
126
+ rescue StandardError => e
127
+ debugger
128
+ end
129
+
130
+
131
+ while @columns[column_name].size < longest_column_size - 1
132
+ @columns[column_name] << nil
133
+ end if longest_column_size > 1
134
+
135
+ @columns[column_name] << value
69
136
  end
70
- else
71
- # This mean's we either have a parsing error or we're in an array.
72
- # I'm assuming the JSON coming in is valid, so we're going to just skip this
73
- return nil
74
137
  end
75
138
 
76
- @headers
139
+ @columns = group_columns(@columns)
77
140
  end
78
141
 
79
- def collapse_json_object_row_to_csv_row(json)
80
- # Time to do a graph traversal! And recursively! Java 103 is usefuL!!!!
81
- row_array = []
142
+ def group_columns(columns)
143
+ grouped_keys_array = group_keys(columns.keys)
144
+
145
+ reordered_columns = {}
146
+ grouped_keys_array.each do |key|
147
+ # Try to find the key using the string or symbol version
148
+ # First convert to string because we don't know what type this is
149
+ key = key.to_s
150
+ key = key.to_sym unless columns.has_key?(key)
151
+ new_key = key.to_sym
152
+ reordered_columns[new_key] = columns[key]
153
+ end
82
154
 
83
- if json.is_a?(Array)
84
- json.each do |j_object|
85
- if j_object.is_a?(Array) || j_object.is_a?(Hash)
86
- row_array << collapse_json_object_row_to_csv_row(j_object)
87
- else
88
- row_array << j_object
155
+ puts "reordered: #{reordered_columns}"
156
+
157
+ reordered_columns
158
+ end
159
+
160
+ def group_keys(keys = [], previous_key = nil)
161
+ # Arrange the columns so that they're grouped as nested in the json
162
+ ordered_keys = []
163
+
164
+ keys.each do |key|
165
+ key_path = key.to_s.split(".")
166
+ if key_path.size > 1
167
+ # Grab all keys that include the same first part
168
+ subcolumns = []
169
+ # get the keys and call recursively
170
+
171
+ keys.each do |k|
172
+ k_path = k.to_s.split(".")
173
+ if k_path[0] == key_path[0]
174
+ subcolumns << k_path[1..-1].join(".")
175
+ end
89
176
  end
90
- end
91
- elsif json.is_a?(Hash)
92
- json.keys.each do |key|
93
- if json[key].is_a?(Array) || json[key].is_a?(Hash)
94
- row_array << collapse_json_object_row_to_csv_row(json[key])
177
+
178
+ # Since we don't want to sort the same top we need to remove the others
179
+ for_recursion = subcolumns.map do |column_name|
180
+ column_name.to_sym
181
+ end
182
+
183
+ for_recursion.each do |key_to_delete|
184
+ keys.delete("#{key_path[0]}.#{key_to_delete}".to_sym)
185
+ end
186
+
187
+ grouped_keys = group_keys(for_recursion, key_path[0])
188
+
189
+ if previous_key.nil?
190
+ ordered_keys << grouped_keys
95
191
  else
96
- row_array << json[key]
192
+ ordered_keys << grouped_keys.map do |subkey|
193
+ "#{previous_key}.#{subkey}".to_sym
194
+ end
97
195
  end
196
+
197
+ else
198
+ to_add = previous_key.nil? ? key : "#{previous_key}.#{key}"
199
+ next if to_add.end_with?(".")
200
+
201
+ ordered_keys << to_add
98
202
  end
99
203
  end
100
-
101
- row_array.flatten
204
+ ordered_keys.flatten.map(&:to_sym)
102
205
  end
103
206
  end
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decombobulate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Guess
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-15 00:00:00.000000000 Z
11
+ date: 2023-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rubocop-rails
28
+ name: rubocop-performance
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop-performance
42
+ name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 7.0.8
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 7.0.8
55
69
  description: Convert JSON structures to CSVs
56
70
  email:
57
71
  - cguess@gmail.com
@@ -85,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - ">="
87
101
  - !ruby/object:Gem::Version
88
- version: 2.6.0
102
+ version: 2.7.0
89
103
  required_rubygems_version: !ruby/object:Gem::Requirement
90
104
  requirements:
91
105
  - - ">="
92
106
  - !ruby/object:Gem::Version
93
107
  version: '0'
94
108
  requirements: []
95
- rubygems_version: 3.4.10
109
+ rubygems_version: 3.4.22
96
110
  signing_key:
97
111
  specification_version: 4
98
112
  summary: Convert JSON structures to CSVs