shale 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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