shale 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +200 -21
- data/exe/shaleb +108 -36
- data/lib/shale/adapter/nokogiri/document.rb +87 -0
- data/lib/shale/adapter/nokogiri/node.rb +100 -0
- data/lib/shale/adapter/nokogiri.rb +11 -151
- data/lib/shale/adapter/ox/document.rb +80 -0
- data/lib/shale/adapter/ox/node.rb +88 -0
- data/lib/shale/adapter/ox.rb +9 -134
- data/lib/shale/adapter/rexml/document.rb +88 -0
- data/lib/shale/adapter/rexml/node.rb +99 -0
- data/lib/shale/adapter/rexml.rb +9 -150
- data/lib/shale/attribute.rb +6 -0
- data/lib/shale/error.rb +39 -0
- data/lib/shale/mapper.rb +8 -6
- data/lib/shale/schema/compiler/boolean.rb +21 -0
- data/lib/shale/schema/compiler/complex.rb +88 -0
- data/lib/shale/schema/compiler/date.rb +21 -0
- data/lib/shale/schema/compiler/float.rb +21 -0
- data/lib/shale/schema/compiler/integer.rb +21 -0
- data/lib/shale/schema/compiler/property.rb +70 -0
- data/lib/shale/schema/compiler/string.rb +21 -0
- data/lib/shale/schema/compiler/time.rb +21 -0
- data/lib/shale/schema/compiler/value.rb +21 -0
- data/lib/shale/schema/compiler/xml_complex.rb +50 -0
- data/lib/shale/schema/compiler/xml_property.rb +73 -0
- data/lib/shale/schema/json_compiler.rb +331 -0
- data/lib/shale/schema/{json → json_generator}/base.rb +2 -2
- data/lib/shale/schema/{json → json_generator}/boolean.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/collection.rb +2 -2
- data/lib/shale/schema/{json → json_generator}/date.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/float.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/integer.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/object.rb +5 -2
- data/lib/shale/schema/{json → json_generator}/ref.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/schema.rb +7 -5
- data/lib/shale/schema/{json → json_generator}/string.rb +1 -1
- data/lib/shale/schema/{json → json_generator}/time.rb +1 -1
- data/lib/shale/schema/json_generator/value.rb +23 -0
- data/lib/shale/schema/{json.rb → json_generator.rb} +36 -36
- data/lib/shale/schema/xml_compiler.rb +919 -0
- data/lib/shale/schema/{xml → xml_generator}/attribute.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/complex_type.rb +5 -2
- data/lib/shale/schema/{xml → xml_generator}/element.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/import.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/ref_attribute.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/ref_element.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/schema.rb +5 -5
- data/lib/shale/schema/{xml → xml_generator}/typed_attribute.rb +1 -1
- data/lib/shale/schema/{xml → xml_generator}/typed_element.rb +1 -1
- data/lib/shale/schema/{xml.rb → xml_generator.rb} +25 -26
- data/lib/shale/schema.rb +44 -5
- data/lib/shale/type/{composite.rb → complex.rb} +34 -22
- data/lib/shale/utils.rb +42 -7
- data/lib/shale/version.rb +1 -1
- data/lib/shale.rb +8 -19
- data/shale.gemspec +1 -1
- metadata +47 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aaa3043e612d332fab30ff87ecab722e3812e7769eba982db27c38f74c205ce1
|
4
|
+
data.tar.gz: bb5cdb963bce5756c48edb37d28bddc40a816901e87b4714ea512f07f8ec4c28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed1f1ffc88cabb5f9403ff957c02efc4565aee73b9a8a1fd2297ff13bfbb6bdf0e94664b865f8d2d812ddd66fe97d154e34982eea3318ba01c6d16b4a6922c70
|
7
|
+
data.tar.gz: d41392de36bcace9efee12a869f49beffba235ae09e09f33c6973159b96320e3f657a6edd4d634e53da430f8127e74fa1025fa2f5c75dad2e7568abe6a0794ce
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## [0.5.0] - 2022-06-28
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Allow to generate Shale model from XML Schema
|
5
|
+
|
6
|
+
### Changed
|
7
|
+
- Shale doesn't defaults to REXML anymore - XML adapter needs to be set explicitly
|
8
|
+
- Rename "JSONSchemaError" to "SchemaError"
|
9
|
+
- Rename "Composite" type to "Complex"
|
10
|
+
- Drop support for Ruby 2.6
|
11
|
+
|
12
|
+
## [0.4.0] - 2022-05-30
|
13
|
+
|
14
|
+
### Added
|
15
|
+
- Allow to add title to JSON Schema
|
16
|
+
- Map Shale::Type::Value to "anyType" XML Schema type
|
17
|
+
- Map Shale::Type::Value to "any" JSON Schema type
|
18
|
+
- Allow to generate Shale model from JSON Schema
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
- Performance improvements
|
22
|
+
- Reformat README a little bit and fix typos
|
23
|
+
|
24
|
+
### Fixed
|
25
|
+
- Fix stack overflow caused by circular dependency when generating JSON and XML schemas
|
26
|
+
|
27
|
+
## [0.3.1] - 2022-04-29
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
- Rename `id` -> `$id` and add info about supported JSON Schema dialect
|
31
|
+
|
1
32
|
## [0.3.0] - 2022-04-29
|
2
33
|
|
3
34
|
### Added
|
data/README.md
CHANGED
@@ -1,10 +1,23 @@
|
|
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
|
+
Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
* Convert JSON, YAML and XML to Ruby data model
|
12
|
+
* Convert Ruby data model to JSON, YAML and XML
|
13
|
+
* Generate JSON and XML Schema from Ruby models
|
14
|
+
* Compile JSON and XML Schema into Ruby models
|
15
|
+
* Out of the box support for JSON, YAML, Nokogiri, REXML and Ox parsers
|
16
|
+
* Support for custom adapters
|
4
17
|
|
5
18
|
## Installation
|
6
19
|
|
7
|
-
Shale supports Ruby (MRI) 2.
|
20
|
+
Shale supports Ruby (MRI) 2.7+
|
8
21
|
|
9
22
|
Add this line to your application's Gemfile:
|
10
23
|
|
@@ -47,12 +60,12 @@ $ gem install shale
|
|
47
60
|
* [Writing your own type](#writing-your-own-type)
|
48
61
|
* [Adapters](#adapters)
|
49
62
|
* [Generating JSON Schema](#generating-json-schema)
|
63
|
+
* [Compiling JSON Schema into Shale model](#compiling-json-schema-into-shale-model)
|
50
64
|
* [Generating XML Schema](#generating-xml-schema)
|
65
|
+
* [Compiling XML Schema into Shale model](#compiling-xml-schema-into-shale-model)
|
51
66
|
|
52
67
|
## Usage
|
53
68
|
|
54
|
-
Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
|
55
|
-
|
56
69
|
### Simple use case
|
57
70
|
|
58
71
|
```ruby
|
@@ -126,6 +139,7 @@ DATA
|
|
126
139
|
|
127
140
|
```ruby
|
128
141
|
person.to_json
|
142
|
+
|
129
143
|
# =>
|
130
144
|
#
|
131
145
|
# {
|
@@ -164,6 +178,7 @@ DATA
|
|
164
178
|
|
165
179
|
```ruby
|
166
180
|
person.to_yaml
|
181
|
+
|
167
182
|
# =>
|
168
183
|
#
|
169
184
|
# ---
|
@@ -201,6 +216,7 @@ person = Person.from_hash(
|
|
201
216
|
|
202
217
|
```ruby
|
203
218
|
person.to_hash
|
219
|
+
|
204
220
|
# =>
|
205
221
|
#
|
206
222
|
# {
|
@@ -215,6 +231,15 @@ person.to_hash
|
|
215
231
|
|
216
232
|
### Converting XML to object
|
217
233
|
|
234
|
+
To use XML with Shale you have to set adapter you want to use.
|
235
|
+
Shale comes with adapters for REXML, Nokogiri and OX parsers.
|
236
|
+
For details see [Adapters](#adapters) section.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
require 'shale/adapter/rexml'
|
240
|
+
Shale.xml_adapter = Shale::Adapter::REXML
|
241
|
+
```
|
242
|
+
|
218
243
|
```ruby
|
219
244
|
person = Person.from_xml(<<~DATA)
|
220
245
|
<person>
|
@@ -237,6 +262,7 @@ DATA
|
|
237
262
|
|
238
263
|
```ruby
|
239
264
|
person.to_xml
|
265
|
+
|
240
266
|
# =>
|
241
267
|
#
|
242
268
|
# <person>
|
@@ -256,7 +282,7 @@ person.to_xml
|
|
256
282
|
|
257
283
|
### Mapping JSON keys to object attributes
|
258
284
|
|
259
|
-
By default keys are named the same as attributes. To use custom
|
285
|
+
By default keys are named the same as attributes. To use custom keys use:
|
260
286
|
|
261
287
|
```ruby
|
262
288
|
class Person < Shale::Mapper
|
@@ -300,7 +326,7 @@ end
|
|
300
326
|
|
301
327
|
### Mapping XML elements and attributes to object attributes
|
302
328
|
|
303
|
-
XML is more
|
329
|
+
XML is more complicated format than JSON or YAML. To map elements, attributes and content use:
|
304
330
|
|
305
331
|
```ruby
|
306
332
|
class Address < Shale::Mapper
|
@@ -356,8 +382,6 @@ DATA
|
|
356
382
|
|
357
383
|
### Using XML namespaces
|
358
384
|
|
359
|
-
:warning: **Ox doesn't support XML namespaces**
|
360
|
-
|
361
385
|
To map namespaced elements and attributes use `namespace` and `prefix` properties on
|
362
386
|
`map_element` and `map_attribute`
|
363
387
|
|
@@ -385,7 +409,7 @@ DATA
|
|
385
409
|
```
|
386
410
|
|
387
411
|
To define default namespace for all elements use `namespace` declaration
|
388
|
-
(this will define namespace
|
412
|
+
(this will define namespace on elements only, if you want to define namespace on an attribute
|
389
413
|
explicitly declare it on `map_attribute`).
|
390
414
|
|
391
415
|
```ruby
|
@@ -574,8 +598,7 @@ end
|
|
574
598
|
### Adapters
|
575
599
|
|
576
600
|
Shale uses adapters for parsing and generating documents.
|
577
|
-
By default Ruby's standard JSON
|
578
|
-
REXML for XML.
|
601
|
+
By default Ruby's standard JSON and YAML parsers are used for handling JSON and YAML documents.
|
579
602
|
|
580
603
|
You can change it by providing your own adapter. For JSON and YAML, adapter must implement
|
581
604
|
`.load` and `.dump` class methods.
|
@@ -588,12 +611,15 @@ Shale.json_adapter = MultiJson
|
|
588
611
|
Shale.yaml_adapter = MyYamlAdapter
|
589
612
|
```
|
590
613
|
|
614
|
+
To handle XML documents you have to explicitly set XML adapter.
|
591
615
|
Shale provides adapters for most popular Ruby XML parsers:
|
592
616
|
|
617
|
+
:warning: **Ox doesn't support XML namespaces**
|
618
|
+
|
593
619
|
```ruby
|
594
620
|
require 'shale'
|
595
621
|
|
596
|
-
#
|
622
|
+
# if you want to use REXML:
|
597
623
|
|
598
624
|
require 'shale/adapter/rexml'
|
599
625
|
Shale.xml_adapter = Shale::Adapter::REXML
|
@@ -611,18 +637,25 @@ Shale.xml_adapter = Shale::Adapter::Ox
|
|
611
637
|
|
612
638
|
### Generating JSON Schema
|
613
639
|
|
614
|
-
|
640
|
+
:warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
|
641
|
+
|
642
|
+
To generate JSON Schema from your Shale data model use:
|
615
643
|
|
616
644
|
```ruby
|
617
645
|
require 'shale/schema'
|
618
646
|
|
619
|
-
Shale::Schema.to_json(
|
647
|
+
Shale::Schema.to_json(
|
648
|
+
Person,
|
649
|
+
id: 'http://foo.bar/schema/person',
|
650
|
+
description: 'My description',
|
651
|
+
pretty: true
|
652
|
+
)
|
620
653
|
|
621
654
|
# =>
|
622
655
|
#
|
623
656
|
# {
|
624
657
|
# "$schema": "https://json-schema.org/draft/2020-12/schema",
|
625
|
-
# "id": "
|
658
|
+
# "$id": "http://foo.bar/schema/person",
|
626
659
|
# "description": "My description",
|
627
660
|
# "$ref": "#/$defs/Person",
|
628
661
|
# "$defs": {
|
@@ -661,7 +694,7 @@ Shale::Schema.to_json(Person, id: 'My ID', description: 'My description', pretty
|
|
661
694
|
You can also use a command line tool to do it:
|
662
695
|
|
663
696
|
```
|
664
|
-
$ shaleb -i data_model.rb -
|
697
|
+
$ shaleb -i data_model.rb -r Person -p
|
665
698
|
```
|
666
699
|
|
667
700
|
If you want to convert your own types to JSON Schema types use:
|
@@ -674,18 +707,88 @@ class MyEmailType < Shale::Type::Value
|
|
674
707
|
...
|
675
708
|
end
|
676
709
|
|
677
|
-
class MyEmailJSONType < Shale::Schema::
|
710
|
+
class MyEmailJSONType < Shale::Schema::JSONGenerator::Base
|
678
711
|
def as_type
|
679
712
|
{ 'type' => 'string', 'format' => 'email' }
|
680
713
|
end
|
681
714
|
end
|
682
715
|
|
683
|
-
Shale::Schema::
|
716
|
+
Shale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)
|
717
|
+
```
|
718
|
+
|
719
|
+
### Compiling JSON Schema into Shale model
|
720
|
+
|
721
|
+
:warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
|
722
|
+
|
723
|
+
To generate Shale data model from JSON Schema use:
|
724
|
+
|
725
|
+
```ruby
|
726
|
+
require 'shale/schema'
|
727
|
+
|
728
|
+
schema = <<~SCHEMA
|
729
|
+
{
|
730
|
+
"type": "object",
|
731
|
+
"properties": {
|
732
|
+
"firstName": { "type": "string" },
|
733
|
+
"lastName": { "type": "string" },
|
734
|
+
"address": {
|
735
|
+
"type": "object",
|
736
|
+
"properties": {
|
737
|
+
"street": { "type": "string" },
|
738
|
+
"city": { "type": "string" }
|
739
|
+
}
|
740
|
+
}
|
741
|
+
}
|
742
|
+
}
|
743
|
+
SCHEMA
|
744
|
+
|
745
|
+
Shale::Schema.from_json([schema], root_name: 'Person')
|
746
|
+
|
747
|
+
# =>
|
748
|
+
#
|
749
|
+
# {
|
750
|
+
# "address" => "
|
751
|
+
# require 'shale'
|
752
|
+
#
|
753
|
+
# class Address < Shale::Mapper
|
754
|
+
# attribute :street, Shale::Type::String
|
755
|
+
# attribute :city, Shale::Type::String
|
756
|
+
#
|
757
|
+
# json do
|
758
|
+
# map 'street', to: :street
|
759
|
+
# map 'city', to: :city
|
760
|
+
# end
|
761
|
+
# end
|
762
|
+
# ",
|
763
|
+
# "person" => "
|
764
|
+
# require 'shale'
|
765
|
+
#
|
766
|
+
# require_relative 'address'
|
767
|
+
#
|
768
|
+
# class Person < Shale::Mapper
|
769
|
+
# attribute :first_name, Shale::Type::String
|
770
|
+
# attribute :last_name, Shale::Type::String
|
771
|
+
# attribute :address, Address
|
772
|
+
#
|
773
|
+
# json do
|
774
|
+
# map 'firstName', to: :first_name
|
775
|
+
# map 'lastName', to: :last_name
|
776
|
+
# map 'address', to: :address
|
777
|
+
# end
|
778
|
+
# end
|
779
|
+
# "
|
780
|
+
# }
|
781
|
+
```
|
782
|
+
|
783
|
+
You can also use a command line tool to do it:
|
784
|
+
|
785
|
+
```
|
786
|
+
$ shaleb -c -i schema.json -r Person
|
684
787
|
```
|
685
788
|
|
686
789
|
### Generating XML Schema
|
687
790
|
|
688
|
-
To generate XML Schema from
|
791
|
+
To generate XML Schema from your Shale data model use:
|
689
792
|
|
690
793
|
```ruby
|
691
794
|
require 'shale/schema'
|
@@ -733,7 +836,7 @@ Shale::Schema.to_xml(Person, pretty: true)
|
|
733
836
|
You can also use a command line tool to do it:
|
734
837
|
|
735
838
|
```
|
736
|
-
$ shaleb -i data_model.rb -
|
839
|
+
$ shaleb -i data_model.rb -r Person -p -f xml
|
737
840
|
```
|
738
841
|
|
739
842
|
If you want to convert your own types to XML Schema types use:
|
@@ -746,7 +849,83 @@ class MyEmailType < Shale::Type::Value
|
|
746
849
|
...
|
747
850
|
end
|
748
851
|
|
749
|
-
Shale::Schema::
|
852
|
+
Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
|
853
|
+
```
|
854
|
+
|
855
|
+
### Compiling XML Schema into Shale model
|
856
|
+
|
857
|
+
To generate Shale data model from XML Schema use:
|
858
|
+
|
859
|
+
```ruby
|
860
|
+
require 'shale/schema'
|
861
|
+
|
862
|
+
schema = <<~SCHEMA
|
863
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
864
|
+
<xs:element name="Person" type="Person" />
|
865
|
+
|
866
|
+
<xs:complexType name="Person">
|
867
|
+
<xs:sequence>
|
868
|
+
<xs:element name="FirstName" type="xs:string" />
|
869
|
+
<xs:element name="LastName" type="xs:string" />
|
870
|
+
<xs:element name="Address" type="Address" />
|
871
|
+
</xs:sequence>
|
872
|
+
</xs:complexType>
|
873
|
+
|
874
|
+
<xs:complexType name="Address">
|
875
|
+
<xs:sequence>
|
876
|
+
<xs:element name="Street" type="xs:string" />
|
877
|
+
<xs:element name="City" type="xs:string" />
|
878
|
+
</xs:sequence>
|
879
|
+
</xs:complexType>
|
880
|
+
</xs:schema>
|
881
|
+
SCHEMA
|
882
|
+
|
883
|
+
Shale::Schema.from_xml([schema])
|
884
|
+
|
885
|
+
# =>
|
886
|
+
#
|
887
|
+
# {
|
888
|
+
# "address" => "
|
889
|
+
# require 'shale'
|
890
|
+
#
|
891
|
+
# class Address < Shale::Mapper
|
892
|
+
# attribute :street, Shale::Type::String
|
893
|
+
# attribute :city, Shale::Type::String
|
894
|
+
#
|
895
|
+
# xml do
|
896
|
+
# root 'Address'
|
897
|
+
#
|
898
|
+
# map_element 'Street', to: :street
|
899
|
+
# map_element 'City', to: :city
|
900
|
+
# end
|
901
|
+
# end
|
902
|
+
# ",
|
903
|
+
# "person" => "
|
904
|
+
# require 'shale'
|
905
|
+
#
|
906
|
+
# require_relative 'address'
|
907
|
+
#
|
908
|
+
# class Person < Shale::Mapper
|
909
|
+
# attribute :first_name, Shale::Type::String
|
910
|
+
# attribute :last_name, Shale::Type::String
|
911
|
+
# attribute :address, Address
|
912
|
+
#
|
913
|
+
# xml do
|
914
|
+
# root 'Person'
|
915
|
+
#
|
916
|
+
# map_element 'FirstName', to: :first_name
|
917
|
+
# map_element 'LastName', to: :last_name
|
918
|
+
# map_element 'Address', to: :address
|
919
|
+
# end
|
920
|
+
# end
|
921
|
+
# "
|
922
|
+
# }
|
923
|
+
```
|
924
|
+
|
925
|
+
You can also use a command line tool to do it:
|
926
|
+
|
927
|
+
```
|
928
|
+
$ shaleb -c -f xml -i schema.xml
|
750
929
|
```
|
751
930
|
|
752
931
|
## Contributing
|
data/exe/shaleb
CHANGED
@@ -1,14 +1,32 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'fileutils'
|
4
5
|
require 'optparse'
|
5
6
|
|
6
|
-
|
7
|
+
def require_local_or_global(path)
|
8
|
+
base_path = File.expand_path('../lib', __dir__)
|
7
9
|
|
8
|
-
if File.exist?(base_path)
|
9
|
-
|
10
|
-
else
|
11
|
-
|
10
|
+
if File.exist?(base_path)
|
11
|
+
require_relative "../lib/#{path}"
|
12
|
+
else
|
13
|
+
require path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_local_or_global('shale/schema')
|
18
|
+
|
19
|
+
def load_xml_parser
|
20
|
+
require_local_or_global('shale/adapter/nokogiri')
|
21
|
+
Shale.xml_adapter = Shale::Adapter::Nokogiri
|
22
|
+
rescue LoadError
|
23
|
+
begin
|
24
|
+
require_local_or_global('shale/adapter/rexml')
|
25
|
+
Shale.xml_adapter = Shale::Adapter::REXML
|
26
|
+
rescue LoadError
|
27
|
+
puts "Can't load XML parser. Make sure Nokogiri or REXML is installed on your system!"
|
28
|
+
exit
|
29
|
+
end
|
12
30
|
end
|
13
31
|
|
14
32
|
params = {}
|
@@ -16,11 +34,17 @@ params = {}
|
|
16
34
|
ARGV << '-h' if ARGV.empty?
|
17
35
|
|
18
36
|
OptionParser.new do |opts|
|
19
|
-
opts.banner =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
37
|
+
opts.banner = <<~BANNER
|
38
|
+
Usage: shaleb [options]
|
39
|
+
example generate schema from Shale model: shaleb -g -i data_model.rb -c MyRoot
|
40
|
+
example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -c MyRoot
|
41
|
+
BANNER
|
42
|
+
|
43
|
+
opts.on('-g', '--generate', 'generate schema from Shale model')
|
44
|
+
opts.on('-c', '--compile', 'compile schema into Shale model')
|
45
|
+
opts.on('-i INPUT', '--input', Array, 'Input file')
|
46
|
+
opts.on('-o OUTPUT', '--output', 'Output (defaults to STDOUT)')
|
47
|
+
opts.on('-r ROOT', '--root ROOT', 'Shale model class name')
|
24
48
|
opts.on('-f FORMAT', '--format FORMAT', 'Schema format: JSON (default), XML')
|
25
49
|
opts.on('-p', '--pretty', 'Pretty print generated schema')
|
26
50
|
|
@@ -30,46 +54,94 @@ OptionParser.new do |opts|
|
|
30
54
|
end
|
31
55
|
end.parse!(into: params)
|
32
56
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
57
|
+
if params[:compile]
|
58
|
+
unless params[:input]
|
59
|
+
puts 'Input file is required: shaleb -c -i schema1.json,schema2.json'
|
60
|
+
exit
|
61
|
+
end
|
39
62
|
|
40
|
-
|
41
|
-
|
42
|
-
exit
|
43
|
-
end
|
63
|
+
schemas = params[:input].map do |file|
|
64
|
+
path = File.expand_path(file, Dir.pwd)
|
44
65
|
|
45
|
-
|
66
|
+
if File.exist?(path)
|
67
|
+
File.read(path)
|
68
|
+
else
|
69
|
+
puts "File '#{path}' does not exist"
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
end
|
46
73
|
|
47
|
-
|
74
|
+
if params[:format] == 'xml'
|
75
|
+
load_xml_parser
|
76
|
+
models = Shale::Schema.from_xml(schemas)
|
77
|
+
else
|
78
|
+
models = Shale::Schema.from_json(schemas, root_name: params[:root])
|
79
|
+
end
|
48
80
|
|
49
|
-
if params[:format] == 'xml'
|
50
81
|
if params[:output]
|
51
|
-
|
52
|
-
|
82
|
+
dir = File.expand_path(params[:output], Dir.pwd)
|
83
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
53
84
|
|
54
|
-
|
55
|
-
File.
|
85
|
+
models.each do |name, model|
|
86
|
+
output_path = File.join(dir, "#{name}.rb")
|
87
|
+
File.write(output_path, model)
|
56
88
|
end
|
57
89
|
else
|
58
|
-
|
59
|
-
|
60
|
-
output = schemas.map do |name, xml|
|
61
|
-
"<!-- #{name} -->\n#{xml}\n"
|
90
|
+
output = models.map do |name, model|
|
91
|
+
"# --- #{name}.rb ---\n#{model}\n"
|
62
92
|
end.join("\n")
|
63
93
|
|
64
94
|
puts output
|
65
95
|
end
|
66
96
|
else
|
67
|
-
|
97
|
+
unless params[:input]
|
98
|
+
puts 'Input file is required: shaleb -i model.rb -r MyClass'
|
99
|
+
exit
|
100
|
+
end
|
68
101
|
|
69
|
-
|
70
|
-
|
71
|
-
|
102
|
+
input_path = File.expand_path(params[:input][0], Dir.pwd)
|
103
|
+
|
104
|
+
unless File.exist?(input_path)
|
105
|
+
puts "File '#{input_path}' does not exist"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
unless params[:root]
|
110
|
+
puts 'Model class is required: shaleb -i model.rb -r MyClass'
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
|
114
|
+
require input_path
|
115
|
+
|
116
|
+
klass = Object.const_get(params[:root])
|
117
|
+
|
118
|
+
if params[:format] == 'xml'
|
119
|
+
load_xml_parser
|
120
|
+
|
121
|
+
if params[:output]
|
122
|
+
base_name = File.basename(params[:output], File.extname(params[:output]))
|
123
|
+
schemas = Shale::Schema.to_xml(klass, base_name, pretty: params[:pretty])
|
124
|
+
|
125
|
+
schemas.map do |name, xml|
|
126
|
+
File.write(File.expand_path(name, Dir.pwd), xml)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
schemas = Shale::Schema.to_xml(klass, pretty: params[:pretty])
|
130
|
+
|
131
|
+
output = schemas.map do |name, xml|
|
132
|
+
"<!-- #{name} -->\n#{xml}\n"
|
133
|
+
end.join("\n")
|
134
|
+
|
135
|
+
puts output
|
136
|
+
end
|
72
137
|
else
|
73
|
-
|
138
|
+
schema = Shale::Schema.to_json(klass, pretty: params[:pretty])
|
139
|
+
|
140
|
+
if params[:output]
|
141
|
+
output_path = File.expand_path(params[:output], Dir.pwd)
|
142
|
+
File.write(output_path, schema)
|
143
|
+
else
|
144
|
+
puts schema
|
145
|
+
end
|
74
146
|
end
|
75
147
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shale
|
4
|
+
module Adapter
|
5
|
+
module Nokogiri
|
6
|
+
# Wrapper around Nokogiri API
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Document
|
10
|
+
# Initialize object
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def initialize
|
14
|
+
@doc = ::Nokogiri::XML::Document.new
|
15
|
+
@namespaces = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return Nokogiri document
|
19
|
+
#
|
20
|
+
# @return [::Nokogiri::XML::Document]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def doc
|
24
|
+
if @doc.root
|
25
|
+
@namespaces.each do |prefix, namespace|
|
26
|
+
@doc.root.add_namespace(prefix, namespace)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@doc
|
31
|
+
end
|
32
|
+
|
33
|
+
# Create Nokogiri element
|
34
|
+
#
|
35
|
+
# @param [String] name Name of the XML element
|
36
|
+
#
|
37
|
+
# @return [::Nokogiri::XML::Element]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def create_element(name)
|
41
|
+
::Nokogiri::XML::Element.new(name, @doc)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add XML namespace to document
|
45
|
+
#
|
46
|
+
# @param [String] prefix
|
47
|
+
# @param [String] namespace
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def add_namespace(prefix, namespace)
|
51
|
+
@namespaces[prefix] = namespace if prefix && namespace
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add attribute to Nokogiri element
|
55
|
+
#
|
56
|
+
# @param [::Nokogiri::XML::Element] element Nokogiri element
|
57
|
+
# @param [String] name Name of the XML attribute
|
58
|
+
# @param [String] value Value of the XML attribute
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
def add_attribute(element, name, value)
|
62
|
+
element[name] = value
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add child element to Nokogiri element
|
66
|
+
#
|
67
|
+
# @param [::Nokogiri::XML::Element] element Nokogiri parent element
|
68
|
+
# @param [::Nokogiri::XML::Element] child Nokogiri child element
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def add_element(element, child)
|
72
|
+
element.add_child(child)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add text node to Nokogiri element
|
76
|
+
#
|
77
|
+
# @param [::Nokogiri::XML::Element] element Nokogiri element
|
78
|
+
# @param [String] text Text to add
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def add_text(element, text)
|
82
|
+
element.content = text
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|