shale 0.8.0 → 1.0.0

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