diff --git a/app/api/[owner]/[repo]/[branch]/entries/[path]/route.ts b/app/api/[owner]/[repo]/[branch]/entries/[path]/route.ts index 0a1e318bf..bfd89f255 100644 --- a/app/api/[owner]/[repo]/[branch]/entries/[path]/route.ts +++ b/app/api/[owner]/[repo]/[branch]/entries/[path]/route.ts @@ -96,8 +96,7 @@ const parseContent = ( schema: Record, config: Record ) => { - const serializedTypes = ["yaml-frontmatter", "json-frontmatter", "toml-frontmatter", "yaml", "json", "toml"]; - + const serializedTypes = ["yaml-frontmatter", "json-frontmatter", "toml-frontmatter", "yaml", "json", "toml", "datagrid", "csv"]; let contentObject: Record = {}; if (serializedTypes.includes(schema && schema.format) && schema.fields && schema.fields.length > 0) { diff --git a/lib/configSchema.ts b/lib/configSchema.ts index 971e25e71..9be3481e8 100644 --- a/lib/configSchema.ts +++ b/lib/configSchema.ts @@ -177,9 +177,9 @@ const ContentObjectSchema = z.object({ }).strict().optional().nullable(), format: z.enum([ "yaml-frontmatter", "json-frontmatter", "toml-frontmatter", - "yaml", "json", "toml", "datagrid", "code", "raw" + "yaml", "json", "toml", "datagrid", "csv", "code", "raw" ], { - message: "'format' must be 'yaml-frontmatter', 'json-frontmatter', 'tom-frontmatter', 'yaml', 'json', 'toml', 'datagrid', 'code' or 'raw'." + message: "'format' must be 'yaml-frontmatter', 'json-frontmatter', 'tom-frontmatter', 'yaml', 'json', 'toml', 'datagrid', 'csv', 'code' or 'raw'." }).optional().nullable(), delimiters: z.union([ z.array(z.string({ diff --git a/lib/serialization.ts b/lib/serialization.ts index 0f3f7e2c1..065408d82 100644 --- a/lib/serialization.ts +++ b/lib/serialization.ts @@ -4,18 +4,20 @@ import YAML from "yaml"; import * as TOML from "@ltd/j-toml" +import * as CSVS from "csv-stringify/sync"; +import * as CSVP from "csv-parse/sync"; type FrontmatterFormat = "json-frontmatter" | "yaml-frontmatter" | "toml-frontmatter"; -type SerialFormat = "json" | "yaml" | "toml"; +type SerialFormat = "json" | "yaml" | "toml" | "csv"; type Format = FrontmatterFormat | SerialFormat; // Parse straight YAML/JSON/TOML and YAML/JSON/TOML frontmatter strings into an object const parse = (content: string = "", options: { delimiters?: string, format?: Format } = {}) => { const format = options.format || "yaml-frontmatter"; - // YAML/JSON/TOML without frontmatter if (["yaml", "json", "toml"].includes(format)) return deserialize(content, format as SerialFormat); - + if (["datagrid", "csv"].includes(format)) return deserialize(content, "csv" as SerialFormat); + const delimiters = setDelimiter(options.delimiters, format as FrontmatterFormat); const startDelimiter = delimiters[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const endDelimiter = delimiters[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -51,6 +53,18 @@ const deserialize = (content: string = "", format: SerialFormat = "yaml") => { switch (format) { case "yaml": return YAML.parse(content, { strict: false, uniqueKeys: false }); + case "csv": + // Deserialise lists (and objects?). Ideally not naively (ie: using the schema). + return CSVP.parse(content, { + columns: true, relax_quotes: true, + cast: function (value, context) { + try { + return JSON.parse(value) + } catch (err) { + return value + } + }, + }); case "json": return JSON.parse(content); case "toml": @@ -64,9 +78,10 @@ const deserialize = (content: string = "", format: SerialFormat = "yaml") => { // Convert an object into straight YAML/JSON/TOML or YAML/JSON/TOML frontmatter strings const stringify = (contentObject: Record = {}, options: { delimiters?: string, format?: Format } = {}) => { const format = options.format || "yaml-frontmatter"; - + // YAML/JSON/TOML without frontmatter if (["yaml", "json", "toml"].includes(format)) return serialize(contentObject, format as SerialFormat); + if (["datagrid", "csv"].includes(format)) return serialize(contentObject, "csv" as SerialFormat); // Frontmatter const delimiters = setDelimiter(options.delimiters, format as FrontmatterFormat); @@ -96,6 +111,8 @@ const serialize = (contentObject: Record = {}, format: SerialFormat return JSON.stringify(contentObject, null, 2); case "toml": return TOML.stringify(contentObject, { newline: "\n"}); + case "csv": + return CSVS.stringify(contentObject as any[], { header: true }); default: return ""; } diff --git a/lib/utils/file.ts b/lib/utils/file.ts index 26e25e6f7..59ae45eb1 100644 --- a/lib/utils/file.ts +++ b/lib/utils/file.ts @@ -2,7 +2,7 @@ * Define file types and provide Helper functions (get file info, get parent path, normalize paths...) */ -const serializedTypes = ["yaml-frontmatter", "json-frontmatter", "toml-frontmatter", "yaml", "json", "toml"]; +const serializedTypes = ["yaml-frontmatter", "json-frontmatter", "toml-frontmatter", "yaml", "json", "toml", "datagrid", "csv"]; const extensionCategories: Record = { image: ["jpg", "jpeg", "apng", "png", "gif", "svg", "ico", "avif", "bmp", "tif", "tiff", "webp"], @@ -101,4 +101,4 @@ export { sortFiles, extensionCategories, serializedTypes -}; \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index 194688d7a..b25b77b49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "csv-parse": "^5.6.0", + "csv-stringify": "^6.5.2", "date-fns": "^3.6.0", "drizzle-orm": "^0.31.2", "install": "^0.13.0", @@ -6145,6 +6147,16 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/csv-parse": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz", + "integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==" + }, + "node_modules/csv-stringify": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.2.tgz", + "integrity": "sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", diff --git a/package.json b/package.json index e6bafba67..9c951fe08 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "csv-parse": "^5.6.0", + "csv-stringify": "^6.5.2", "date-fns": "^3.6.0", "drizzle-orm": "^0.31.2", "install": "^0.13.0",