Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

tapioca

tapioca is a package that builds on cassava, to provide a simpler, more succinct method of encoding and decoding CSV's with headers.

Why?

Let's say we have a list of data MyRecord which we want to encode and decode to and from a CSV file:

dataMyRecord=MyRecord{field1::Int
, field2::String}myRecords:: [a]
myRecords =..

Here is how it might be done in cassava:

importData.CsvinstanceToNamedRecordMyRecordwhere
toNamedRecord (MyRecord field1 field2)= namedRecord
[ "Header for Field 1".= field1
, "Header for Field 2".= field2
]
instanceDefaultOrderedMyRecordwhere
headerOrder _ =
[ "Header for Field 1"
, "Header for Field 2"
]
instanceFromNamedRecordMyRecordwhere
parseNamedRecord m =MyRecord<$> m .:"Header for Field 1"<*> m .:"Header for Field 2"-- Example usagetoCSV::ByteString
toCSV = encodeDefaultOrderedByName myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = (snd<$>) . decodeByName

While serviceable, the need to define headers twice is less than ideal, resulting in code that is bulkier and more fragile.

Here's how we do it in tapioca:

importData.TapiocainstanceCsvMappedMyRecordwhere
csvMap =CsvMap$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
-- Example usagemyCSV::ByteString
myCSV = encode WithHeader myRecords
fromCSV::ByteString->EitherString (VectorMyRecord)
fromCSV = decode WithHeader

We see here that tapioca provides us with a more succinct definition for defining bidrectional CSV mappings, avoiding any unnecessary duplication, and keeping the entire definition within a single typeclass.

Usage

Bidirectional mappings

As seen earlier, the key part of using Tapioca to create a bidirectional mapping is to define an instance of CsvMapped EncodeDecode for your type, using the mkCsvMap function. This instance will only be allowed if no encode or decode-only functions are used in your mapping.

instanceCsvMappedEncodeDecodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".->#field3

Encode-only mappings

Encode-only mappings allow for more encoding options than bidrectional mappings, as there is no concern for parsing a csv back into the record.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".->#field1
:|"Header for Field 2".->#field2
:|"Header for Field 3".-> to (\record -> foo record)

Mapping selectors

Fields can be mapped on top of cassava's FromField and ToField instances on a per-field basis. If you wish to map a bidirectional field, use codec together with encoding and decoding mapping functions. If you wish to map an encode-only field, use encoder together with an encoding function. If you wish to map a decode-only field, use decoder together with a decoding function.

instanceCsvMappedEncodeMyRecordwhere
csvMap = mkCsvMap
$"Header for Field 1".-> codec toOrdinal fromOrdinal #field1
:|"Header for Field 2".-> encoder toOrdinal #field2 -- Can no longer have a bidirectional mapping

Refer to the EncodeOnly and DecodeWith examples to see this in practice.

Nesting maps

Occasionally you may want to nest a record within another record. Provided that both your records implement CsvMapped, this can be done by using the Nest constructor:

dataNestingRecord=NestingRecord{exampleRecord::ExampleRecord
, other::Int}deriving (Show, Generic)
instanceCsvMappedEncodeDecodeNestingRecordwhere
csvMap = mkCsvMap
$ nest #exampleRecord
:|"Other".->#other

Then in this example, for each row, the fields of ExampleRecord will precede the "Other" column field. Note that when decoding a spliced CSV with Headers, order of each field of ExampleRecord within the row is inferred from the order of the CSV headers. It is not required that the CSV's ExampleRecord columns are contiguous.

Refer to the NestedEncode and NestedDecode examples to see this in practice.

About

A tasty enhancement to cassava for easy csv exporting

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages