@@ -20,6 +20,7 @@ import (
20
20
"bytes"
21
21
"crypto/md5"
22
22
"encoding/base64"
23
+ "encoding/csv"
23
24
"encoding/hex"
24
25
"encoding/json"
25
26
"fmt"
@@ -1425,6 +1426,170 @@ func builtinParseYAML(i *interpreter, str value) (value, error) {
1425
1426
return jsonToValue (i , elems [0 ])
1426
1427
}
1427
1428
1429
+ func builtinParseCSVWithHeader (i * interpreter , arguments []value ) (value , error ) {
1430
+ strv := arguments [0 ]
1431
+ dv := arguments [1 ]
1432
+
1433
+ sval , err := i .getString (strv )
1434
+ if err != nil {
1435
+ return nil , err
1436
+ }
1437
+ s := sval .getGoString ()
1438
+
1439
+ d := ',' // default delimiter
1440
+ if dv .getType () != nullType {
1441
+ dval , err := i .getString (dv )
1442
+ if err != nil {
1443
+ return nil , err
1444
+ }
1445
+ ds := dval .getGoString ()
1446
+ if len (ds ) != 1 {
1447
+ return nil , i .Error (fmt .Sprintf ("Delimiter %s is invalid" , ds ))
1448
+ }
1449
+ d = rune (ds [0 ]) // conversion to rune
1450
+ }
1451
+
1452
+ json := make ([]interface {}, 0 )
1453
+ var keys []string
1454
+
1455
+ reader := csv .NewReader (strings .NewReader (s ))
1456
+ reader .Comma = d
1457
+
1458
+ for row := 0 ; ; row ++ {
1459
+ record , err := reader .Read ()
1460
+ if err == io .EOF {
1461
+ break
1462
+ }
1463
+ if err != nil {
1464
+ return nil , i .Error (fmt .Sprintf ("failed to parse CSV: %s" , err .Error ()))
1465
+ }
1466
+
1467
+ if row == 0 { // consider first row as header
1468
+ // detect and handle duplicate headers
1469
+ keyCount := map [string ]int {}
1470
+ for _ , k := range record {
1471
+ keyCount [k ]++
1472
+ if c := keyCount [k ]; c > 1 {
1473
+ keys = append (keys , fmt .Sprintf ("%s__%d" , k , c - 1 ))
1474
+ } else {
1475
+ keys = append (keys , k )
1476
+ }
1477
+ }
1478
+ } else {
1479
+ j := make (map [string ]interface {})
1480
+ for i , k := range keys {
1481
+ j [k ] = record [i ]
1482
+ }
1483
+ json = append (json , j )
1484
+ }
1485
+ }
1486
+ return jsonToValue (i , json )
1487
+ }
1488
+
1489
+ func builtinManifestCsv (i * interpreter , arguments []value ) (value , error ) {
1490
+ arrv := arguments [0 ]
1491
+ hv := arguments [1 ]
1492
+
1493
+ arr , err := i .getArray (arrv )
1494
+ if err != nil {
1495
+ return nil , err
1496
+ }
1497
+
1498
+ var headers []string
1499
+ if hv .getType () == nullType {
1500
+ if len (arr .elements ) == 0 { // no elements to select headers
1501
+ return makeValueString ("" ), nil
1502
+ }
1503
+
1504
+ // default to all headers
1505
+ obj , err := i .evaluateObject (arr .elements [0 ])
1506
+ if err != nil {
1507
+ return nil , err
1508
+ }
1509
+
1510
+ simpleObj := obj .uncached .(* simpleObject )
1511
+ for fieldName := range simpleObj .fields {
1512
+ headers = append (headers , fieldName )
1513
+ }
1514
+ } else {
1515
+ // headers are provided
1516
+ ha , err := i .getArray (hv )
1517
+ if err != nil {
1518
+ return nil , err
1519
+ }
1520
+
1521
+ for _ , elem := range ha .elements {
1522
+ header , err := i .evaluateString (elem )
1523
+ if err != nil {
1524
+ return nil , err
1525
+ }
1526
+ headers = append (headers , header .getGoString ())
1527
+ }
1528
+ }
1529
+
1530
+ var buf bytes.Buffer
1531
+ w := csv .NewWriter (& buf )
1532
+
1533
+ // Write headers
1534
+ w .Write (headers )
1535
+
1536
+ // Write rest of the rows
1537
+ for _ , elem := range arr .elements {
1538
+ obj , err := i .evaluateObject (elem )
1539
+ if err != nil {
1540
+ return nil , err
1541
+ }
1542
+
1543
+ record := make ([]string , len (headers ))
1544
+ for c , h := range headers {
1545
+ val , err := obj .index (i , h )
1546
+ if err != nil { // no corresponding column
1547
+ // skip to next column
1548
+ continue
1549
+ }
1550
+
1551
+ s , err := stringFromValue (i , val )
1552
+ if err != nil {
1553
+ return nil , err
1554
+ }
1555
+ record [c ] = s
1556
+ }
1557
+ w .Write (record )
1558
+ }
1559
+
1560
+ w .Flush ()
1561
+
1562
+ return makeValueString (buf .String ()), nil
1563
+ }
1564
+
1565
+ func stringFromValue (i * interpreter , v value ) (string , error ) {
1566
+ switch v .getType () {
1567
+ case stringType :
1568
+ s , err := i .getString (v )
1569
+ if err != nil {
1570
+ return "" , err
1571
+ }
1572
+ return s .getGoString (), nil
1573
+ case numberType :
1574
+ n , err := i .getNumber (v )
1575
+ if err != nil {
1576
+ return "" , err
1577
+ }
1578
+ return fmt .Sprint (n .value ), nil
1579
+ case booleanType :
1580
+ b , err := i .getBoolean (v )
1581
+ if err != nil {
1582
+ return "" , err
1583
+ }
1584
+ return fmt .Sprint (b .value ), nil
1585
+ case nullType :
1586
+ return "" , nil
1587
+ default :
1588
+ // for functionType, objectType and arrayType
1589
+ return "" , i .Error ("invalid string conversion" )
1590
+ }
1591
+ }
1592
+
1428
1593
func jsonEncode (v interface {}) (string , error ) {
1429
1594
buf := new (bytes.Buffer )
1430
1595
enc := json .NewEncoder (buf )
@@ -2290,6 +2455,8 @@ var funcBuiltins = buildBuiltinMap([]builtin{
2290
2455
& unaryBuiltin {name : "parseInt" , function : builtinParseInt , params : ast.Identifiers {"str" }},
2291
2456
& unaryBuiltin {name : "parseJson" , function : builtinParseJSON , params : ast.Identifiers {"str" }},
2292
2457
& unaryBuiltin {name : "parseYaml" , function : builtinParseYAML , params : ast.Identifiers {"str" }},
2458
+ & generalBuiltin {name : "parseCsvWithHeader" , function : builtinParseCSVWithHeader , params : []generalBuiltinParameter {{name : "str" }, {name : "delimiter" , defaultValue : & nullValue }}},
2459
+ & generalBuiltin {name : "manifestCsv" , function : builtinManifestCsv , params : []generalBuiltinParameter {{name : "json" }, {name : "headers" , defaultValue : & nullValue }}},
2293
2460
& generalBuiltin {name : "manifestJsonEx" , function : builtinManifestJSONEx , params : []generalBuiltinParameter {{name : "value" }, {name : "indent" },
2294
2461
{name : "newline" , defaultValue : & valueFlatString {value : []rune ("\n " )}},
2295
2462
{name : "key_val_sep" , defaultValue : & valueFlatString {value : []rune (": " )}}}},
0 commit comments