shale 0.2.2 → 0.4.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +366 -8
  4. data/exe/shaleb +123 -0
  5. data/lib/shale/adapter/json.rb +7 -2
  6. data/lib/shale/adapter/nokogiri.rb +48 -12
  7. data/lib/shale/adapter/ox.rb +28 -4
  8. data/lib/shale/adapter/rexml.rb +56 -13
  9. data/lib/shale/attribute.rb +7 -1
  10. data/lib/shale/error.rb +12 -0
  11. data/lib/shale/mapper.rb +17 -15
  12. data/lib/shale/mapping/descriptor/dict.rb +57 -0
  13. data/lib/shale/mapping/descriptor/xml.rb +43 -0
  14. data/lib/shale/mapping/descriptor/xml_namespace.rb +37 -0
  15. data/lib/shale/mapping/{key_value.rb → dict.rb} +8 -6
  16. data/lib/shale/mapping/validator.rb +51 -0
  17. data/lib/shale/mapping/xml.rb +86 -15
  18. data/lib/shale/schema/json_compiler/boolean.rb +21 -0
  19. data/lib/shale/schema/json_compiler/date.rb +21 -0
  20. data/lib/shale/schema/json_compiler/float.rb +21 -0
  21. data/lib/shale/schema/json_compiler/integer.rb +21 -0
  22. data/lib/shale/schema/json_compiler/object.rb +85 -0
  23. data/lib/shale/schema/json_compiler/property.rb +70 -0
  24. data/lib/shale/schema/json_compiler/string.rb +21 -0
  25. data/lib/shale/schema/json_compiler/time.rb +21 -0
  26. data/lib/shale/schema/json_compiler/utils.rb +52 -0
  27. data/lib/shale/schema/json_compiler/value.rb +13 -0
  28. data/lib/shale/schema/json_compiler.rb +333 -0
  29. data/lib/shale/schema/json_generator/base.rb +41 -0
  30. data/lib/shale/schema/json_generator/boolean.rb +23 -0
  31. data/lib/shale/schema/json_generator/collection.rb +39 -0
  32. data/lib/shale/schema/json_generator/date.rb +23 -0
  33. data/lib/shale/schema/json_generator/float.rb +23 -0
  34. data/lib/shale/schema/json_generator/integer.rb +23 -0
  35. data/lib/shale/schema/json_generator/object.rb +40 -0
  36. data/lib/shale/schema/json_generator/ref.rb +28 -0
  37. data/lib/shale/schema/json_generator/schema.rb +59 -0
  38. data/lib/shale/schema/json_generator/string.rb +23 -0
  39. data/lib/shale/schema/json_generator/time.rb +23 -0
  40. data/lib/shale/schema/json_generator/value.rb +23 -0
  41. data/lib/shale/schema/json_generator.rb +165 -0
  42. data/lib/shale/schema/xml_generator/attribute.rb +41 -0
  43. data/lib/shale/schema/xml_generator/complex_type.rb +70 -0
  44. data/lib/shale/schema/xml_generator/element.rb +55 -0
  45. data/lib/shale/schema/xml_generator/import.rb +46 -0
  46. data/lib/shale/schema/xml_generator/ref_attribute.rb +37 -0
  47. data/lib/shale/schema/xml_generator/ref_element.rb +39 -0
  48. data/lib/shale/schema/xml_generator/schema.rb +121 -0
  49. data/lib/shale/schema/xml_generator/typed_attribute.rb +46 -0
  50. data/lib/shale/schema/xml_generator/typed_element.rb +46 -0
  51. data/lib/shale/schema/xml_generator.rb +315 -0
  52. data/lib/shale/schema.rb +70 -0
  53. data/lib/shale/type/boolean.rb +2 -2
  54. data/lib/shale/type/composite.rb +78 -72
  55. data/lib/shale/type/date.rb +35 -2
  56. data/lib/shale/type/float.rb +2 -2
  57. data/lib/shale/type/integer.rb +2 -2
  58. data/lib/shale/type/string.rb +2 -2
  59. data/lib/shale/type/time.rb +35 -2
  60. data/lib/shale/type/{base.rb → value.rb} +18 -7
  61. data/lib/shale/utils.rb +18 -2
  62. data/lib/shale/version.rb +1 -1
  63. data/lib/shale.rb +10 -10
  64. data/shale.gemspec +6 -2
  65. metadata +53 -13
  66. data/lib/shale/mapping/base.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5907ba25342167fa8079e683c671f83f89b51777d0b27e8d0126ba404304c2bd
