decombobulate 0.1.3 → 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: 153e6f51eadf84455fdd682c8c6eb3fb986f9dfafaa833f3c60e0e491a08fc94
4
- data.tar.gz: '08e60c60fab9867131aa075177bdd7e470e628804ab51232a1a4bc259844c6ca'
3
+ metadata.gz: e84472027fcfa0174c8c7da896340b34a3c7f15f4575df81c756e6eac38ae171
4
+ data.tar.gz: 39d3ec49fb48c092b7501f7bd158c9b63272b341632b0844f299cfae7e080882
5
5
  SHA512:
6
- metadata.gz: 7ae3eba70539c1d56e98c69e190f1eeb6e4c726dcb3c0a2f390e80c163ef6cb95010bc04908b3fd274d3481de89e271db2f71bd8d14c0d697b13deaa201636a0
7
- data.tar.gz: a32b6a3f54a1afb2b939470ed1b33cf29cf04b32c4f5955552e508a512d76a24832d6f7781e8c5b86c9d06839cbed7009bc9d3d16af76eab36b9555d7faed7f2
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,129 +1,63 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- decombobulate (0.1.3)
4
+ decombobulate (0.1.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- actionpack (7.0.7)
10
- actionview (= 7.0.7)
11
- activesupport (= 7.0.7)
12
- rack (~> 2.0, >= 2.2.4)
13
- rack-test (>= 0.6.3)
14
- rails-dom-testing (~> 2.0)
15
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
16
- actionview (7.0.7)
17
- activesupport (= 7.0.7)
18
- builder (~> 3.1)
19
- erubi (~> 1.4)
20
- rails-dom-testing (~> 2.0)
21
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
22
- activesupport (7.0.7)
9
+ activesupport (7.0.8)
23
10
  concurrent-ruby (~> 1.0, >= 1.0.2)
24
11
  i18n (>= 1.6, < 2)
25
12
  minitest (>= 5.1)
26
13
  tzinfo (~> 2.0)
27
14
  ast (2.4.2)
28
- base64 (0.1.1)
29
- builder (3.2.4)
30
15
  concurrent-ruby (1.2.2)
31
- crass (1.0.6)
32
- erubi (1.12.0)
33
16
  i18n (1.14.1)
34
17
  concurrent-ruby (~> 1.0)
35
- json (2.6.3)
18
+ json (2.7.0)
36
19
  language_server-protocol (3.17.0.3)
37
- loofah (2.21.3)
38
- crass (~> 1.0.2)
39
- nokogiri (>= 1.12.0)
40
- method_source (1.0.0)
41
- minitest (5.19.0)
42
- nokogiri (1.15.4-arm64-darwin)
43
- racc (~> 1.4)
44
- nokogiri (1.15.4-x86_64-linux)
45
- racc (~> 1.4)
20
+ minitest (5.20.0)
46
21
  parallel (1.23.0)
47
- parser (3.2.2.3)
22
+ parser (3.2.2.4)
48
23
  ast (~> 2.4.1)
49
24
  racc
50
- racc (1.7.1)
51
- rack (2.2.8)
52
- rack-test (2.1.0)
53
- rack (>= 1.3)
54
- rails-dom-testing (2.2.0)
55
- activesupport (>= 5.0.0)
56
- minitest
57
- nokogiri (>= 1.6)
58
- rails-html-sanitizer (1.6.0)
59
- loofah (~> 2.21)
60
- nokogiri (~> 1.14)
61
- railties (7.0.7)
62
- actionpack (= 7.0.7)
63
- activesupport (= 7.0.7)
64
- method_source
65
- rake (>= 12.2)
66
- thor (~> 1.0)
67
- zeitwerk (~> 2.5)
25
+ racc (1.7.3)
68
26
  rainbow (3.1.1)
69
- rake (13.0.6)
70
- regexp_parser (2.8.1)
27
+ rake (13.1.0)
28
+ regexp_parser (2.8.3)
71
29
  rexml (3.2.6)
72
- rubocop (1.56.0)
73
- base64 (~> 0.1.1)
30
+ rubocop (1.58.0)
74
31
  json (~> 2.3)
75
32
  language_server-protocol (>= 3.17.0)
76
33
  parallel (~> 1.10)
77
- parser (>= 3.2.2.3)
34
+ parser (>= 3.2.2.4)
78
35
  rainbow (>= 2.2.2, < 4.0)
79
36
  regexp_parser (>= 1.8, < 3.0)
80
37
  rexml (>= 3.2.5, < 4.0)
81
- rubocop-ast (>= 1.28.1, < 2.0)
38
+ rubocop-ast (>= 1.30.0, < 2.0)
82
39
  ruby-progressbar (~> 1.7)
83
40
  unicode-display_width (>= 2.4.0, < 3.0)
84
- rubocop-ast (1.29.0)
41
+ rubocop-ast (1.30.0)
85
42
  parser (>= 3.2.1.0)
86
- rubocop-md (1.2.0)
87
- rubocop (>= 1.0)
88
- rubocop-minitest (0.31.0)
89
- rubocop (>= 1.39, < 2.0)
90
- rubocop-packaging (0.5.2)
91
- rubocop (>= 1.33, < 2.0)
92
- rubocop-performance (1.19.0)
43
+ rubocop-performance (1.19.1)
93
44
  rubocop (>= 1.7.0, < 2.0)
94
45
  rubocop-ast (>= 0.4.0)
95
- rubocop-rails (2.20.2)
96
- activesupport (>= 4.2.0)
97
- rack (>= 1.1)
98
- rubocop (>= 1.33.0, < 2.0)
99
- rubocop-rails_config (1.13.0)
100
- railties (>= 5.0)
101
- rubocop (>= 1.48.0)
102
- rubocop-ast (>= 1.26.0)
103
- rubocop-md
104
- rubocop-minitest (~> 0.22)
105
- rubocop-packaging (~> 0.5)
106
- rubocop-performance (~> 1.11)
107
- rubocop-rails (~> 2.0)
108
46
  ruby-progressbar (1.13.0)
109
- thor (1.2.2)
110
47
  tzinfo (2.0.6)
111
48
  concurrent-ruby (~> 1.0)
112
- unicode-display_width (2.4.2)
113
- zeitwerk (2.6.11)
49
+ unicode-display_width (2.5.0)
114
50
 
115
51
  PLATFORMS
116
52
  arm64-darwin-22
117
- x86_64-linux
118
53
 
119
54
  DEPENDENCIES
55
+ activesupport (= 7.0.8)
120
56
  decombobulate!
121
- minitest (~> 5.0)
122
- rake (~> 13.0)
123
- rubocop (~> 1.21)
57
+ minitest
58
+ rake (~> 13.1)
59
+ rubocop
124
60
  rubocop-performance
125
- rubocop-rails
126
- rubocop-rails_config
127
61
 
128
62
  BUNDLED WITH
129
- 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.3"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/decombobulate.rb CHANGED
@@ -3,28 +3,42 @@
3
3
  require_relative "decombobulate/version"
4
4
  require "json"
5
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.3
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
  - - ">="
@@ -53,19 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rubocop-rails_config
56
+ name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 7.0.8
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 7.0.8
69
69
  description: Convert JSON structures to CSVs
70
70
  email:
71
71
  - cguess@gmail.com
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  - !ruby/object:Gem::Version
107
107
  version: '0'
108
108
  requirements: []
109
- rubygems_version: 3.4.10
109
+ rubygems_version: 3.4.22
110
110
  signing_key:
111
111
  specification_version: 4
112
112
  summary: Convert JSON structures to CSVs