As its name suggests, psv uses the pipe character (|) to
separate columns of data.
So, how can pipes be used in a table's data?
There are two (and a half) solutions to this problem:
Solution #1: Use Escape Sequences
psv understands the following
escape sequences.
| Escape Sequence | Description |
| \t | a tab character (ASCII/Unicode 0x09) |
| \n | a newline character (ASCII/Unicode 0x0a) |
| \| | a tab character (ASCII/Unicode 0x7c) |
| \\ | a tab character (ASCII/Unicode 0x5c) |
So if you just need the occasional | in your tables, just put a
\ in front of it. Done.
Example:
| Escape Sequence | Description |
| --------------- | ---------------------------------------- |
| \t | a tab character (ASCII/Unicode 0x09) |
| \n | a newline character (ASCII/Unicode 0x0a) |
| \| | a tab character (ASCII/Unicode 0x7c) |
| \\ | a tab character (ASCII/Unicode 0x5c) |
Solution #2: Use Your Own Encoding
Instead of bending psv to fit you data, you can also bend your data to
fit psv.
This is not necessarily a pretty solution, but it is very simple,
works suprisingly well and can be self documenting.
The idea is to find any sequence you like to represent otherwise
problematic data (e.g. invisible characters) and replace those sequences when
you use the data.
Here is an excerpt of one of the unit tests used to verify the ruler parser.
// The following mappings are used here:
// I represents a pipe character
// {tab} represents a tab character
// {nbsp} represents a non-breaking space character
testCases := psv.NewTable().FromString(`
| input || ruler |
| ---------- || --------- |
| I{tab}--- || I -I |
| I{nbsp}--- || I{nbsp}-I |
| ---------- || --------- |
| I-^ || I--I |
| I-v || I--I |
| I-v^ || I--I |
| I-^v || I--I |
| I^-v || I--I |
| I-^IvII^ || I--I |
`)
// map encoded values back to their intended equivalents
unpack := func(in string) string {
in = strings.ReplaceAll(in, "I", "|" ) // pipe (not handled by is.TableFromString)
in = strings.ReplaceAll(in, "{tab}", "\t" ) // tab
in = strings.ReplaceAll(in, "{nbsp}", "\xa0") // non-breaking space
return in
}
for _, tc := range testCases.DataRows() {
var (
givenInput = unpack(tc.Field("input")) // givenInput gets actual pipe and tab characters etc.
wantRulerString = unpack(tc.Field("ruler"))
)
...
}
And, what about Quotes?
psv does not support quoting, as doing so would
require that user's always
check that they have a matching closing quote for every opening quote. This
becomes a real mess when people start using data that contain's apostrophes
(i.e., a single, single-quote)
By not automatically quoting / unquoting data,
psv allows the natural use of quotes as data without any special
treatment.
| Quote | Author |
| ------------------------- | ------ |
| this is a dog's breakfast | anon |
Solution #2-and-a-half: But You Can Have Quotes When YOU Want Them
Once caveat to the previous section is that you can add quotes to your data,
which psv while happily ignore, and then use
psv.Unquote() to remove the quotes only where you need to!
psv.Unquote() is a bit special, in that it only removes
matching pairs of quotes, and you must specify excactly which quotes
you wish to have removed. All other inputs are returned unmodified!
This is very similar to the Use Your Own Encoding
solution described above.
Do note, however, that surrounding a pipe character with quotes
will not prevent psv from using the pipe as a column
separator.
Only \| will allow an actual pipe character to be used
directly as data!
For example:
// The following mappings are used here:
// - '!' is mapped to '\' for escape sequences
// - '{S}' is mapped to ' ' (space) for escape sequences
// - quotes are removed explicitly via psv.Unquote()
testCases := psv.NewTable().FromString(`
| input | quotes || unquoted | notes |
| ------- | ------ || -------- | ----------------------------------------- |
| | || | empty string |
| ' | ' || ' | un-paired single quotes - no change |
| ' | " || ' | |
| " | " || " | |
| !" | " || !" | |
| ------- | ------ || -------- | ----------------------------------------- |
| '{S}' | ' || {S} | paired quotes surrounding whitespace |
| "{S}" | " || {S} | |
| ------- | ------ || -------- | ----------------------------------------- |
| '' | ' || | paired quotes surrounding embedded quotes |
| '"' | ' || " | |
| ''"'' | ' || '"' | |
| "" | " || | |
| "'" | " || ' | |
| ""'"" | " || "'" | |
| ------- | ------ || -------- | ----------------------------------------- |
| "~' | " || "~' | mis-matched pairs of quotes - no change |
| '~" | " || '~" | |
| '"~'" | " || '"~'" | |
| ~"foo"~ | " || ~"foo"~ | embedded quotes - no change |
`)
decode := func(in string) string {
in = psv.Replace(in, "!", "\\" )
in = psv.Replace(in, "{S}", " " )
return in
}
for _, tc := range testCases.DataRows() {
var (
givenInput = decode(tc.field("input")) // remove quotes
givenQuotes = tc.field("quotes") // do not remove quotes
wantUnquoted = decode(tc.field("unquoted")) // do not remove quotes
)
gotOutput := psv.Unquote(givenInput, givenQuotes) // only remove specific quotes
...
}