4
- data.tar.gz: d88f5acb763a1aaa5f3ceff194613df16377107035cf2c5528bbda593a70b6fa
3
+ metadata.gz: 26877841702bfce542b42206f519ffd1b03e2f0d3db1e70a42e37836ff0f3bff
4
+ data.tar.gz: 23fddba7256bc38b5d64309cf56833f47fd636ed0e9f6ec61f2a076764dad2bb
5
5
  SHA512:
6
- metadata.gz: 967646fcc21c1735acc67ad67b467013dae4da76c2e7cfc3a863b8bc811ae3fcee73cc3a99e1344ab0ea38ef03fed181ba14286666afc6e965399c8929de045a
7
- data.tar.gz: bebfbda2bb8fab102ae8b70506cd3fad3e1b178c02ed0486576d2fb26ef5a98ac45a0f62d410888e95b40d9a21ace32c76da36e2a75151ce104c4764a98b1b68
6
+ metadata.gz: e98bd0bbb20fbbaf4a8bdbfb5e2f83ba5d7bae78ad66a8597a1fe28c0c13d9799bc3ade785105ea7261147f55590ff530e85c31da459fe8c9171ee5b68f02977
7
+ data.tar.gz: 54be0efe36f4d330c551f1bd8994a36b360ecec3696aafe12382408a0656e50a181b91ff54ec9f39758908d15fcf51eafc4810540cd1aeb00bda7b552382bc80
data/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## [0.4.0] - 2022-05-30
2
+
3
+ ### Added
4
+ - Allow to add title to JSON Schema
5
+ - Map Shale::Type::Value to "anyType" XML Schema type
6
+ - Map Shale::Type::Value to "any" JSON Schema type
7
+ - Allow to generate Shale model from JSON Schema
8
+
9
+ ### Changed
10
+ - Performance improvements
11
+ - Reformat README a little bit and fix typos
12
+
13
+ ### Fixed
14
+ - Fix stack overflow caused by circular dependency when generating JSON and XML schemas
15
+
16
+ ## [0.3.1] - 2022-04-29
17
+
18
+ ### Changed
19
+ - Rename `id` -> `$id` and add info about supported JSON Schema dialect
20
+
21
+ ## [0.3.0] - 2022-04-29
22
+
23
+ ### Added
24
+ - Support for XML namespaces
25
+ - Add option to pretty print JSON and XML and to include XML declaration
26
+ - Add support for generating JSON and XML Schema
27
+
28
+ ### Changed
29
+ - Various fixes to documentation
30
+ - Rename `hash` -> `hsh` (`hash` is used internally by Ruby)
31
+ - Rename `Shale::Type::Base` -> `Shale::Type::Value`
32
+ - Use ISO 8601 format for date and time in JSON, YAML and XML
33
+
1
34
  ## [0.2.2] - 2022-03-06
2
35
 
3
36
  ### Fixed
data/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # Shale
2
2
 
3
- Shale is a object mapper and serializer for JSON, YAML and XML.
3
+ Shale is a Ruby object mapper and serializer for JSON, YAML and XML.
4
+ It allows you to parse JSON, YAML and XML data and convert it into Ruby data structures,
5
+ as well as serialize data structures into JSON, YAML or XML.
6
+
7
+ ## Features
8
+
9
+ * Convert JSON, YAML and XML to Ruby data model
10
+ * Convert Ruby data model to JSON, YAML and XML
11
+ * Generate JSON and XML Schema from Ruby models
12
+ * Compile JSON Schema into Ruby models
13
+ * Out of the box support for JSON, YAML, Nokogiri, REXML and Ox parsers
14
+ * Support for custom adapters
4
15
 
5
16
  ## Installation
6
17
 
@@ -24,8 +35,36 @@ Or install it yourself as:
24
35
  $ gem install shale
