Skip to content

Commit 75c204e

Browse files
Allow setting nil during encode (#47)
- Updated to allow setting nil during encode - Updated CI/CD to use hererocks latest
1 parent e6324c4 commit 75c204e

File tree

5 files changed

+196
-96
lines changed

5 files changed

+196
-96
lines changed

.github/workflows/test-and-coverage.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,27 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
luaVersion: [ "5.4", "5.3", "5.2", "5.1", "luajit", "luajit-openresty" ]
15+
lua: [lua=5.1, lua=5.2, lua=5.3, lua=5.4, luajit[email protected], luajit[email protected]]
1616
steps:
1717
- uses: actions/checkout@master
1818

19-
- name: Setup ‘lua’
20-
uses: leafo/gh-actions-lua@v9
21-
with:
22-
luaVersion: ${{ matrix.luaVersion }}
23-
- name: Setup ‘luarocks’
24-
uses: leafo/gh-actions-luarocks@v4
19+
- name: Install libreadline
20+
run: sudo apt-get install -y libreadline-dev
21+
22+
- name: Install Lua (${{ matrix.lua }})
23+
run: |
24+
pip install git+https://github.com/luarocks/hererocks
25+
hererocks -r^ --${{ matrix.lua }} lua_install
26+
echo lua_install/bin >> $GITHUB_PATH
2527
26-
- name: install depedencies
28+
- name: Install depedencies
2729
run: |
2830
luarocks install busted
2931
luarocks install lua-cjson
3032
luarocks install luacov
3133
luarocks install luacov-coveralls
32-
3334
34-
- name: run unit tests with coverage
35+
- name: Run unit tests with coverage
3536
run: busted --verbose --coverage
3637

3738
- name: Report test coverage

README.md

Lines changed: 75 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -48,80 +48,80 @@ end
4848
The options are the same for `parseLine` and `parse`, with the exception of `loadFromString` and `bufferSize`. `loadFromString` only works with `parse` and `bufferSize` can only be specified for `parseLine`.
4949

5050
The following are optional parameters passed in via the third argument as a table.
51-
- `delimiter`
51+
- `delimiter`
5252

53-
If your file doesn't use the comma character as the delimiter, you can specify your own. It is limited to one character and defaults to `,`
54-
```lua
55-
ftcsv.parse("a>b>c\r\n1,2,3", {loadFromString=true, delimiter=">"})
56-
```
53+
If your file doesn't use the comma character as the delimiter, you can specify your own. It is limited to one character and defaults to `,`
54+
```lua
55+
ftcsv.parse("a>b>c\r\n1,2,3", {loadFromString=true, delimiter=">"})
56+
```
5757

58-
- `loadFromString`
58+
- `loadFromString`
5959

60-
If you want to load a csv from a string instead of a file, set `loadFromString` to `true` (default: `false`)
61-
```lua
62-
ftcsv.parse("a,b,c\r\n1,2,3", {loadFromString=true})
63-
```
60+
If you want to load a csv from a string instead of a file, set `loadFromString` to `true` (default: `false`)
61+
```lua
62+
ftcsv.parse("a,b,c\r\n1,2,3", {loadFromString=true})
63+
```
6464

65-
- `rename`
65+
- `rename`
6666

67-
If you want to rename a field, you can set `rename` to change the field names. The below example will change the headers from `a,b,c` to `d,e,f`
67+
If you want to rename a field, you can set `rename` to change the field names. The below example will change the headers from `a,b,c` to `d,e,f`
6868

69-
Note: You can rename two fields to the same value, ftcsv will keep the field that appears latest in the line.
69+
Note: You can rename two fields to the same value, ftcsv will keep the field that appears latest in the line.
7070

71-
```lua
72-
local options = {loadFromString=true, rename={["a"] = "d", ["b"] = "e", ["c"] = "f"}}
73-
local actual = ftcsv.parse("a,b,c\r\napple,banana,carrot", options)
74-
```
71+
```lua
72+
local options = {loadFromString=true, rename={["a"] = "d", ["b"] = "e", ["c"] = "f"}}
73+
local actual = ftcsv.parse("a,b,c\r\napple,banana,carrot", options)
74+
```
7575

76-
- `fieldsToKeep`
76+
- `fieldsToKeep`
7777

78-
If you only want to keep certain fields from the CSV, send them in as a table-list and it should parse a little faster and use less memory.
78+
If you only want to keep certain fields from the CSV, send them in as a table-list and it should parse a little faster and use less memory.
7979

80-
Note: If you want to keep a renamed field, put the new name of the field in `fieldsToKeep`:
80+
Note: If you want to keep a renamed field, put the new name of the field in `fieldsToKeep`:
8181

82-
```lua
83-
local options = {loadFromString=true, fieldsToKeep={"a","f"}, rename={["c"] = "f"}}
84-
local actual = ftcsv.parse("a,b,c\r\napple,banana,carrot\r\n", options)
85-
```
82+
```lua
83+
local options = {loadFromString=true, fieldsToKeep={"a","f"}, rename={["c"] = "f"}}
84+
local actual = ftcsv.parse("a,b,c\r\napple,banana,carrot\r\n", options)
85+
```
8686

87-
Also Note: If you apply a function to the headers via headerFunc, and want to select fields from fieldsToKeep, you need to have what the post-modified header would be in fieldsToKeep.
87+
Also Note: If you apply a function to the headers via headerFunc, and want to select fields from fieldsToKeep, you need to have what the post-modified header would be in fieldsToKeep.
8888

89-
- `ignoreQuotes`
89+
- `ignoreQuotes`
9090

91-
If `ignoreQuotes` is `true`, it will leave all quotes in the final parsed output. This is useful in situations where the fields aren't quoted, but contain quotes, or if the CSV didn't handle quotes correctly and you're trying to parse it.
91+
If `ignoreQuotes` is `true`, it will leave all quotes in the final parsed output. This is useful in situations where the fields aren't quoted, but contain quotes, or if the CSV didn't handle quotes correctly and you're trying to parse it.
9292

93-
```lua
94-
local options = {loadFromString=true, ignoreQuotes=true}
95-
local actual = ftcsv.parse('a,b,c\n"apple,banana,carrot', options)
96-
```
93+
```lua
94+
local options = {loadFromString=true, ignoreQuotes=true}
95+
local actual = ftcsv.parse('a,b,c\n"apple,banana,carrot', options)
96+
```
9797

98-
- `headerFunc`
98+
- `headerFunc`
9999

100-
Applies a function to every field in the header. If you are using `rename`, the function is applied after the rename.
100+
Applies a function to every field in the header. If you are using `rename`, the function is applied after the rename.
101101

102-
Ex: making all fields uppercase
103-
```lua
104-
local options = {loadFromString=true, headerFunc=string.upper}
105-
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", options)
106-
```
102+
Ex: making all fields uppercase
103+
```lua
104+
local options = {loadFromString=true, headerFunc=string.upper}
105+
local actual = ftcsv.parse("a,b,c\napple,banana,carrot", options)
106+
```
107107

108-
- `headers`
108+
- `headers`
109109

110-
Set `headers` to `false` if the file you are reading doesn't have any headers. This will cause ftcsv to create indexed tables rather than a key-value tables for the output.
110+
Set `headers` to `false` if the file you are reading doesn't have any headers. This will cause ftcsv to create indexed tables rather than a key-value tables for the output.
111111

112-
```lua
113-
local options = {loadFromString=true, headers=false, delimiter=">"}
114-
local actual = ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", options)
115-
```
112+
```lua
113+
local options = {loadFromString=true, headers=false, delimiter=">"}
114+
local actual = ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", options)
115+
```
116116

117-
Note: Header-less files can still use the `rename` option and after a field has been renamed, it can specified as a field to keep. The `rename` syntax changes a little bit:
117+
Note: Header-less files can still use the `rename` option and after a field has been renamed, it can specified as a field to keep. The `rename` syntax changes a little bit:
118118

119-
```lua
120-
local options = {loadFromString=true, headers=false, rename={"a","b","c"}, fieldsToKeep={"a","b"}, delimiter=">"}
121-
local actual = ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", options)
122-
```
119+
```lua
120+
local options = {loadFromString=true, headers=false, rename={"a","b","c"}, fieldsToKeep={"a","b"}, delimiter=">"}
121+
local actual = ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", options)
122+
```
123123

124-
In the above example, the first field becomes 'a', the second field becomes 'b' and so on.
124+
In the above example, the first field becomes 'a', the second field becomes 'b' and so on.
125125

126126
For all tested examples, take a look in /spec/feature_spec.lua
127127

@@ -147,29 +147,39 @@ file:close()
147147
```
148148

149149
### Options
150-
- `delimiter`
150+
- `delimiter`
151151

152-
by default the encoder uses a `,` as a delimiter. The delimiter can be changed by setting a value for `delimiter`
152+
by default the encoder uses a `,` as a delimiter. The delimiter can be changed by setting a value for `delimiter`
153153

154-
```lua
155-
local output = ftcsv.encode(everyUser, {delimiter="\t"})
156-
```
154+
```lua
155+
local output = ftcsv.encode(everyUser, {delimiter="\t"})
156+
```
157+
158+
- `fieldsToKeep`
159+
160+
if `fieldsToKeep` is set in the encode process, only the fields specified will be written out to a file. The `fieldsToKeep` will be written out in the order that is specified.
161+
162+
```lua
163+
local output = ftcsv.encode(everyUser, {fieldsToKeep={"Name", "Phone", "City"}})
164+
```
165+
166+
- `onlyRequiredQuotes`
157167

158-
- `fieldsToKeep`
168+
if `onlyRequiredQuotes` is set to `true`, the output will only include quotes around fields that are quotes, have newlines, or contain the delimter.
159169

160-
if `fieldsToKeep` is set in the encode process, only the fields specified will be written out to a file. The `fieldsToKeep` will be written out in the order that is specified.
170+
```lua
171+
local output = ftcsv.encode(everyUser, {onlyRequiredQuotes=true})
172+
```
161173

162-
```lua
163-
local output = ftcsv.encode(everyUser, {fieldsToKeep={"Name", "Phone", "City"}})
164-
```
174+
- `encodeNilAs`
165175

166-
- `onlyRequiredQuotes`
176+
by default a `nil` value in a table will be encoded as the string `"nil"`. The value a `nil` value in the a table can be set with `encodeNilAs`.
167177

168-
if `onlyRequiredQuotes` is set to `true`, the output will only include quotes around fields that are quotes, have newlines, or contain the delimter.
178+
```lua
179+
local output = ftcsv.encode(everyUser, {encodeNilAs=""}) -- for setting nil to the empty string
180+
local output = ftcsv.encode(everyUser, {encodeNilAs=0}) -- for setting it to 0
181+
```
169182

170-
```lua
171-
local output = ftcsv.encode(everyUser, {onlyRequiredQuotes=true})
172-
```
173183

174184

175185
## Error Handling
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package = "ftcsv"
2-
version = "1.4.0-1"
2+
version = "1.5.0-1"
33

44
source = {
55
url = "git://github.com/FourierTransformer/ftcsv.git",
6-
tag = "1.4.0"
6+
tag = "1.5.0"
77
}
88

99
description = {

ftcsv.lua

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
local ftcsv = {
2-
_VERSION = 'ftcsv 1.4.0',
2+
_VERSION = 'ftcsv 1.5.0',
33
_DESCRIPTION = 'CSV library for Lua',
44
_URL = 'https://github.com/FourierTransformer/ftcsv',
55
_LICENSE = [[
66
The MIT License (MIT)
77
8-
Copyright (c) 2016-2023 Shakil Thakur
8+
Copyright (c) 2016-2025 Fourier Transformer
99
1010
Permission is hereby granted, free of charge, to any person obtaining a copy
1111
of this software and associated documentation files (the "Software"), to deal
@@ -658,19 +658,33 @@ end
658658
-- The ENCODER code is below here
659659
-- This could be broken out, but is kept here for portability
660660

661+
local function generateCustomToString(valueToConvertNilTo)
662+
local newReturnValue = tostring(valueToConvertNilTo)
663+
local generatedFunction = function(field)
664+
if type(field) == "nil" then
665+
return newReturnValue
666+
else
667+
return tostring(field)
668+
end
669+
end
670+
return generatedFunction
671+
end
661672

662-
local function delimitField(field)
663-
field = tostring(field)
664-
if field:find('"') then
665-
return field:gsub('"', '""')
666-
else
667-
return field
673+
local function generateDelimitField(customToString)
674+
local delimitField = function(field)
675+
field = customToString(field)
676+
if field:find('"') then
677+
return field:gsub('"', '""')
678+
else
679+
return field
680+
end
668681
end
682+
return delimitField
669683
end
670684

671-
local function generateDelimitAndQuoteField(delimiter)
685+
local function generateDelimitAndQuoteField(delimiter, customToString)
672686
local generatedFunction = function(field)
673-
field = tostring(field)
687+
field = customToString(field)
674688
if field:find('"') then
675689
return '"' .. field:gsub('"', '""') .. '"'
676690
elseif field:find('[\n' .. delimiter .. ']') then
@@ -722,10 +736,15 @@ local function csvLineGenerator(inputTable, delimiter, headers, options)
722736
arguments.t = inputTable
723737
-- we want to use the same delimitField throughout,
724738
-- so we're just going to pass it in
739+
740+
local toStringToUse = tostring
741+
if options and options.encodeNilAs ~= nil then
742+
toStringToUse = generateCustomToString(options.encodeNilAs)
743+
end
725744
if options and options.onlyRequiredQuotes == true then
726-
arguments.delimitField = generateDelimitAndQuoteField(delimiter)
745+
arguments.delimitField = generateDelimitAndQuoteField(delimiter, toStringToUse)
727746
else
728-
arguments.delimitField = delimitField
747+
arguments.delimitField = generateDelimitField(toStringToUse)
729748
end
730749

731750
return luaCompatibility.load(outputFunc), arguments, 0
@@ -752,9 +771,9 @@ end
752771

753772
local function escapeHeadersForOutput(headers, delimiter, options)
754773
local escapedHeaders = {}
755-
local delimitField = delimitField
774+
local delimitField = generateDelimitField(tostring)
756775
if options and options.onlyRequiredQuotes == true then
757-
delimitField = generateDelimitAndQuoteField(delimiter)
776+
delimitField = generateDelimitAndQuoteField(delimiter, tostring)
758777
end
759778
for i = 1, #headers do
760779
escapedHeaders[i] = delimitField(headers[i])

0 commit comments

Comments
 (0)