shale 0.8.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76c0022b3d2d42be143362c84c6697628781acdce7694fb9bc0f305d3f854964
4
- data.tar.gz: 0b0b1b060eab81ebfae80afa3c31c809280b66ebd77e413c5908c4bbd09a081a
3
+ metadata.gz: 4e3b4d27ab7e8cc9b4873dfa60b52bdbafe50c2885085c4ec77ac6887351aab1
4
+ data.tar.gz: 6ce71a10ebf53633930b6d5a92ebb5f736aab44c47d6910a25238209192a4b92
5
5
  SHA512:
6
- metadata.gz: 43bc9188d1f07c5c367a8ed94e2a3da6b878a7b7b86773d7898697abbd3937d4de6ae0471f9362c72e8a6be2cc5e5100a5206b2f2cc7155ff2ff8784953ef702
7
- data.tar.gz: 0e610703cc73809a78007785962d9a322e515b88ab143a55adfeaa1a0da5961f14d8768a3dec4ad797070bbab165c238123702cbc671f522f86e2934968ff78b
6
+ metadata.gz: 23c3b182c72c0e1d5102eb667541caccfb56e836e636bd083027425a60a12ed39ffce1ab3540adc0659c6b1bcbcf5255d192bbdc2d39bfc10c762ee4bdaaaa1e
7
+ data.tar.gz: ef7481e6076cc8f426c1dd0fb002d984594cbf3d59743a31450e8989048ffdd6cdc8fc735e90b7529c1935d7251c6751e70f7c3734d5c3f7df4b8124cc4b8256
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## [1.0.0] - 2023-07-15
2
+
3
+ ### Added
4
+ - Support for Ruby 3.2
5
+ - Support for delegating fields to nested attributes
6
+ - JSON and XML schema namespace mapping support
7
+ - Allow to set render_nil defaults
8
+
9
+ ### Changed
10
+ - Use `ShaleError` as a base class for exceptions
11
+ - Use model instead of mapper names for schema types
12
+
13
+ ### Fixed
14
+ - Fix compilation error for bundled JSON schemas
15
+ - Fix type inference for XML schema when default namespace is used
16
+ - Fix XML schema handling with a period in the element name
17
+
18
+ ## [0.9.0] - 2022-10-31
19
+
20
+ ### Added
21
+ - Support for CSV
22
+ - Allow to specify version and add encoding to XML declaration
23
+ - Support for mapping/generating collections
24
+
1
25
  ## [0.8.0] - 2022-08-30
2
26
 
3
27
  ### Added
data/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # Shale
2
2
 
3
- Shale is a Ruby object mapper and serializer for JSON, YAML, TOML and XML.
4
- It allows you to parse JSON, YAML, TOML and XML data and convert it into Ruby data structures,
5
- as well as serialize data structures into JSON, YAML, TOML or XML.
3
+ Shale is a Ruby object mapper and serializer for JSON, YAML, TOML, CSV and XML.
4
+ It allows you to parse JSON, YAML, TOML, CSV and XML data and convert it into Ruby data structures,
5
+ as well as serialize data structures into JSON, YAML, TOML, CSV or XML.
6
6
 