25
36
  ```
26
37
 
38
+ ## Contents
39
+
40
+ * [Simple use case](#user-content-simple-use-case)
41
+ * [Creating objects](#creating-objects)
42
+ * [Converting JSON to object](#converting-json-to-object)
43
+ * [Converting object to JSON](#converting-object-to-json)
44
+ * [Converting YAML to object](#converting-yaml-to-object)
45
+ * [Converting object to YAML](#converting-object-to-yaml)
46
+ * [Converting Hash to object](#converting-hash-to-object)
47
+ * [Converting object to Hash](#converting-object-to-hash)
48
+ * [Converting XML to object](#converting-xml-to-object)
49
+ * [Converting object to XML](#converting-object-to-xml)
50
+ * [Mapping JSON keys to object attributes](#mapping-json-keys-to-object-attributes)
51
+ * [Mapping YAML keys to object attributes](#mapping-yaml-keys-to-object-attributes)
52
+ * [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
53
+ * [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
54
+ * [Using XML namespaces](#using-xml-namespaces)
55
+ * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
56
+ * [Pretty printing and XML declaration](#pretty-printing-and-xml-declaration)
57
+ * [Supported types](#supported-types)
58
+ * [Writing your own type](#writing-your-own-type)
59
+ * [Adapters](#adapters)
60
+ * [Generating JSON Schema](#generating-json-schema)
61
+ * [Compiling JSON Schema into Shale model](#compiling-json-schema-into-shale-model)
62
+ * [Generating XML Schema](#generating-xml-schema)
63
+
27
64
  ## Usage
28
65
 
66
+ Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
67
+
29
68
  ### Simple use case
30
69
 
31
70
  ```ruby
@@ -99,6 +138,7 @@ DATA
99
138
 
100
139
  ```ruby
101
140
  person.to_json
141
+
102
142
  # =>
103
143
  #
104
144
  # {
@@ -137,6 +177,7 @@ DATA
137
177
 
138
178
  ```ruby
139
179
  person.to_yaml
180
+
140
181
  # =>
141
182
  #
142
183
  # ---
