class_from_son 0.1.5 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f413bc7bc0f742bf3ac5f2cee734a668edf7d53f0a2e10718f852fdcce4cc901
4
- data.tar.gz: 87ca04dc961b84b5fdb1779643e52d548e6c4f84899870c9f297802d498ce196
3
+ metadata.gz: 7faa342b7ee967052d435004f43ce42c65da00131023013fc42309ce6a9e8808
4
+ data.tar.gz: 356699a2d3b8f2df84da9010cc03fa33c779e3ca1bb6dbdfa11cfbb2568487aa
5
5
  SHA512:
6
- metadata.gz: aeff2562e7f122804a323348eebc4cfa6a3cadb89e3e5f4a9bb755d62e0f9890b07268fb483b71f4b245bf575300de88599903fdeb8c7ff4906bff96323fc936
7
- data.tar.gz: 941bb7fbd059e1634d5dfd6fd5628b7e913c99883610b4e7a30c13ea29bd8d2610f766bd818ce53fee931e3e19167d1b61163fab9234151bdd09cc102664e15b
6
+ metadata.gz: 2bbd9f6ca9246dc4b273a037e322bf4da6ce69258c120fc56f5ba2240b4a5047020867ed16cd86833c2a7dd4806855174acea4cabcb272e25a4527d821042e5f
7
+ data.tar.gz: 1b7967cffab7c886230b0d726260516441dfaf320d7238d50b201d2b655340d1446396ec9eb57b477b7a5122fd6c842954baf20a89b65b6934182e78f2bfdd62
data/README.md CHANGED
@@ -31,6 +31,12 @@ or
31
31
  ruby -e "require 'class_from_son'; ClassFromSON.generate :ruby, my_json_string, :json"
32
32
  ```
33
33
 
34
+ Advanced use :
35
+
36
+ ```
37
+ ruby -e "require 'class_from_son'; ClassFromSON.generate :ruby, my_json_string, :json, true, false, true, '../write/files/somewhere/else'"
38
+ ```
39
+
34
40
  Method parameter explanations :
35
41
 
36
42
  ```
@@ -42,7 +48,10 @@ Method parameter explanations :
42
48
  # source_lang is symbol or nil (if nil, source language will be determined from the file extension)
43
49
  # make_file flag defaults to true; set to false if you do not want files to be created by this method
44
50
  # force_file flag is false; set to true if you wish to overwrite matching destination files (use with caution!)
45
- def ClassFromSON.generate_from_file(dest_lang, file, source_lang, make_file = true, force_file = false)
51
+ # lenient_mode flag is true; if the SON contains different objects with the same name (e.g. "data") then these will be treated
52
+ # as different objects with a _1, _2, etc. suffix. If this flag is false and these different objects are present, then errors will occur
53
+ # custom_file_path is nil; set to an absoulte or relative path to have the new files be written to that location
54
+ def ClassFromSON.generate_from_file(dest_lang, file, source_lang = nil, make_file = true, force_file = false, lenient_mode = true, custom_file_path = nil)
46
55
  ```
47
56
 
48
57
  ```
@@ -54,5 +63,8 @@ def ClassFromSON.generate_from_file(dest_lang, file, source_lang, make_file = tr
54
63
  # source_lang is symbol
55
64
  # make_file flag defaults to true; set to false if you do not want files to be created by this method
56
65
  # force_file flag is false; set to true if you wish to overwrite matching destination files (use with caution!)
57
- def ClassFromSON.generate(dest_lang, source, source_lang, make_file = true, force_file = false)
66
+ # lenient_mode flag is true; if the SON contains different objects with the same name (e.g. "data") then these will be treated
67
+ # as different objects with a _1, _2, etc. suffix. If this flag is false and these different objects are present, then errors will occur
68
+ # custom_file_path is nil; set to an absoulte or relative path to have the new files be written to that location
69
+ def ClassFromSON.generate(dest_lang, source, source_lang, make_file = true, force_file = false, lenient_mode = true, custom_file_path = nil)
58
70
  ```
@@ -172,15 +172,16 @@ class ClassFromSON
172
172
 
173
173
  # Returns code representing the start of the class
174
174
  def generate_class_start(name)
175
+ # TODO make this more readable
175
176
  case @language
176
- when :java, :java_lombok
177
+ when :java
177
178
  start = <<-START
178
179
 
179
180
  import com.fasterxml.jackson.annotation.JsonProperty;
180
181
 
181
182
  public class #{convert_custom_class_type(name)} {
182
183
  START
183
- when :java_lombok # TODO
184
+ when :java_lombok
184
185
  start = <<-START
185
186
 
186
187
  import com.fasterxml.jackson.annotation.JsonProperty;
@@ -199,7 +200,16 @@ import lombok.Setter;
199
200
  public class #{convert_custom_class_type(name)} {
200
201
  START
201
202
  when :ruby
202
- start = "class #{convert_custom_class_type(name)}"
203
+ case @mode
204
+ when :json
205
+ start = <<-START
206
+ require 'json'
207
+
208
+ class #{convert_custom_class_type(name)}
209
+ START
210
+ else
211
+ error_and_exit "Cannot parse mode #{@mode}"
212
+ end
203
213
  else
204
214
  error_and_exit "Could not convert to output language #{@language}"
205
215
  end
@@ -225,13 +235,17 @@ START
225
235
  when :java_lombok
226
236
  # do nothing - Lombok's raison d'etre is to avoid getters & setters
227
237
  when :java
228
- class_name = convert_custom_class_type(name)
238
+ # This is safe even if the name is already in snakecase
239
+ field_name_for_getter = name.snakecase.pascalcase
240
+
241
+ name = name.camelcase if name.include? "_"
242
+
229
243
  lines << "\t"
230
- lines << "\tpublic #{type} get#{class_name}() {"
244
+ lines << "\tpublic #{type} get#{field_name_for_getter}() {"
231
245
  lines << "\t\treturn #{name};"
232
246
  lines << "\t}"
233
247
  lines << "\t"
234
- lines << "\tpublic void set#{class_name}(#{type} #{name}) {"
248
+ lines << "\tpublic void set#{field_name_for_getter}(#{type} #{name}) {"
235
249
  lines << "\t\tthis.#{name} = #{name};"
236
250
  lines << "\t}"
237
251
  else
@@ -262,6 +276,7 @@ START
262
276
  camelcase_name = att[:name].camelcase
263
277
  code << "\t@JsonProperty(\"#{snakecase_name}\")"
264
278
  code << "\tprivate #{convert_ruby_type_to_type(att[:type], att[:value_types])} #{camelcase_name};"
279
+ code << "" # add a new line so that fields are separated & easier to read
265
280
  else
266
281
  code << "\tprivate #{convert_ruby_type_to_type(att[:type], att[:value_types])} #{att[:name]};"
267
282
  end
@@ -280,21 +295,83 @@ START
280
295
  def generate_ruby_code_from_attributes(attributes)
281
296
  code = []
282
297
  names = []
283
- attributes.each {|att| names << att[:name]}
298
+ attributes.each {|att| names << att[:name].snakecase}
284
299
 
285
300
  # Instance variables
286
301
  names.each do |name|
287
- code << "\tattr_accessor #{name.to_sym.inspect}"
302
+ code << " attr_accessor #{name.to_sym.inspect}"
288
303
  end
289
304
  code << "" # An empty string is enough to trigger a newline
290
305
 
291
306
  # Constructor
292
- code << "\tdef initialize(#{names.join(", ")})"
307
+ # This is deliberately commented out, in favour of self.from_hash
308
+ code << " # Using self.from_hash(hash) is usually better, but this code is here in case you prefer this style of constructor"
309
+ code << " # def initialize(#{names.join(", ")})"
293
310
  names.each do |name|
294
- code << "\t\t@#{name} = #{name}"
311
+ code << " # @#{name} = #{name}"
312
+ end
313
+ code << " # end"
314
+ code
315
+ end
316
+
317
+ # Returns code for a from_SON and to_SON method
318
+ def generate_from_and_to_methods(classname, attributes)
319
+ case @language
320
+ when :java, :java_lombok
321
+ return generate_java_from_and_to_methods(classname, attributes)
322
+ when :ruby
323
+ return generate_ruby_from_and_to_methods(classname, attributes)
324
+ else
325
+ error_and_exit "Could not convert to output language #{@language}"
295
326
  end
296
- code << "\tend"
327
+ end
297
328
 
329
+ # Returns Java code for a from_SON and to_SON method
330
+ def generate_java_from_and_to_methods(classname, attributes)
331
+ code = []
332
+ # TODO
333
+ code
334
+ end
335
+
336
+ # Returns Ruby code for a from_SON and to_SON method
337
+ def generate_ruby_from_and_to_methods(classname, attributes)
338
+ code = []
339
+ names = []
340
+ attributes.each {|att| names << att[:name].snakecase}
341
+
342
+ # from_hash method
343
+ code << ""
344
+ code << " def self.from_hash(h)"
345
+ code << " o = self.new"
346
+ names.each do |name|
347
+ code << " o.#{name} = h[:#{name}]"
348
+ end
349
+ code << " o"
350
+ code << " end"
351
+
352
+ # from_SON method
353
+ code << ""
354
+ code << " def self.from_#{@mode}(#{@mode})"
355
+ case @mode
356
+ when :json
357
+ code << " self.from_hash(JSON.parse(#{@mode}))"
358
+ # TODO other input languages, e.g. XML, YAML
359
+ end
360
+ code << " end"
361
+
362
+ # to_SON method
363
+ code << ""
364
+ code << " def to_#{@mode}"
365
+ code << " h = {}"
366
+ names.each do |name|
367
+ code << " h[:#{name}] = @#{name}"
368
+ end
369
+ case @mode
370
+ when :json
371
+ code << " JSON.generate(h)"
372
+ # TODO other input languages, e.g. XML, YAML
373
+ end
374
+ code << " end"
298
375
  code
299
376
  end
300
377
 
@@ -332,6 +409,7 @@ START
332
409
  end
333
410
 
334
411
  lines << generate_code_from_attributes(attributes)
412
+ lines << generate_from_and_to_methods(classname, attributes)
335
413
  lines << generate_class_end
336
414
  lines.flatten!
337
415
  this_file[:contents] = lines.join("\n")
@@ -351,13 +429,16 @@ START
351
429
  # source_lang is symbol or nil (if nil, source language will be determined from the file extension)
352
430
  # make_file flag defaults to true; set to false if you do not want files to be created by this method
353
431
  # force_file flag is false; set to true if you wish to overwrite matching destination files (use with caution!)
354
- def ClassFromSON.generate_from_file(dest_lang, file, source_lang = nil, make_file = true, force_file = false)
432
+ # lenient_mode flag is true; if the SON contains different objects with the same name (e.g. "data") then these will be treated
433
+ # as different objects with a _1, _2, etc. suffix. If this flag is false and these different objects are present, then errors will occur
434
+ # custom_file_path is nil; set to an absoulte or relative path to have the new files be written to that location
435
+ def ClassFromSON.generate_from_file(dest_lang, file, source_lang = nil, make_file = true, force_file = false, lenient_mode = true, custom_file_path = nil)
355
436
 
356
437
  error_and_exit "Could not locate file #{file}" unless File.exists?(file)
357
438
 
358
439
  source_lang ||= File.extname(file).gsub(".", "")
359
440
  source = File.readlines(file).join
360
- ClassFromSON.generate(dest_lang, source, source_lang, make_file, force_file)
441
+ ClassFromSON.generate(dest_lang, source, source_lang, make_file, force_file, lenient_mode, custom_file_path)
361
442
 
362
443
  # o = ClassFromSON.new
363
444
  # o.generate(dest_lang, source, source_lang, make_file)
@@ -371,9 +452,12 @@ START
371
452
  # source_lang is symbol
372
453
  # make_file flag defaults to true; set to false if you do not want files to be created by this method
373
454
  # force_file flag is false; set to true if you wish to overwrite matching destination files (use with caution!)
374
- def ClassFromSON.generate(dest_lang, source, source_lang, make_file = true, force_file = false)
455
+ # lenient_mode flag is true; if the SON contains different objects with the same name (e.g. "data") then these will be treated
456
+ # as different objects with a _1, _2, etc. suffix. If this flag is false and these different objects are present, then errors will occur
457
+ # custom_file_path is nil; set to an absoulte or relative path to have the new files be written to that location
458
+ def ClassFromSON.generate(dest_lang, source, source_lang, make_file = true, force_file = false, lenient_mode = true, custom_file_path = nil)
375
459
  o = ClassFromSON.new
376
- o.generate(dest_lang, source, source_lang, make_file, force_file)
460
+ o.generate(dest_lang, source, source_lang, make_file, force_file, lenient_mode, custom_file_path)
377
461
  end
378
462
 
379
463
  # Will generate classes from a SON string.
@@ -384,7 +468,10 @@ START
384
468
  # source_lang is symbol
385
469
  # make_file flag defaults to true; set to false if you do not want files to be created by this method
386
470
  # force_file flag is false; set to true if you wish to overwrite matching destination files (use with caution!)
387
- def generate(dest_lang, source, source_lang, make_file = true, force_file = false)
471
+ # lenient_mode flag is true; if the SON contains different objects with the same name (e.g. "data") then these will be treated
472
+ # as different objects with a _1, _2, etc. suffix. If this flag is false and these different objects are present, then errors will occur
473
+ # custom_file_path is nil; set to an absoulte or relative path to have the new files be written to that location
474
+ def generate(dest_lang, source, source_lang, make_file = true, force_file = false, lenient_mode = true, custom_file_path = nil)
388
475
 
389
476
  error_and_exit "Please supply first argument as a Symbol" unless dest_lang.class == Symbol
390
477
 
@@ -429,16 +516,46 @@ START
429
516
  top_level_classname = generate_top_level_name
430
517
  output_classes = generate_output_classes(hash, top_level_classname).flatten # returns an array
431
518
 
519
+ # Set the directory that the files will be written into
520
+ if custom_file_path
521
+ # This caters for both absolute & relative file paths
522
+ file_path = File.absolute_path(custom_file_path)
523
+ else
524
+ file_path = Dir.getwd
525
+ end
526
+
527
+ # Track the names of the classes/files we have written so far
528
+ written_file_names = []
529
+
432
530
  if make_file
433
531
  output_classes.each do |out|
434
532
  name = out[:name_with_ext]
533
+ # Check the name against the files we have already written
534
+ if written_file_names.include?(name)
535
+ if lenient_mode
536
+ # Let us increment the name, e.g. "data.rb" -> "data_1.rb", "data_2.rb", etc.
537
+ increment = 1
538
+ new_name = name.gsub(@extension, "_#{increment}#{@extension}")
539
+ while written_file_names.include?(new_name)
540
+ increment += 1
541
+ new_name = name.gsub(@extension, "_#{increment}#{@extension}")
542
+ end
543
+ name = new_name
544
+ else
545
+ message = "Want to generate output file #{name}, but a file with that name has already been written by this process. Your SON structure contains 2+ different classes with the same name"
546
+ error_and_exit(message)
547
+ end
548
+ end
549
+
550
+ filename = file_path + File::SEPARATOR + name
435
551
  contents = out[:contents]
436
552
  unless force_file
437
553
  error_and_exit "Want to generate output file #{name}, but that file already exists" if File.exists?(name)
438
554
  end
439
- File.open(name, "w+") do |f|
555
+ File.open(filename, "w+") do |f|
440
556
  f.puts contents
441
557
  end
558
+ written_file_names << name
442
559
  puts "Wrote out file #{name}"
443
560
  end
444
561
 
@@ -19,7 +19,7 @@ class TestEx < Test::Unit::TestCase
19
19
 
20
20
  address_filename = "address.rb"
21
21
  top_level_filename = "generated_from_json.rb"
22
- phonenumbers_filename = "phonenumbers.rb"
22
+ phonenumbers_filename = "phone_numbers.rb"
23
23
 
24
24
  expected_address = File.readlines(address_filename).join.chomp # trim off any trailing whitespace (irrelevent for this test)
25
25
  expected_example = File.readlines(top_level_filename).join.chomp # trim off any trailing whitespace (irrelevent for this test)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: class_from_son
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Morrisby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-20 00:00:00.000000000 Z
11
+ date: 2021-07-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This gem will attempt to generate code of a class of an object representing
14
14
  the contents of a Serialised-Object-Notation (SON) string (or file). E.g. it will
@@ -22,7 +22,7 @@ files:
22
22
  - README.md
23
23
  - lib/class_from_SON.rb
24
24
  - test/test_class_from_son.rb
25
- homepage: https://rubygems.org/gems/class_from_son
25
+ homepage: https://github.com/RMorrisby/class_from_son
26
26
  licenses:
27
27
  - MIT
28
28
  metadata: {}