7
7
  Documentation with interactive examples is available at [Shale website](https://www.shalerb.org)
8
8
 
9
9
  ## Features
10
10
 
11
- * Convert JSON, YAML, TOML and XML to Ruby data model
12
- * Convert Ruby data model to JSON, YAML, TOML and XML
11
+ * Convert JSON, YAML, TOML, CSV and XML to Ruby data model
12
+ * Convert Ruby data model to JSON, YAML, TOML, CSV and XML
13
13
  * Generate JSON and XML Schema from Ruby models
14
14
  * Compile JSON and XML Schema into Ruby models
15
- * Out of the box support for JSON, YAML, Tomlib, toml-rb, Nokogiri, REXML and Ox parsers
15
+ * Out of the box support for JSON, YAML, Tomlib, toml-rb, CSV, Nokogiri, REXML and Ox parsers
16
16
  * Support for custom adapters
17
17
 
18
18
  ## Installation
@@ -51,14 +51,19 @@ $ gem install shale
51
51
  * [Converting object to Hash](#converting-object-to-hash)
52
52
  * [Converting XML to object](#converting-xml-to-object)
53
53
  * [Converting object to XML](#converting-object-to-xml)
54
+ * [Converting CSV to object](#converting-csv-to-object)
55
+ * [Converting object to CSV](#converting-object-to-csv)
56
+ * [Converting collections](#converting-collections)
54
57
  * [Mapping JSON keys to object attributes](#mapping-json-keys-to-object-attributes)
55
58
  * [Mapping YAML keys to object attributes](#mapping-yaml-keys-to-object-attributes)
56
59
  * [Mapping TOML keys to object attributes](#mapping-toml-keys-to-object-attributes)
60
+ * [Mapping CSV columns to object attributes](#mapping-csv-columns-to-object-attributes)
57
61
  * [Mapping Hash keys to object attributes](#mapping-hash-keys-to-object-attributes)
58
62
  * [Mapping XML elements and attributes to object attributes](#mapping-xml-elements-and-attributes-to-object-attributes)
59
63
  * [Using XML namespaces](#using-xml-namespaces)
60
64
  * [Rendering nil values](#rendering-nil-values)
61
65
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
66
+ * [Delegating fields to child attributes](#delegating-fields-to-child-attributes)
62
67
  * [Additional options](#additional-options)
63
68
  * [Using custom models](#using-custom-models)
64
69
  * [Supported types](#supported-types)
@@ -346,6 +351,70 @@ person.to_xml
346
351
  # </person>
347
352
  ```
348
353
 
354
+ ### Converting CSV to object
355
+
356
+ CSV represents a flat data structure, so you can't map properties to complex types directly,
357
+ but you can use methods to map properties to complex types
358
+ (see [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
359
+ section).
360
+
361
+ `.from_csv` method allways returns an array of records.
362
+
363
+ ```ruby
364
+ people = Person.from_csv(<<~DATA)
365
+ John,Doe,50,false
366
+ DATA
367
+ ```
368
+
369
+ ### Converting object to CSV
370
+
371
+ ```ruby
372
+ people[0].to_csv # or Person.to_csv(people) if you want to convert a collection
373
+
374
+ # =>
375
+ #
376
+ # John,Doe,50,false
377
+ ```
378
+
379
+ ### Converting collections
380
+
381
+ Shale allows converting collections for formats that support it (JSON, YAML and CSV).
382
+ To convert Ruby array to JSON:
383
+
384
+ ```ruby
385
+ person1 = Person.new(name: 'John Doe')
386
+ person2 = Person.new(name: 'Joe Sixpack')
387
+
388
+ Person.to_json([person1, person2], pretty: true)
389
+ # or Person.to_yaml([person1, person2])
390
+ # or Person.to_csv([person1, person2])
391
+
392
+ # =>
393
+ #
394
+ # [
395
+ # { "name": "John Doe" },
396
+ # { "name": "Joe Sixpack" }
397
+ # ]
398
+ ```
399
+
400
+ To convert JSON array to Ruby:
401
+
402
+ ```ruby
403
+ Person.from_json(<<~JSON)
404
+ [
405
+ { "name": "John Doe" },
406
+ { "name": "Joe Sixpack" }
407
+ ]
408
+ JSON
409
+
410
+ # =>
411
+ #
412
+ # [
413
+ # #<Person:0x00000001033dbce8 @name="John Doe">,
414
+ # #<Person:0x00000001033db4c8 @name="Joe Sixpack">
415
+ # ]
416
+ ```
417
+
349
418
  ### Mapping JSON keys to object attributes
350
419
 
351
420
  By default keys are named the same as attributes. To use custom keys use:
@@ -392,6 +461,24 @@ class Person < Shale::Mapper
392
461
  end
393
462
  ```
394
463
 
464
+ ### Mapping CSV columns to object attributes
465
+
466
+ For CSV the order of mapping matters, the first argument in the `map` method is only
467
+ used as a label in header row. So, in the example below the first column will be mapped
468
+ to `:first_name` attribute and the second column to `:last_name`.
469
+
470
+ ```ruby
471
+ class Person < Shale::Mapper
472
+ attribute :first_name, Shale::Type::String
473
+ attribute :last_name, Shale::Type::String
474
+
475
+ csv do
476
+ map 'firstName', to: :first_name
477
+ map 'lastName', to: :last_name
478
+ end
479
+ end
480
+ ```
481
+
395
482
  ### Mapping Hash keys to object attributes
396
483
 
397
484
  ```ruby
@@ -561,8 +648,9 @@ DATA
561
648
 
562
649
  ### Rendering nil values
563
650
 
564
- By default elements with `nil` value are not rendered. You can change this behavior
565
- by using `render_nil: true` on a mapping.
651
+ For JSON, YAML, TOML and XML by default, elements with `nil` value are not rendered.
652
+ You can change this behavior by using `render_nil: true` on a mapping.
653
+ For CSV the default is to render `nil` elements.
566
654
 
567
655
  ```ruby
568
656
  class Person < Shale::Mapper
@@ -605,6 +693,49 @@ puts person.to_xml(pretty: true)
605
693
  # </person>
606
694
  ```
607
695
 
696
+ If you want to change how nil values are rendered for all mappings you can use `render_nil` method:
697
+
698
+ ```ruby
699
+ class Base < Shale::Mapper
700
+ json do
701
+ # change render_nil default for all JSON mappings inheriting from Base class
702
+ render_nil true
703
+ end
704
+ end
705
+
706
+ class Person < Base
707
+ attribute :first_name, Shale::Type::String
708
+ attribute :last_name, Shale::Type::String
709
+ attribute :age, Shale::Type::Integer
710
+
711
+ json do
712
+ # override default from Base class
713
+ render_nil false
714
+
715
+ map 'first_name', to: :first_name
716
+ map 'last_name', to: :last_name
717
+ map 'age', to: :age, render_nil: true # override default
718
+ end
719
+ end
720
+ ```
721
+
722
+ :warning: The default affects only the mappings declared after setting the default value e.g.
723
+
724
+ ```ruby
725
+ class Person < Base
726
+ attribute :first_name, Shale::Type::String
727
+ attribute :last_name, Shale::Type::String
728
+
729
+ json do
730
+ render_nil false
731
+ map 'first_name', to: :first_name # render_nil will be false for this mapping
732
+
733
+ render_nil true
734
+ map 'last_name', to: :last_name # render_nil will be true for this mapping
735
+ end
736
+ end
737
+ ```
738
+
608
739
  ### Using methods to extract and generate data
609
740
 
610
741
  If you need full controll over extracting and generating data from/to document,
@@ -778,6 +909,42 @@ DATA
778
909
  # => #<Person:0x00007f9bc3086d60 @name="John Doe">
779
910
  ```
780
911
 
912
+ ### Delegating fields to child attributes
913
+
914
+ To delegate fields to child complex types you can use `receiver: :child` declaration:
915
+
916
+ ```ruby
917
+ class Address < Shale::Mapper
918
+ attribute :city, Shale::Type::String
919
+ attribute :street, Shale::Type::String
920
+ end
921
+
922
+ class Person < Shale::Mapper
923
+ attribute :name, Shale::Type::String
924
+ attribute :address, Address
925
+
926
+ json do
927
+ map 'name', to: :name
928
+ map 'city', to: :city, receiver: :address
929
+ map 'street', to: :street, receiver: :address
930
+ end
931
+ end
932
+
933
+ person = Person.from_json(<<~DATA)
934
+ {
935
+ "name": "John Doe",
936
+ "city": "London",
937
+ "street": "Oxford Street"
938
+ }
939
+ DATA
940
+
941
+ # =>
942
+ #
943
+ # #<Person:0x00007f9bc3086d60
944
+ # @name="John Doe",
945
+ # @address=#<Address:0x0000000102cbd218 @city="London", @street="Oxford Street">>
946
+ ```
947
+
781
948
  ### Additional options
782
949
 
783
950
  You can control which attributes to render and parse by
@@ -853,19 +1020,52 @@ person.to_json(pretty: true)
853
1020
  # }
854
1021
  ```
855
1022
 
856
- You can also add an XML declaration by passing `declaration: true` to `#to_xml`
1023
+ You can also add an XML declaration by passing `declaration: true` and `encoding: true`
1024
+ or if you want to spcify version: `declaration: '1.1'` and `encoding: 'ASCII'` to `#to_xml`
857
1025
 
858
1026
  ```ruby
859
- person.to_xml(pretty: true, declaration: true)
1027
+ person.to_xml(pretty: true, declaration: true, encoding: true)
860
1028
 
861
1029
  # =>
862
1030
  #
863
- # <?xml version="1.0"?>
1031
+ # <?xml version="1.0" encoding="UTF-8"?>
864
1032
  # <Person>
865
1033
  # <Address city="London"/>
866
1034
  # </Person>
867
1035
  ```
868
1036
 
1037
+ For CSV you can pass `headers: true` to indicate that the first row contains column
1038
+ names and shouldn't be included in the returned collection. It also accepts all the options that
1039
+ [CSV parser](https://ruby-doc.org/stdlib-3.1.2/libdoc/csv/rdoc/CSV.html#class-CSV-label-Options) accepts.
1040
+
1041
+ ```ruby
1042
+ class Person
1043
+ attribute :first_name, Shale::Type::String
1044
+ attribute :last_name, Shale::Type::String
1045
+ end
1046
+
1047
+ people = Person.from_csv(<<~DATA, headers: true, col_sep: '|')
1048
+ first_name|last_name
1049
+ John|Doe
1050
+ James|Sixpack
1051
+ DATA
1052
+
1053
+ # =>
1054
+ #
1055
+ # [
1056
+ # #<Person:0x0000000113d7a488 @first_name="John", @last_name="Doe">,
1057
+ # #<Person:0x0000000113d7a488 @first_name="James", @last_name="Sixpack">
1058
+ # ]
1059
+
1060
+ Person.to_csv(people, headers: true, col_sep: '|')
1061
+
1062
+ # =>
1063
+ #
1064
+ # first_name|last_name
1065
+ # John|Doe
1066
+ # James|Sixpack
1067
+ ```
1068
+
869
1069
  ### Using custom models
870
1070
 
871
1071
  By default Shale combines mapper and model into one class. If you want to use your own classes
@@ -955,10 +1155,10 @@ end
955
1155
  ### Adapters
956
1156
 
957
1157
  Shale uses adapters for parsing and generating documents.
958
- By default Ruby's standard JSON, YAML parsers are used for handling JSON and YAML documents.
1158
+ By default Ruby's standard JSON, YAML, CSV parsers are used for handling JSON YAML, CSV documents.
959
1159
 
960
- You can change it by providing your own adapter. For JSON, YAML and TOML, adapter must implement
961
- `.load` and `.dump` class methods.
1160
+ You can change it by providing your own adapter. For JSON, YAML, TOML and CSV adapter must
1161
+ implement `.load` and `.dump` class methods.
962
1162
 
963
1163
  ```ruby
964
1164
  require 'shale'
@@ -1092,7 +1292,9 @@ Shale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)
1092
1292
 
1093
1293
  :warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
1094
1294
 
1095
- To generate Shale data model from JSON Schema use:
1295
+ To generate Shale data model from JSON Schema use `Shale::Schema.from_json`.
1296
+ You can pass `root_name: 'Foobar'` to change the name of the root type and
1297
+ `namespace_mapping: {}` to map schemas to Ruby modules:
1096
1298
 
1097
1299
  ```ruby
1098
1300
  require 'shale/schema'
@@ -1103,7 +1305,11 @@ schema = <<~SCHEMA
1103
1305
  "properties": {
1104
1306
  "firstName": { "type": "string" },
1105
1307
  "lastName": { "type": "string" },
1106
- "address": {
1308
+ "address": { "$ref": "http://bar.com" }
1309
+ },
1310
+ "$defs": {
1311
+ "Address": {
1312
+ "$id": "http://bar.com",
1107
1313
  "type": "object",
1108
1314
  "properties": {
1109
1315
  "street": { "type": "string" },
@@ -1114,38 +1320,53 @@ schema = <<~SCHEMA
1114
1320
  }
1115
1321
  SCHEMA
1116
1322
 
1117
- Shale::Schema.from_json([schema], root_name: 'Person')
1323
+ Shale::Schema.from_json(
1324
+ [schema],
1325
+ root_name: 'Person',
1326
+ namespace_mapping: {
1327
+ nil => 'Api::Foo', # default schema (without ID)
1328
+ 'http://bar.com' => 'Api::Bar',
1329
+ }
1330
+ )
1118
1331
 
1119
1332
  # =>
1120
1333
  #
1121
1334
  # {
1122
- # "address" => "
1335
+ # "api/bar/address" => "
1123
1336
  # require 'shale'
1124
1337
  #
1125
- # class Address < Shale::Mapper
1126
- # attribute :street, Shale::Type::String
1127
- # attribute :city, Shale::Type::String
1338
+ # module Api
1339
+ # module Bar
1340
+ # class Address < Shale::Mapper
1341
+ # attribute :street, Shale::Type::String
1342
+ # attribute :city, Shale::Type::String
1128
1343
  #
1129
- # json do
1130
- # map 'street', to: :street
1131
- # map 'city', to: :city
1344
+ # json do
1345
+ # map 'street', to: :street
1346
+ # map 'city', to: :city
1347
+ # end
1348
+ # end
1132
1349
  # end
1133
1350
  # end
1134
1351
  # ",
1135
- # "person" => "
1352
+ # "api/foo/person" => "
1136
1353
  # require 'shale'
1137
1354
  #
1138
- # require_relative 'address'
1355
+ # require_relative '../bar/address'
1139
1356
  #
1140
- # class Person < Shale::Mapper
1141
- # attribute :first_name, Shale::Type::String
1142
- # attribute :last_name, Shale::Type::String
1143
- # attribute :address, Address
1357
+ # module Api
1358
+ # module Foo
1359
+ # class Person < Shale::Mapper
1360
+ # attribute :first_name, Shale::Type::String
1361
+ # attribute :last_name, Shale::Type::String
1362
+ # attribute :address, Api::Bar::Address
1144
1363
  #
1145
- # json do
1146
- # map 'firstName', to: :first_name
1147
- # map 'lastName', to: :last_name
1148
- # map 'address', to: :address
1364
+ # json do
1365
+ # map 'firstName', to: :first_name
1366
+ # map 'lastName', to: :last_name
1367
+ # map 'address', to: :address
1368
+ # end
1369
+ # end
1149
1370
  # end
1150
1371
  # end
1151
1372
  # "
@@ -1155,7 +1376,7 @@ Shale::Schema.from_json([schema], root_name: 'Person')
1155
1376
  You can also use a command line tool to do it:
1156
1377
 
1157
1378
  ```
1158
- $ shaleb -c -i schema.json -r Person
1379
+ $ shaleb -c -i schema.json -r Person -m http://bar.com=Api::Bar,=Api::Foo
1159
1380
  ```
1160
1381
 
1161
1382
  ### Generating XML Schema
@@ -1226,22 +1447,39 @@ Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
1226
1447
 
1227
1448
  ### Compiling XML Schema into Shale model
1228
1449
 
1229
- To generate Shale data model from XML Schema use:
1450
+ To generate Shale data model from XML Schema use `Shale::Schema.from_xml`.
1451
+ You can pass `namespace_mapping: {}` to map XML namespaces to Ruby modules:
1230
1452
 
1231
1453
  ```ruby
1232
1454
  require 'shale/schema'
1233
1455
 
1234
- schema = <<~SCHEMA
1235
- <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1456
+ schema1 = <<~SCHEMA
1457
+ <xs:schema
1458
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
1459
+ xmlns:bar="http://bar.com"
1460
+ elementFormDefault="qualified"
1461
+ >
1462
+ <xs:import namespace="http://bar.com" />
1463
+
1236
1464
  <xs:element name="Person" type="Person" />
1237
1465
 
1238
1466
  <xs:complexType name="Person">
1239
1467
  <xs:sequence>
1240
- <xs:element name="FirstName" type="xs:string" />
1241
- <xs:element name="LastName" type="xs:string" />
1242
- <xs:element name="Address" type="Address" />
1468
+ <xs:element name="Name" type="xs:string" />
1469
+ <xs:element ref="bar:Address" />
1243
1470
  </xs:sequence>
1244
1471
  </xs:complexType>
1472
+ </xs:schema>
1473
+ SCHEMA
1474
+
1475
+ schema2 = <<~SCHEMA
1476
+ <xs:schema
1477
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
1478
+ xmlns:bar="http://bar.com"
1479
+ targetNamespace="http://bar.com"
1480
+ elementFormDefault="qualified"
1481
+ >
1482
+ <xs:element name="Address" type="bar:Address" />
1245
1483
 
1246
1484
  <xs:complexType name="Address">
1247
1485
  <xs:sequence>
@@ -1252,42 +1490,55 @@ schema = <<~SCHEMA
1252
1490
  </xs:schema>
1253
1491
  SCHEMA
1254
1492
 
1255
- Shale::Schema.from_xml([schema])
1493
+ Shale::Schema.from_xml(
1494
+ [schema1, schema2],
1495
+ namespace_mapping: {
1496
+ nil => 'Api::Foo', # no namespace
1497
+ 'http://bar.com' => 'Api::Bar',
1498
+ }
1499
+ )
1256
1500
 
1257
1501
  # =>
1258
1502
  #
1259
1503
  # {
1260
- # "address" => "
1504
+ # "api/bar/address" => "
1261
1505
  # require 'shale'
1262
1506
  #
1263
- # class Address < Shale::Mapper
1264
- # attribute :street, Shale::Type::String
1265
- # attribute :city, Shale::Type::String
1507
+ # module Api
1508
+ # module Bar
1509
+ # class Address < Shale::Mapper
1510
+ # attribute :street, Shale::Type::String
1511
+ # attribute :city, Shale::Type::String
1266
1512
  #
1267
- # xml do
1268
- # root 'Address'
1513
+ # xml do
1514
+ # root 'Address'
1515
+ # namespace 'http://bar.com', 'bar'
1269
1516
  #
1270
- # map_element 'Street', to: :street
1271
- # map_element 'City', to: :city
1517
+ # map_element 'Street', to: :street
1518
+ # map_element 'City', to: :city
1519
+ # end
1520
+ # end
1272
1521
  # end
1273
1522
  # end
1274
1523
  # ",
1275
- # "person" => "
1524
+ # "api/foo/person" => "
1276
1525
  # require 'shale'
1277
1526
  #
1278
- # require_relative 'address'
1527
+ # require_relative '../bar/address'
1279
1528
  #
1280
- # class Person < Shale::Mapper
1281
- # attribute :first_name, Shale::Type::String
1282
- # attribute :last_name, Shale::Type::String
1283
- # attribute :address, Address
1529
+ # module Api
1530
+ # module Foo
1531
+ # class Person < Shale::Mapper
1532
+ # attribute :name, Shale::Type::String
1533
+ # attribute :address, Api::Bar::Address
1284
1534
  #
1285
- # xml do
1286
- # root 'Person'
1535
+ # xml do
1536
+ # root 'Person'
1287
1537
  #
1288
- # map_element 'FirstName', to: :first_name
1289
- # map_element 'LastName', to: :last_name
1290
- # map_element 'Address', to: :address
1538
+ # map_element 'Name', to: :name
1539
+ # map_element 'Address', to: :address, prefix: 'bar', namespace: 'http://bar.com'
1540
+ # end
1541
+ # end
1291
1542
  # end
1292
1543
  # end
1293
1544
  # "
@@ -1297,7 +1548,7 @@ Shale::Schema.from_xml([schema])
1297
1548
  You can also use a command line tool to do it:
1298
1549
 
1299
1550
  ```
1300
- $ shaleb -c -f xml -i schema.xml
1551
+ $ shaleb -c -f xml -i schema.xml -m http://bar.com=Api::Bar,=Api::Foo
1301
1552
  ```
1302
1553
 
1303
1554
  ## Contributing
data/exe/shaleb CHANGED
@@ -36,8 +36,8 @@ ARGV << '-h' if ARGV.empty?
36
36
  OptionParser.new do |opts|
37
37
  opts.banner = <<~BANNER
38
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
39
+ example generate schema from Shale model: shaleb -g -i data_model.rb -r MyRoot
40
+ example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -r MyRoot
41
41
  BANNER
42
42
 
43
43
  opts.on('-g', '--generate', 'generate schema from Shale model')
@@ -47,6 +47,7 @@ OptionParser.new do |opts|
47
47
  opts.on('-r ROOT', '--root ROOT', 'Shale model class name')
48
48
  opts.on('-f FORMAT', '--format FORMAT', 'Schema format: JSON (default), XML')
49
49
  opts.on('-p', '--pretty', 'Pretty print generated schema')
50
+ opts.on('-m MAPPING', '--mapping', Array, 'Namespace mapping')
50
51
 
51
52
  opts.on('-v', '--version', 'Show version') do
52
53
  puts "shaleb version #{Shale::VERSION}"
@@ -71,11 +72,24 @@ if params[:compile]
71
72
  end
72
73
  end
73
74
 
75
+ if params[:mapping]
76
+ namespace_mapping = params[:mapping]
77
+ .to_h { |e| [*e.split('='), nil][0, 2] }
78
+ .transform_keys { |key| key.empty? ? nil : key }
79
+ end
80
+
74
81
  if params[:format] == 'xml'
75
82
  load_xml_parser
76
- models = Shale::Schema.from_xml(schemas)
83
+ models = Shale::Schema.from_xml(
84
+ schemas,
85
+ namespace_mapping: namespace_mapping
86
+ )
77
87
  else
78
- models = Shale::Schema.from_json(schemas, root_name: params[:root])
88
+ models = Shale::Schema.from_json(
89
+ schemas,
90
+ root_name: params[:root],
91
+ namespace_mapping: namespace_mapping
92
+ )
79
93
  end
80
94
 
81
95
  if params[:output]
@@ -84,6 +98,7 @@ if params[:compile]
84
98
 
85
99
  models.each do |name, model|
86
100
  output_path = File.join(dir, "#{name}.rb")
101
+ FileUtils.mkdir_p(File.dirname(output_path))
87
102
  File.write(output_path, model)
88
103
  end
89
104
  else
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ module Shale
6
+ module Adapter
7
+ # CSV adapter
8
+ #
9
+ # @api public
10
+ class CSV
11
+ # Parse CSV into Array<Hash<String, any>>
12
+ #
13
+ # @param [String] csv CSV document
14
+ # @param [Array<String>] headers
15
+ # @param [Hash] options
16
+ #
17
+ # @return [Array<Hash<String, any>>]
18
+ #
19
+ # @api private
20
+ def self.load(csv, headers:, **options)
21
+ ::CSV.parse(csv, headers: headers, **options).map(&:to_h)
22
+ end
23
+
24
+ # Serialize Array<Hash<String, any>> into CSV
25
+ #
26
+ # @param [Array<Hash<String, any>>] obj Array<Hash<String, any>> object
27
+ # @param [Array<String>] headers
28
+ # @param [Hash] options
29
+ #
30
+ # @return [String]
31
+ #
32
+ # @api private
33
+ def self.dump(obj, headers:, **options)
34
+ ::CSV.generate(**options) do |csv|
35
+ obj.each do |row|
36
+ values = []
37
+
38
+ headers.each do |header|
39
+ values << row[header] if row.key?(header)
40
+ end
41
+
42
+ csv << values
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end