@@ -174,6 +215,7 @@ person = Person.from_hash(
174
215
 
175
216
  ```ruby
176
217
  person.to_hash
218
+
177
219
  # =>
178
220
  #
179
221
  # {
@@ -210,6 +252,7 @@ DATA
210
252
 
211
253
  ```ruby
212
254
  person.to_xml
255
+
213
256
  # =>
214
257
  #
215
258
  # <person>
@@ -229,7 +272,7 @@ person.to_xml
229
272
 
230
273
  ### Mapping JSON keys to object attributes
231
274
 
232
- By default keys are named the same as attributes. To use custom key names use:
275
+ By default keys are named the same as attributes. To use custom keys use:
233
276
 
234
277
  ```ruby
235
278
  class Person < Shale::Mapper
@@ -264,7 +307,7 @@ class Person < Shale::Mapper
264
307
  attribute :first_name, Shale::Type::String
265
308
  attribute :last_name, Shale::Type::String
266
309
 
267
- hash do
310
+ hsh do
268
311
  map 'firstName', to: :first_name
269
312
  map 'lastName', to: :last_name
270
313
  end
@@ -273,7 +316,7 @@ end
273
316
 
274
317
  ### Mapping XML elements and attributes to object attributes
275
318
 
276
- XML is more complcated format than JSON or YAML. To map elements, attributes and content use:
319
+ XML is more complicated format than JSON or YAML. To map elements, attributes and content use:
277
320
 
278
321
  ```ruby
279
322
  class Address < Shale::Mapper
@@ -327,6 +370,72 @@ DATA
327
370
  - `map_attribute` - map element's attribute to attribute
328
371
  - `map_content` - map first text node to attribute
329
372
 
373
+ ### Using XML namespaces
374
+
375
+ To map namespaced elements and attributes use `namespace` and `prefix` properties on
376
+ `map_element` and `map_attribute`
377
+
378
+ ```ruby
379
+ class Person < Shale::Mapper
380
+ attribute :first_name, Shale::Type::String
381
+ attribute :last_name, Shale::Type::String
382
+ attribute :age, Shale::Type::Integer
383
+
384
+ xml do
385
+ root 'person'
386
+
387
+ map_element 'first_name', to: :first_name, namespace: 'http://ns1.com', prefix: 'ns1'
388
+ map_element 'last_name', to: :last_name, namespace: 'http://ns2.com', prefix: 'ns2'
389
+ map_attribute 'age', to: :age, namespace: 'http://ns2.com', prefix: 'ns2'
390
+ end
391
+ end
392
+
393
+ person = Person.from_xml(<<~DATA)
394
+ <person xmlns:ns1="http://ns1.com" xmlns:ns2="http://ns2.com" ns2:age="50">
395
+ <ns1:first_name>John</ns1:first_name>
396
+ <ns2:last_name>Doe</ns2:last_name>
397
+ </person>
398
+ DATA
399
+ ```
400
+
401
+ To define default namespace for all elements use `namespace` declaration
402
+ (this will define namespace on elements only, if you want to define namespace on an attribute
403
+ explicitly declare it on `map_attribute`).
404
+
405
+ ```ruby
406
+ class Person < Shale::Mapper
407
+ attribute :first_name, Shale::Type::String
408
+ attribute :middle_name, Shale::Type::String
409
+ attribute :last_name, Shale::Type::String
410
+ attribute :age, Shale::Type::Integer
411
+ attribute :hobby, Shale::Type::String
412
+
413
+ xml do
414
+ root 'person'
415
+ namespace 'http://ns1.com', 'ns1'
416
+
417
+ map_element 'first_name', to: :first_name
418
+
419
+ # undeclare namespace on 'middle_name' element
420
+ map_element 'middle_name', to: :middle_name, namespace: nil, prefix: nil
421
+
422
+ # overwrite default namespace
423
+ map_element 'last_name', to: :last_name, namespace: 'http://ns2.com', prefix: 'ns2'
424
+
425
+ map_attribute 'age', to: :age
426
+ map_attribute 'hobby', to: :hobby, namespace: 'http://ns1.com', prefix: 'ns1'
427
+ end
428
+ end
429
+
430
+ person = Person.from_xml(<<~DATA)
431
+ <ns1:person xmlns:ns1="http://ns1.com" xmlns:ns2="http://ns2.com" age="50" ns1:hobby="running">
432
+ <ns1:first_name>John</ns1:first_name>
433
+ <middle_name>Joe</middle_name>
434
+ <ns2:last_name>Doe</ns2:last_name>
435
+ </ns1:person>
436
+ DATA
437
+ ```
438
+
330
439
  ### Using methods to extract and generate data
331
440
 
332
441
  If you need full controll over extracting and generating data from/to document,
@@ -421,6 +530,36 @@ DATA
421
530
  # @city="London">
422
531
  ```
423
532
 
533
+ ### Pretty printing and XML declaration
534
+
535
+ If you need formatted output you can pass `:pretty` parameter to `#to_json` and `#to_xml`
536
+
537
+ ```ruby
538
+ person.to_json(:pretty)
539
+
540
+ # =>
541
+ #
542
+ # {
543
+ # "name": "John Doe",
544
+ # "address": {
545
+ # "city": "London"
546
+ # }
547
+ # }
548
+ ```
549
+
550
+ You can also add an XML declaration by passing `:declaration` to `#to_xml`
551
+
552
+ ```ruby
553
+ person.to_xml(:pretty, :declaration)
554
+
555
+ # =>
556
+ #
557
+ # <?xml version="1.0"?>
558
+ # <Person>
559
+ # <Address city="London"/>
560
+ # </Person>
561
+ ```
562
+
424
563
  ### Supported types
425
564
 
426
565
  Shale supports these types out of the box:
@@ -434,12 +573,12 @@ Shale supports these types out of the box:
434
573
 
435
574
  ### Writing your own type
436
575
 
437
- To add your own type extend it from `Shale::Type::Base` and implement `.cast` class method.
576
+ To add your own type extend it from `Shale::Type::Value` and implement `.cast` class method.
438
577
 
439
578
  ```ruby
440
- require 'shale/type/base'
579
+ require 'shale/type/value'
441
580
 
442
- class MyIntegerType < Shale::Type::Base
581
+ class MyIntegerType < Shale::Type::Value
443
582
  def self.cast(value)
444
583
  value.to_i
445
584
  end
@@ -463,7 +602,9 @@ Shale.json_adapter = MultiJson
463
602
  Shale.yaml_adapter = MyYamlAdapter
464
603
  ```
465
604
 
466
- For XML, Shale provides adapters for most popular Ruby XML parsers:
605
+ Shale provides adapters for most popular Ruby XML parsers:
606
+
607
+ :warning: **Ox doesn't support XML namespaces**
467
608
 
468
609
  ```ruby
469
610
  require 'shale'
@@ -484,6 +625,223 @@ require 'shale/adapter/ox'
484
625
  Shale.xml_adapter = Shale::Adapter::Ox
485
626
  ```
486
627
 
628
+ ### Generating JSON Schema
629
+
630
+ :warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
631
+
632
+ To generate JSON Schema from your Shale data model use:
633
+
634
+ ```ruby
635
+ require 'shale/schema'
636
+
637
+ Shale::Schema.to_json(
638
+ Person,
639
+ id: 'http://foo.bar/schema/person',
640
+ description: 'My description',
641
+ pretty: true
642
+ )
643
+
644
+ # =>
645
+ #
646
+ # {
647
+ # "$schema": "https://json-schema.org/draft/2020-12/schema",
648
+ # "$id": "http://foo.bar/schema/person",
649
+ # "description": "My description",
650
+ # "$ref": "#/$defs/Person",
651
+ # "$defs": {
652
+ # "Address": {
653
+ # "type": [
654
+ # "object",
655
+ # "null"
656
+ # ],
657
+ # "properties": {
658
+ # "city": {
659
+ # "type": [
660
+ # "string",
661
+ # "null"
662
+ # ]
663
+ # }
664
+ # }
665
+ # },
666
+ # "Person": {
667
+ # "type": "object",
668
+ # "properties": {
669
+ # "name": {
670
+ # "type": [
671
+ # "string",
672
+ # "null"
673
+ # ]
674
+ # },
675
+ # "address": {
676
+ # "$ref": "#/$defs/Address"
677
+ # }
678
+ # }
679
+ # }
680
+ # }
681
+ # }
682
+ ```
683
+
684
+ You can also use a command line tool to do it:
685
+
686
+ ```
687
+ $ shaleb -i data_model.rb -r Person -p
688
+ ```
689
+
690
+ If you want to convert your own types to JSON Schema types use:
691
+
692
+ ```ruby
693
+ require 'shale'
694
+ require 'shale/schema'
695
+
696
+ class MyEmailType < Shale::Type::Value
697
+ ...
698
+ end
699
+
700
+ class MyEmailJSONType < Shale::Schema::JSONGenerator::Base
701
+ def as_type
702
+ { 'type' => 'string', 'format' => 'email' }
703
+ end
704
+ end
705
+
706
+ Shale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)
707
+ ```
708
+
709
+ ### Compiling JSON Schema into Shale model
710
+
711
+ :warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
712
+
713
+ To generate Shale data model from JSON Schema use:
714
+
715
+ ```ruby
716
+ require 'shale/schema'
717
+
718
+ schema = <<~SCHEMA
719
+ {
720
+ "type": "object",
721
+ "properties": {
722
+ "firstName": { "type": "string" },
723
+ "lastName": { "type": "string" },
724
+ "address": {
725
+ "type": "object",
726
+ "properties": {
727
+ "street": { "type": "string" },
728
+ "city": { "type": "string" }
729
+ }
730
+ }
731
+ }
732
+ }
733
+ SCHEMA
734
+
735
+ Shale::Schema.from_json([schema], root_name: 'Person')
736
+
737
+ # =>
738
+ #
739
+ # {
740
+ # "address" => "
741
+ # require 'shale'
742
+ #
743
+ # class Address < Shale::Mapper
744
+ # attribute :street, Shale::Type::String
745
+ # attribute :city, Shale::Type::String
746
+ #
747
+ # json do
748
+ # map 'street', to: :street
749
+ # map 'city', to: :city
750
+ # end
751
+ # end
752
+ # ",
753
+ # "person" => "
754
+ # require 'shale'
755
+ #
756
+ # require_relative 'address'
757
+ #
758
+ # class Person < Shale::Mapper
759
+ # attribute :first_name, Shale::Type::String
760
+ # attribute :last_name, Shale::Type::String
761
+ # attribute :address, Address
762
+ #
763
+ # json do
764
+ # map 'firstName', to: :first_name
765
+ # map 'lastName', to: :last_name
766
+ # map 'address', to: :address
767
+ # end
768
+ # end
769
+ # "
770
+ # }
771
+ ```
772
+
773
+ You can also use a command line tool to do it:
774
+
775
+ ```
776
+ $ shaleb -c -i schema.json -r Person
777
+ ```
778
+
779
+ ### Generating XML Schema
780
+
781
+ To generate XML Schema from your Shale data model use:
782
+
783
+ ```ruby
784
+ require 'shale/schema'
785
+
786
+ Shale::Schema.to_xml(Person, pretty: true)
787
+
788
+ # =>
789
+ #
790
+ # {
791
+ # 'schema0.xsd' => '
792
+ # <xs:schema
793
+ # elementFormDefault="qualified"
794
+ # attributeFormDefault="qualified"
795
+ # xmlns:xs="http://www.w3.org/2001/XMLSchema"
796
+ # xmlns:foo="http://foo.com"
797
+ # >
798
+ # <xs:import namespace="http://foo.com" schemaLocation="schema1.xsd"/>
799
+ # <xs:element name="person" type="Person"/>
800
+ # <xs:complexType name="Person">
801
+ # <xs:sequence>
802
+ # <xs:element name="name" type="xs:string" minOccurs="0"/>
803
+ # <xs:element ref="foo:address" minOccurs="0"/>
804
+ # </xs:sequence>
805
+ # </xs:complexType>
806
+ # </xs:schema>',
807
+ #
808
+ # 'schema1.xsd' => '
809
+ # <xs:schema
810
+ # elementFormDefault="qualified"
811
+ # attributeFormDefault="qualified"
812
+ # targetNamespace="http://foo.com"
813
+ # xmlns:xs="http://www.w3.org/2001/XMLSchema"
814
+ # xmlns:foo="http://foo.com"
815
+ # >
816
+ # <xs:element name="address" type="foo:Address"/>
817
+ # <xs:complexType name="Address">
818
+ # <xs:sequence>
819
+ # <xs:element name="city" type="xs:string" minOccurs="0"/>
820
+ # </xs:sequence>
821
+ # </xs:complexType>
822
+ # </xs:schema>'
823
+ # }
824
+ ```
825
+
826
+ You can also use a command line tool to do it:
827
+
828
+ ```
829
+ $ shaleb -i data_model.rb -r Person -p -f xml
830
+ ```
831
+
832
+ If you want to convert your own types to XML Schema types use:
833
+
834
+ ```ruby
835
+ require 'shale'
836
+ require 'shale/schema'
837
+
838
+ class MyEmailType < Shale::Type::Value
839
+ ...
840
+ end
841
+
842
+ Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
843
+ ```
844
+
487
845
  ## Contributing
488
846
 
489
847
  Bug reports and pull requests are welcome on GitHub at https://github.com/kgiszczak/shale.
data/exe/shaleb ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+ require 'optparse'
6
+
7
+ base_path = File.expand_path('../lib', __dir__)
8
+
9
+ if File.exist?(base_path)
10
+ require_relative '../lib/shale/schema'
11
+ else
12
+ require 'shale/schema'
13
+ end
14
+
15
+ params = {}
16
+
17
+ ARGV << '-h' if ARGV.empty?
18
+
19
+ OptionParser.new do |opts|
20
+ opts.banner = <<~BANNER
21
+ Usage: shaleb [options]
22
+ example generate schema from Shale model: shaleb -g -i data_model.rb -c MyRoot
23
+ example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -c MyRoot
24
+ BANNER
25
+
26
+ opts.on('-g', '--generate', 'generate schema from Shale model')
27
+ opts.on('-c', '--compile', 'compile schema into Shale model')
28
+ opts.on('-i INPUT', '--input', Array, 'Input file')
29
+ opts.on('-o OUTPUT', '--output', 'Output (defaults to STDOUT)')
30
+ opts.on('-r ROOT', '--root ROOT', 'Shale model class name')
31
+ opts.on('-f FORMAT', '--format FORMAT', 'Schema format: JSON (default), XML')
32
+ opts.on('-p', '--pretty', 'Pretty print generated schema')
33
+
34
+ opts.on('-v', '--version', 'Show version') do
35
+ puts "shaleb version #{Shale::VERSION}"
36
+ exit
37
+ end
38
+ end.parse!(into: params)
39
+
40
+ if params[:compile]
41
+ unless params[:input]
42
+ puts 'Input file is required: shaleb -c -i schema1.json,schema2.json'
43
+ exit
44
+ end
45
+
46
+ schemas = params[:input].map do |file|
47
+ path = File.expand_path(file, Dir.pwd)
48
+
49
+ if File.exist?(path)
50
+ File.read(path)
51
+ else
52
+ puts "File '#{path}' does not exist"
53
+ exit
54
+ end
55
+ end
56
+
57
+ models = Shale::Schema.from_json(schemas, root_name: params[:root])
58
+
59
+ if params[:output]
60
+ dir = File.expand_path(params[:output], Dir.pwd)
61
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
62
+
63
+ models.each do |name, model|
64
+ output_path = File.join(dir, "#{name}.rb")
65
+ File.write(output_path, model)
66
+ end
67
+ else
68
+ output = models.map do |name, model|
69
+ "# --- #{name}.rb ---\n#{model}\n"
70
+ end.join("\n")
71
+
72
+ puts output
73
+ end
74
+ else
75
+ unless params[:input]
76
+ puts 'Input file is required: shaleb -i model.rb -r MyClass'
77
+ exit
78
+ end
79
+
80
+ input_path = File.expand_path(params[:input][0], Dir.pwd)
81
+
82
+ unless File.exist?(input_path)
83
+ puts "File '#{input_path}' does not exist"
84
+ exit
85
+ end
86
+
87
+ unless params[:root]
88
+ puts 'Model class is required: shaleb -i model.rb -r MyClass'
89
+ exit
90
+ end
91
+
92
+ require input_path
93
+
94
+ klass = Object.const_get(params[:root])
95
+
96
+ if params[:format] == 'xml'
97
+ if params[:output]
98
+ base_name = File.basename(params[:output], File.extname(params[:output]))
99
+ schemas = Shale::Schema.to_xml(klass, base_name, pretty: params[:pretty])
100
+
101
+ schemas.map do |name, xml|
102
+ File.write(File.expand_path(name, Dir.pwd), xml)
103
+ end
104
+ else
105
+ schemas = Shale::Schema.to_xml(klass, pretty: params[:pretty])
106
+
107
+ output = schemas.map do |name, xml|
108
+ "<!-- #{name} -->\n#{xml}\n"
109
+ end.join("\n")
110
+
111
+ puts output
112
+ end
113
+ else
114
+ schema = Shale::Schema.to_json(klass, pretty: params[:pretty])
115
+
116
+ if params[:output]
117
+ output_path = File.expand_path(params[:output], Dir.pwd)
118
+ File.write(output_path, schema)
119
+ else
120
+ puts schema
121
+ end
122
+ end
123
+ end
@@ -22,12 +22,17 @@ module Shale
22
22
  # Serialize Hash into JSON
23
23
  #
24
24
  # @param [Hash] obj Hash object
25
+ # @param [Array<Symbol>] options
25
26
  #
26
27
  # @return [String]
27
28
  #
28
29
  # @api private
29
- def self.dump(obj)
30
- ::JSON.generate(obj)
30
+ def self.dump(obj, *options)
31
+ if options.include?(:pretty)
32
+ ::JSON.pretty_generate(obj)
33
+ else
34
+ ::JSON.generate(obj)
35
+ end
31
36
  end
32
37
  end
33
38
  end