-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcsv.janet
96 lines (85 loc) · 2.37 KB
/
csv.janet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# Copyright (c) 2022 Lorenzo Giuliani
#
# Licensed under the ISC license: https://opensource.org/licenses/ISC
# Permission is granted to use, copy, modify, and redistribute the work.
# Full license information available in the project LICENSE file.
(def csv-lang
(peg/compile
'{:comma ","
:space " "
:space? (any :space)
:comma? (at-most 1 :comma)
:cr "\r"
:lf "\n"
:nl (+ (* :cr :lf)
:cr :lf)
:dquote "\""
:dquote? (? "\"")
:d_dquote (* :dquote :dquote)
:textdata (+ (<- (some (if-not (+ :dquote :comma :nl) 1)))
(* :dquote
(<- (any (+ (if :d_dquote 2)
(if-not :dquote 1))))
:dquote))
:empty_field 0
:field (accumulate (+ (* :space? :textdata :space?)
:empty_field))
:row (* :field
(any (* :comma :field))
(+ :nl 0))
:main (some (group :row))}))
(defn- unescape-field [field]
(string/replace-all "\"\"" "\"" field))
(defn- unescape-row [row]
(map unescape-field row))
(defn- parse-and-clean [data]
(->> data
(peg/match csv-lang)
(map unescape-row)))
(defn- headerize [ary]
(let [header (map keyword (first ary))
data (array/slice ary 1)]
(map (fn [row] (zipcoll header row))
data)))
(defn parse [input &opt header]
(let [data (parse-and-clean input)]
(if header
(headerize data)
data)))
(defn- field-to-csv
[field]
"escape strings for csv"
(if (and (not= nil field)
(or (string/find "\"" field)
(string/find "\n" field)
(string/find " " field)))
(->> (string/replace-all "\"" "\"\"" field)
(string/format "\"%s\""))
(if (= nil field)
""
field)))
(defn- is-list?
[data]
(or (array? data)
(struct? data)))
(defn- row-to-csv
[row]
(let [data (if (not (is-list? row))
(values row)
row)]
(map field-to-csv
data)))
(defn- to-array-of-array
[data]
(let [ary @[]]
(when (not (is-list? (first data)))
(array/push ary (-> (first data)
keys)))
(each row data
(array/push ary (row-to-csv row)))
ary))
(defn to-string
[data]
(string/join (map (fn [row] (string/join row ","))
(to-array-of-array data))
"\r\n"))