├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── examples ├── csv │ ├── arquero-example.ts │ ├── csv-example.ts │ ├── flights.csv │ ├── flights2.csv │ ├── names.csv │ ├── prices-write.csv │ └── prices.csv ├── image │ ├── 41906373431_72c25d0dfd_b.jpg │ ├── cat-cropped.jpeg │ ├── cat.jpeg │ ├── giphy.gif │ └── image-example.ts ├── json │ ├── data.json │ ├── json-example.ts │ ├── json-indent-test.json │ ├── json-test.json │ └── post-data.json ├── sheets │ └── sheets-example.ts ├── txt │ ├── text-write.txt │ ├── text-write2.txt │ ├── text.txt │ └── txt-example.ts ├── xlsx │ ├── prices.csv │ ├── prices.xlsx │ └── xlsx-example.ts └── zip │ ├── zip-example.ts │ ├── zip-folder.zip │ └── zip-folder │ ├── flights.csv │ └── prices.csv ├── mod.ts ├── src ├── csv.ts ├── image.ts ├── json.ts ├── remove.ts ├── txt.ts ├── xlsx.ts └── zip.ts └── tests ├── csv-test.ts ├── image-test.ts ├── json-test.ts ├── remove-test.ts ├── txt-test.ts ├── xlsx-test.ts └── zip-test.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 GitHub OCTO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flat Data: Postprocessing Library and Examples 2 | 3 | A collection of postprocessing [helper functions](https://deno.land/x/flat/mod.ts) and examples for [Flat Data](https://octo.github.com/projects/flat-data). 4 | 5 | These examples and functions are written in [Deno](https://deno.land/), a new language created by the same founders of Node.js and meant to improve on many aspects of Node. 6 | 7 | > Note: If you're noticing your scripts failing recently, try updating to `0.0.15`. [More info here](https://github.com/githubocto/flat/issues/67#issuecomment-1025911772) 8 | 9 | ## Usage 10 | 11 | When writing a [Flat Data]() Action, you can specify a path to a postprocessing Deno script that can manipulate the data downloaded by Flat even further. 12 | 13 | ```yaml 14 | - name: Fetch data 15 | uses: githubocto/flat@v2 16 | with: 17 | http_url: http://api.coindesk.com/v2/bpi/currentprice.json # The endpoint to fetch 18 | downloaded_filename: btc-price.json # The http_url gets saved and renamed in our repository as btc-price.json 19 | postprocess: postprocess.ts # A postprocessing javascript or typescript file written in Deno 20 | ``` 21 | 22 | This is an example of a postprocessing script. Notice the use of `Deno.args[0]` to pass in the path of the `downloaded_filename`. 23 | 24 | ```ts 25 | // The Flat Data postprocessing libraries can be found at https://deno.land/x/flat/mod.ts 26 | // Replace 'x' with latest library version 27 | import { readJSON, writeJSON } from 'https://deno.land/x/flat@0.0.x/mod.ts' 28 | 29 | const filename = Deno.args[0] // equivalent to writing `const filename = 'btc-price.json'` 30 | const data = await readJSON(filename) 31 | 32 | // pluck a specific key off and write it out to a new file 33 | const newfile = `postprocessed_${filename}` 34 | await writeJSON(newfile, data.path.to.something) 35 | ``` 36 | 37 | ## Examples 38 | 39 | Can be found in the examples folder. Once you [install Deno](https://deno.land/) you can run these examples with: 40 | 41 | * `deno run -A examples/csv/csv-example.ts` 42 | * `deno run -A examples/csv/arquero-example.ts` 43 | * `deno run -A --unstable examples/image/image-example.ts` 44 | * `deno run -A examples/json/json-example.ts` 45 | * `deno run -A examples/sheets/sheets-example.ts` 46 | * `deno run -A examples/xlsx/xlsx-example.ts` 47 | * `deno run -A --unstable examples/zip/zip-example.ts` 48 | 49 | Deno can run javascript or typescript files, so you can easily convert any of these examples to javascript and run them in the same way: 50 | 51 | `deno run -A examples/csv/csv-example.js` 52 | 53 | ## Using Python 54 | 55 | While our examples use a Deno file to run postprocessing tasks, you can also use Python as specified in this example: [https://github.com/pierrotsmnrd/flat_data_py_example](https://github.com/pierrotsmnrd/flat_data_py_example). Thank you [@pierrotsmnrd](https://github.com/pierrotsmnrd)! 56 | 57 | ## Using bash 58 | 59 | You can also use bash as specified in this example: [https://github.com/aborruso/flat_data_bash_example](https://github.com/aborruso/flat_data_bash_example). By [@aborruso](https://twitter.com/aborruso) 60 | 61 | ## Postprocessing Library 62 | 63 | The Flat Data postprocessing library can be found at: [https://deno.land/x/flat/mod.ts](https://deno.land/x/flat/mod.ts) 64 | 65 | You can import and use these helper functions directly, or treat them as a starting point for writing your own postprocessing scripts. 66 | 67 | ### CSV 68 | 69 | #### readCSV 70 | 71 | ```ts 72 | readCSV(path: string, options?: ParseOptions): Promise[]> 73 | ``` 74 | 75 | Args: 76 | 77 | * **path:** path to a local CSV file 78 | * **options:** [options](https://deno.land/std@0.92.0/encoding#codeparseoptionscode) for parsing the CSV file 79 | 80 | Usage: 81 | 82 | `const csv = await readCSV('./path/to/file.csv')` 83 | 84 | #### writeCSV 85 | 86 | ```ts 87 | writeCSV(path: string, data: Record[] | string, options?: Deno.WriteFileOptions) 88 | ``` 89 | 90 | Args: 91 | 92 | * **path**: path to a local CSV file 93 | * **data**: string or object array to store 94 | * **options**: [options](https://doc.deno.land/builtin/stable#Deno.WriteFileOptions) for writing the CSV file 95 | 96 | Usage: 97 | 98 | ```ts 99 | const data = [ 100 | { age: 70, name: 'Rick' }, 101 | { age: 14, name: 'Smith' } 102 | ] 103 | await writeCSV('./path/to/file.csv', data) 104 | ``` 105 | 106 | ### TXT 107 | 108 | #### readTXT 109 | 110 | ```ts 111 | readTXT(path: string): string 112 | ``` 113 | 114 | Args: 115 | 116 | * **path**: path to a local TXT file 117 | 118 | 119 | Usage: 120 | 121 | ```ts 122 | const text = await readTXT('./path/to/file.txt') 123 | ``` 124 | 125 | #### writeTXT 126 | 127 | ```ts 128 | writeTXT(path: string, text: string, options?: Deno.WriteFileOptions): void 129 | ``` 130 | 131 | Args: 132 | 133 | * **path**: path to a local TXT file 134 | * **text**: text to write to file 135 | * **options**: [options](https://doc.deno.land/builtin/stable#Deno.WriteFileOptions) for writing the TXT file 136 | 137 | Usage: 138 | 139 | ```ts 140 | await writeTXT('./path/to/file.txt', 'Content for the file') 141 | ``` 142 | 143 | ### JSON 144 | 145 | 146 | #### readJSON 147 | 148 | ```ts 149 | readJSON(path: string): JSON 150 | ``` 151 | 152 | Args: 153 | 154 | * **path**: path to a local JSON file 155 | 156 | 157 | Usage: 158 | 159 | ```ts 160 | const json = await readJSON('./path/to/file.json') 161 | ``` 162 | 163 | #### readJSONFromURL 164 | 165 | ```ts 166 | readJSONFromURL(url: string): JSON 167 | ``` 168 | 169 | Args: 170 | 171 | * **url**: URL to a json file 172 | 173 | 174 | Usage: 175 | 176 | ```ts 177 | const json = await readJSON('www.url.com/file.json') 178 | ``` 179 | 180 | #### writeJSON 181 | 182 | ```ts 183 | writeJSON(path: string, data: any, replacer?: any, space?: string | number): void 184 | 185 | 186 | ``` 187 | 188 | Args: 189 | 190 | * **path**: path to a local JSON file 191 | * **data**: data to store as JSON 192 | * **replacer**: [replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#parameters) function that transforms the results or an array of strings and numbers that acts as an approved list for selecting the object properties that will be stringified 193 | * **space**: adds indentation, white space, and line break characters to the to the JSON text to make it easier to read. 194 | 195 | 196 | Usage: 197 | 198 | ```ts 199 | const data = { age: 40 } 200 | await writeJSON('./path/to/file.json', data) 201 | 202 | await writeJSON('./path/to/file-with-indentation', data, null, 2) 203 | ``` 204 | 205 | ### XLSX 206 | 207 | Our library relies on [SheetJS](https://github.com/SheetJS/sheetjs), a library for parsing various spreadsheet formats. In addition to a simple `readXLSX` function you can access the core `xlsx` module by importing it directly. 208 | 209 | ```ts 210 | import { xlsx, readXLSX } from 'https://deno.land/x/flat/mod.ts' 211 | ``` 212 | 213 | `xlsx` provides many more [utility functions](https://github.com/SheetJS/sheetjs). 214 | 215 | 216 | #### readXLSX 217 | 218 | ```ts 219 | readXLSX(path: string): XLSX.WorkBook 220 | ``` 221 | 222 | Args: 223 | 224 | * **path**: path to a local XLSX file 225 | 226 | 227 | Usage: 228 | 229 | ```ts 230 | const workbook = await readXLSX('./path/to/file.xlsx') 231 | const sheetData = workbook.Sheets[workbook.SheetNames[0]] 232 | const csvString = await xlsx.utils.sheet_to_csv(sheetData) 233 | ``` 234 | 235 | 236 | ### Image 237 | 238 | We recommend using a library like [imagescript](https://deno.land/x/imagescript) for more advanced image manipulation. See an example [here](https://github.com/githubocto/flat-postprocessing/blob/main/examples/image/image-example.ts). 239 | 240 | #### readImageFromFile 241 | 242 | ```ts 243 | readImageFromFile(path: string): Promise 244 | ``` 245 | 246 | Args: 247 | 248 | * **path**: path to a local image file 249 | 250 | 251 | Usage: 252 | 253 | ```ts 254 | const bytes = await readImageFromFile('./path/to/image.jpeg') 255 | ``` 256 | 257 | 258 | #### readImageFromURL 259 | 260 | ```ts 261 | readImageFromURL(url: string): Promise<{ bytes: Uint8Array; name: string; }> 262 | ``` 263 | 264 | Args: 265 | 266 | * **url**: url string to an image 267 | 268 | Usage: 269 | 270 | ```ts 271 | const image = await readImageFromURL('www.url.com/image.jpg') 272 | const bytes = image.bytes 273 | const name = image.name 274 | ``` 275 | 276 | #### writeImage 277 | 278 | ```ts 279 | writeImage(imageBytes: Uint8Array, path: string): void 280 | ``` 281 | 282 | Args: 283 | 284 | * **imageBytes**: a byte array 285 | * **path**: path and name to write the image file 286 | 287 | 288 | Usage: 289 | 290 | ```ts 291 | await writeImage(bytes, './path/to/image.jpeg') 292 | ``` 293 | 294 | ### Zip 295 | 296 | #### unZipFromFile 297 | 298 | ```ts 299 | unZipFromFile( 300 | filePath: string, 301 | destinationPath: string | null = "./", 302 | options: any = {}, 303 | ): Promise 304 | ``` 305 | 306 | Args: 307 | 308 | * **filePath**: a path to a local zip file 309 | * **destinationPath**: a folder path to unzip the files 310 | * **options**: option.includeFileName can be true or false 311 | 312 | 313 | Usage: 314 | 315 | ```ts 316 | const result = await unZipFromFile('./path/to/folder.zip', './unzip/path') 317 | const output = result ? 'File unzipped successfully' : 'Error unzipping' 318 | ``` 319 | 320 | #### unZipFromURL 321 | ```ts 322 | unZipFromURL( 323 | fileURL: string, 324 | destinationPath: string | null = "./", 325 | options: any = {}, 326 | ): Promise 327 | ``` 328 | 329 | Args: 330 | 331 | * **filePath**: a path to a local zip file 332 | * **destinationPath**: a folder path to unzip the files 333 | * **options**: option.includeFileName can be true or false 334 | 335 | Usage: 336 | 337 | ```ts 338 | const result = await unZipFromURL('www.url.com/file.zip', './unzip/path') 339 | const output = result ? 'File unzipped successfully' : 'Error unzipping' 340 | ``` 341 | 342 | 343 | ### Remove 344 | 345 | #### removeFile 346 | 347 | ```ts 348 | removeFile(path: string): void 349 | ``` 350 | 351 | Args: 352 | 353 | * **path**: path to a local file to delete 354 | 355 | 356 | Usage: 357 | 358 | ```ts 359 | await removeFile('/path/to/file.x') 360 | ``` 361 | 362 | ## Testing 363 | 364 | Run all the tests: 365 | 366 | `deno test -A --unstable tests/*` 367 | 368 | Run separate tests 369 | 370 | `deno test -A --unstable tests/csv-test.ts` 371 | 372 | 373 | ## License 374 | 375 | [MIT](LICENSE) 376 | 377 | -------------------------------------------------------------------------------- /examples/csv/arquero-example.ts: -------------------------------------------------------------------------------- 1 | import * as aq from "https://cdn.skypack.dev/arquero"; 2 | 3 | let t = await aq.fromCSV(await Deno.readTextFile("./examples/csv/flights.csv")); 4 | 5 | t = t 6 | .derive({ 7 | // convert the JSON duration representation into seconds 8 | // input: {hours: 1, minutes: 2, seconds: 3} 9 | // output: 3723 10 | duration: (d: any) => { 11 | const parsed = aq.op.parse_json(d.duration); 12 | return ( 13 | (parsed.days ?? 0) * 24 * 60 * 60 + 14 | (parsed.hours ?? 0) * 60 * 60 + 15 | (parsed.minutes ?? 0) * 60 + 16 | (parsed.seconds ?? 0) 17 | ) 18 | }, 19 | // convert the date from Unix millis to ISO8601 20 | day: (d: any) => aq.op.format_utcdate(d.day, true), 21 | }) 22 | // Sort rows by duration, descreasing 23 | .orderby(aq.desc("duration")); 24 | 25 | await Deno.writeTextFile("./examples/csv/flights2.csv", t.toCSV()); 26 | -------------------------------------------------------------------------------- /examples/csv/csv-example.ts: -------------------------------------------------------------------------------- 1 | import { readCSV, writeCSV } from '../../src/csv.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | 3 | // Path to a csv file 4 | const csvPath = './examples/csv/prices.csv'; 5 | 6 | /* 7 | Parse a csv file and return an object[] 8 | 9 | [ 10 | { Name: "One", Amount: "500", Price: "$0.5" }, 11 | { Name: "Two", Amount: "13", Price: "$10" }, 12 | { Name: "Three", Amount: "-3", Price: "$3000" } 13 | ] 14 | */ 15 | const originalCSV = await readCSV(csvPath) 16 | console.log(originalCSV) 17 | 18 | /* 19 | Can use other options for reading CSV 20 | More detail on options can be found here: https://deno.land/std@0.92.0/encoding#csv 21 | */ 22 | const csvOptions = await readCSV(csvPath, { 23 | separator: ',', // can use an optional separator. default is comma 24 | trimLeadingSpace: false, // whether to trim the leading space. default is false 25 | lazyQuotes: false // Allow unquoted quote in a quoted field or non double quoted quotes in quoted field. default is false 26 | }) 27 | console.log(csvOptions); 28 | 29 | /* 30 | Parse a CSV file, skip the first row, and rename the column headers. Return an object[] 31 | 32 | [ 33 | { id: "One", quantity: "500", cost: "$0.5" }, 34 | { id: "Two", quantity: "13", cost: "$10" }, 35 | { id: "Three", quantity: "-3", cost: "$3000" } 36 | ] 37 | */ 38 | const renameColumnsCSV = await readCSV(csvPath, { 39 | columns: ['id', 'quantity', 'cost'], 40 | }); 41 | console.log(renameColumnsCSV); 42 | 43 | /* 44 | Parse a CSV file, skip the first row, and apply a custom function to the second 'quantity' row. Returns an object[] 45 | 46 | [ 47 | { id: "One", quantity: "50", cost: "$0.5" }, 48 | { id: "Two", quantity: "1.3", cost: "$10" }, 49 | { id: "Three", quantity: "-0.3", cost: "$3000" } 50 | ] 51 | */ 52 | const parseColumnCSV = await readCSV(csvPath, { 53 | columns: [ 54 | { 55 | name: 'id' 56 | }, 57 | { 58 | name: 'quantity', 59 | parse: (e: string): string => { 60 | return `${parseFloat(e) / 10}` 61 | } 62 | }, 63 | { 64 | name: 'cost' 65 | } 66 | ], 67 | }); 68 | console.log(parseColumnCSV) 69 | 70 | /* 71 | Write data to a CSV file 72 | 73 | age,name 74 | 70,Rick 75 | 14,Smith 76 | */ 77 | const data = [ 78 | { 79 | age: 70, 80 | name: 'Rick' 81 | }, 82 | { 83 | age: 14, 84 | name: 'Smith' 85 | } 86 | ] 87 | writeCSV('./examples/csv/names.csv', data) 88 | 89 | /* 90 | Write one of the previously parsed csv examples 91 | 92 | id,quantity,cost 93 | One,50,$0.5 94 | Two,1.3,$10 95 | Three,-0.3,$3000 96 | */ 97 | console.log(parseColumnCSV) 98 | writeCSV('./examples/csv/prices-write.csv', parseColumnCSV) 99 | -------------------------------------------------------------------------------- /examples/csv/flights.csv: -------------------------------------------------------------------------------- 1 | callsign,number,origin,duration,day 2 | SWA3239,WN3239,KLAX,"{""minutes"":52,""seconds"":8}",1612051200000 3 | DAL1030,DL1030,KMSP,"{""hours"":3,""minutes"":53,""seconds"":2}",1612051200000 4 | UAL1978,UA1978,KEWR,"{""hours"":5,""minutes"":46,""seconds"":18}",1612051200000 5 | SKW5805,OO5805,,"{""hours"":1,""minutes"":43,""seconds"":16}",1612051200000 6 | SKW5736,OO5736,KSLC,"{""hours"":1,""minutes"":54,""seconds"":46}",1612051200000 7 | UAL419,UA419,KDEN,"{""hours"":2,""minutes"":15,""seconds"":56}",1612051200000 8 | ASA607,AS607,KMCF,"{""hours"":6,""minutes"":12,""seconds"":30}",1612051200000 9 | UAL1687,UA1687,KPDX,"{""hours"":1,""minutes"":28,""seconds"":26}",1612051200000 10 | SKW3380,OO3380,KLAS,"{""hours"":1,""minutes"":12,""seconds"":41}",1612051200000 -------------------------------------------------------------------------------- /examples/csv/flights2.csv: -------------------------------------------------------------------------------- 1 | callsign,number,origin,duration,day 2 | ASA607,AS607,KMCF,22350,2021-01-31T00:00:00.000Z 3 | UAL1978,UA1978,KEWR,20778,2021-01-31T00:00:00.000Z 4 | DAL1030,DL1030,KMSP,13982,2021-01-31T00:00:00.000Z 5 | UAL419,UA419,KDEN,8156,2021-01-31T00:00:00.000Z 6 | SKW5736,OO5736,KSLC,6886,2021-01-31T00:00:00.000Z 7 | SKW5805,OO5805,,6196,2021-01-31T00:00:00.000Z 8 | UAL1687,UA1687,KPDX,5306,2021-01-31T00:00:00.000Z 9 | SKW3380,OO3380,KLAS,4361,2021-01-31T00:00:00.000Z 10 | SWA3239,WN3239,KLAX,3128,2021-01-31T00:00:00.000Z -------------------------------------------------------------------------------- /examples/csv/names.csv: -------------------------------------------------------------------------------- 1 | age,name 2 | 70,Rick 3 | 14,Smith 4 | -------------------------------------------------------------------------------- /examples/csv/prices-write.csv: -------------------------------------------------------------------------------- 1 | id,quantity,cost 2 | One,50,$0.5 3 | Two,1.3,$10 4 | Three,-0.3,$3000 5 | -------------------------------------------------------------------------------- /examples/csv/prices.csv: -------------------------------------------------------------------------------- 1 | Name,Amount,Price 2 | One,500,$0.5 3 | Two,13,$10 4 | Three,-3,$3000 -------------------------------------------------------------------------------- /examples/image/41906373431_72c25d0dfd_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/image/41906373431_72c25d0dfd_b.jpg -------------------------------------------------------------------------------- /examples/image/cat-cropped.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/image/cat-cropped.jpeg -------------------------------------------------------------------------------- /examples/image/cat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/image/cat.jpeg -------------------------------------------------------------------------------- /examples/image/giphy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/image/giphy.gif -------------------------------------------------------------------------------- /examples/image/image-example.ts: -------------------------------------------------------------------------------- 1 | import { readImageFromFile, readImageFromURL, writeImage } from '../../src/image.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | import { Image } from 'https://cdn.deno.land/imagescript/versions/1.2.0/raw/mod.ts'; // library for image manipulations 3 | 4 | const url1 = 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Felis_catus-cat_on_snow.jpg/800px-Felis_catus-cat_on_snow.jpg' 5 | const url2 = 'https://live.staticflickr.com/962/41906373431_72c25d0dfd_b.jpg' 6 | const url3 = 'https://i.giphy.com/media/5wWf7HapUvpOumiXZRK/giphy.gif' 7 | 8 | // Read different images from a url 9 | const image1 = await readImageFromURL(url1) 10 | // (bytes, path, name) 11 | await writeImage(image1.bytes, './examples/image/cat.jpeg') // custom name 12 | 13 | const image2 = await readImageFromURL(url2) 14 | await writeImage(image2.bytes, `./examples/image/${image2.name}`) // default image name 15 | 16 | const image3 = await readImageFromURL(url3) 17 | await writeImage(image3.bytes, `./examples/image/${image3.name}`) 18 | 19 | // Image manipulation example - reading from a file 20 | const bytes = await readImageFromFile('./examples/image/cat.jpeg') // local file 21 | const image = await Image.decode(bytes); 22 | image.crop(image.width/4, image.height/4, image.width/2, image.height/2); // x, y, width, height 23 | const newImageBytes = await image.encode() 24 | await writeImage(newImageBytes, './examples/image/cat-cropped.jpeg') -------------------------------------------------------------------------------- /examples/json/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2021-04-15", 3 | "explanation": "Bright elliptical galaxy Messier 87 (M87) is home to the supermassive black hole captured by planet Earth's Event Horizon Telescope in the first ever image of a black hole. Giant of the Virgo galaxy cluster about 55 million light-years away, M87 is the large galaxy rendered in blue hues in this infrared image from the Spitzer Space telescope. Though M87 appears mostly featureless and cloud-like, the Spitzer image does record details of relativistic jets blasting from the galaxy's central region. Shown in the inset at top right, the jets themselves span thousands of light-years. The brighter jet seen on the right is approaching and close to our line of sight. Opposite, the shock created by the otherwise unseen receding jet lights up a fainter arc of material. Inset at bottom right, the historic black hole image is shown in context, at the center of giant galaxy and relativistic jets. Completely unresolved in the Spitzer image, the supermassive black hole surrounded by infalling material is the source of enormous energy driving the relativistic jets from the center of active galaxy M87.", 4 | "hdurl": "https://apod.nasa.gov/apod/image/2104/pia23122c-16.jpg", 5 | "media_type": "image", 6 | "service_version": "v1", 7 | "title": "The Galaxy, the Jet, and a Famous Black Hole", 8 | "url": "https://apod.nasa.gov/apod/image/2104/pia23122c-16_1067.jpg" 9 | } -------------------------------------------------------------------------------- /examples/json/json-example.ts: -------------------------------------------------------------------------------- 1 | import { readJSON, writeJSON } from '../../src/json.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | 3 | const json = await readJSON('./examples/json/data.json') 4 | 5 | // a New custom json to save 6 | const newData = { 7 | 'image': json.url 8 | } 9 | 10 | await writeJSON('./examples/json/post-data.json', newData) -------------------------------------------------------------------------------- /examples/json/json-indent-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "prices": "$20", 3 | "name": "table" 4 | } -------------------------------------------------------------------------------- /examples/json/json-test.json: -------------------------------------------------------------------------------- 1 | {"prices":"$20","name":"table"} -------------------------------------------------------------------------------- /examples/json/post-data.json: -------------------------------------------------------------------------------- 1 | {"image":"https://apod.nasa.gov/apod/image/2104/pia23122c-16_1067.jpg"} -------------------------------------------------------------------------------- /examples/sheets/sheets-example.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from 'https://deno.land/x/xmlparser@v0.2.0/mod.ts' 2 | 3 | // Example of how to fetch and read a Google spreadsheet 4 | // For this to work without authentication, you have to publish the spreadsheet to the web 5 | // File > Publish to the web > publish entire document as a csv file 6 | 7 | const url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQT7KW2U7o4zh2Sh5qpTl42OQzbKZG-glDd3rVcXhvQ1MtXTQiitvGEV6xvkfrXAn1dYADV1qLc2CEC/pub?output=csv' 8 | 9 | // fetch the spreadsheet as XML 10 | const response = await fetch(url) 11 | const csv = await response.text() 12 | console.log(csv); 13 | -------------------------------------------------------------------------------- /examples/txt/text-write.txt: -------------------------------------------------------------------------------- 1 | Writing test -------------------------------------------------------------------------------- /examples/txt/text-write2.txt: -------------------------------------------------------------------------------- 1 | Write to a file -------------------------------------------------------------------------------- /examples/txt/text.txt: -------------------------------------------------------------------------------- 1 | Test file to read -------------------------------------------------------------------------------- /examples/txt/txt-example.ts: -------------------------------------------------------------------------------- 1 | import { readTXT, writeTXT } from '../../src/txt.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | 3 | // Path to a csv file 4 | const readTXTPath = './examples/txt/text.txt' 5 | const writeTXTPath = './examples/txt/text-write.txt' 6 | const content = 'Writing test' 7 | 8 | // read from a text file 9 | const text = await readTXT(readTXTPath) 10 | console.log(text) 11 | 12 | // write a text file 13 | await writeTXT(writeTXTPath, content) -------------------------------------------------------------------------------- /examples/xlsx/prices.csv: -------------------------------------------------------------------------------- 1 | Name,Amount,Price 2 | One,500,$0.50 3 | Two,13,$10 4 | Thre,-3,"$3,000" 5 | -------------------------------------------------------------------------------- /examples/xlsx/prices.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/xlsx/prices.xlsx -------------------------------------------------------------------------------- /examples/xlsx/xlsx-example.ts: -------------------------------------------------------------------------------- 1 | import { xlsx, readXLSX } from '../../src/xlsx.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | import { writeCSV } from '../../src/csv.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 3 | 4 | const inputFilename = './examples/xlsx/prices.xlsx' 5 | const outputFilename = './examples/xlsx/prices.csv' 6 | 7 | // read about what the xlsx library can do here: https://github.com/SheetJS/sheetjs 8 | 9 | const workbook = await readXLSX(inputFilename) 10 | const sheetData = workbook.Sheets[workbook.SheetNames[0]] 11 | const csvString = await xlsx.utils.sheet_to_csv(sheetData) // can use to_json, to_txt, to_html, to_formulae 12 | 13 | // Write to csv 14 | writeCSV(outputFilename, csvString) -------------------------------------------------------------------------------- /examples/zip/zip-example.ts: -------------------------------------------------------------------------------- 1 | import { unZipFromFile, unZipFromURL } from '../../src/zip.ts' // replace with latest library https://deno.land/x/flat@0.0.x/mod.ts 2 | 3 | // zip from a local file 4 | // (zip file, destination path) 5 | const result = await unZipFromFile('./examples/zip/zip-folder.zip', './examples/zip') 6 | const output = result ? 'File unzipped successfully' : 'Error unzipping' 7 | console.log(output) 8 | 9 | // zip from a URL 10 | // (zip file URL, destination path) 11 | const url = 'https://github.com/githubocto/flat-postprocessing/raw/main/examples/zip/zip-folder.zip' // zip-folder.zip but hosted 12 | const result2 = await unZipFromURL(url, './examples/zip') 13 | const output2 = result2 ? 'File unzipped successfully' : 'Error unzipping' 14 | console.log(output2) -------------------------------------------------------------------------------- /examples/zip/zip-folder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubocto/flat-postprocessing/127af8ca8e748925402a44e6d55f6365ca29d0d2/examples/zip/zip-folder.zip -------------------------------------------------------------------------------- /examples/zip/zip-folder/flights.csv: -------------------------------------------------------------------------------- 1 | callsign,number,origin,duration,day 2 | SWA3239,WN3239,KLAX,"{""minutes"":52,""seconds"":8}",1612051200000 3 | DAL1030,DL1030,KMSP,"{""hours"":3,""minutes"":53,""seconds"":2}",1612051200000 4 | UAL1978,UA1978,KEWR,"{""hours"":5,""minutes"":46,""seconds"":18}",1612051200000 5 | SKW5805,OO5805,,"{""hours"":1,""minutes"":43,""seconds"":16}",1612051200000 6 | SKW5736,OO5736,KSLC,"{""hours"":1,""minutes"":54,""seconds"":46}",1612051200000 7 | UAL419,UA419,KDEN,"{""hours"":2,""minutes"":15,""seconds"":56}",1612051200000 8 | ASA607,AS607,KMCF,"{""hours"":6,""minutes"":12,""seconds"":30}",1612051200000 9 | UAL1687,UA1687,KPDX,"{""hours"":1,""minutes"":28,""seconds"":26}",1612051200000 10 | SKW3380,OO3380,KLAS,"{""hours"":1,""minutes"":12,""seconds"":41}",1612051200000 -------------------------------------------------------------------------------- /examples/zip/zip-folder/prices.csv: -------------------------------------------------------------------------------- 1 | Name,Amount,Price 2 | One,500,$0.5 3 | Two,13,$10 4 | Three,-3,$3000 -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from './src/json.ts' 2 | export * from './src/csv.ts' 3 | export * from './src/image.ts' 4 | export * from './src/xlsx.ts' 5 | export * from './src/zip.ts' 6 | export * from './src/remove.ts' 7 | export * from './src/txt.ts' -------------------------------------------------------------------------------- /src/csv.ts: -------------------------------------------------------------------------------- 1 | import { parse, ParseOptions } from 'https://deno.land/std@0.92.0/encoding/csv.ts' 2 | import { DataItem, stringify } from 'https://deno.land/std@0.92.0/encoding/csv.ts'; 3 | 4 | export async function readCSV(path: string, options?: ParseOptions): Promise[]> { 5 | if (!options || !('skipFirstRow' in options)) { 6 | // to force library to be consistent and return an array of objects 7 | options = { 8 | ...options, 9 | skipFirstRow: true 10 | } 11 | } 12 | 13 | const raw = await Deno.readTextFile(path) 14 | const content = await parse(raw, options) 15 | return content as Record[] 16 | } 17 | 18 | export async function writeCSV(path: string, data: Record[] | string, options?: Deno.WriteFileOptions) { 19 | if (typeof data === 'string') { 20 | await Deno.writeTextFile(path, data, options); 21 | return 22 | } 23 | 24 | const headers = Object.keys(data[0]) 25 | // we have to stringify the data with a row header 26 | const dataString = await stringify(data as DataItem[], headers) 27 | 28 | await Deno.writeTextFile(path, dataString, options); 29 | } 30 | -------------------------------------------------------------------------------- /src/image.ts: -------------------------------------------------------------------------------- 1 | import { mime } from "https://cdn.deno.land/mimetypes/versions/v1.0.0/raw/mod.ts" 2 | import { urlParse } from 'https://cdn.deno.land/url_parse/versions/1.0.0/raw/mod.ts'; 3 | import { basename } from "https://deno.land/std@0.92.0/path/mod.ts"; 4 | 5 | export async function readImageFromURL(url: string): Promise<{ bytes: Uint8Array; name: string; }> { 6 | const response = await fetch(url); // fetch an image 7 | const mimeType = response.headers.get('content-type'); 8 | const imageBytes = new Uint8Array(await response.arrayBuffer()); 9 | 10 | const extension = mime.getExtension(mimeType as string); 11 | const defaultName = basename(urlParse(url).pathname) 12 | let defaultImageName = defaultName 13 | 14 | // if image name does NOT include an extension 15 | if (mime.getType(defaultName) === undefined) { 16 | defaultImageName = `${defaultName.split('.')[0]}.${extension}` 17 | } 18 | 19 | return { bytes: imageBytes, name: defaultImageName } 20 | } 21 | 22 | export async function readImageFromFile(path: string): Promise { 23 | const bytes = await Deno.readFile(path) // local file 24 | return bytes 25 | } 26 | 27 | export async function writeImage(imageBytes: Uint8Array, path: string) { 28 | await Deno.writeFile(path, imageBytes); 29 | } 30 | -------------------------------------------------------------------------------- /src/json.ts: -------------------------------------------------------------------------------- 1 | export async function readJSON(path: string) { 2 | const text = await Deno.readTextFile(path) 3 | return JSON.parse(text) 4 | } 5 | 6 | export async function writeJSON(path: string, ...args: Parameters) { 7 | await Deno.writeTextFile(path, JSON.stringify(...args)) 8 | } 9 | 10 | export async function readJSONFromURL(url: string) { 11 | const response = await fetch(url) 12 | const json = await response.json() 13 | return json 14 | } -------------------------------------------------------------------------------- /src/remove.ts: -------------------------------------------------------------------------------- 1 | export async function removeFile(path: string) { 2 | await Deno.remove(path) 3 | } -------------------------------------------------------------------------------- /src/txt.ts: -------------------------------------------------------------------------------- 1 | export async function readTXT(path: string) { 2 | const text = await Deno.readTextFile(path) 3 | return text 4 | } 5 | 6 | export async function writeTXT(path: string, text: string, options?: Deno.WriteFileOptions) { 7 | await Deno.writeTextFile(path, text, options) 8 | } 9 | -------------------------------------------------------------------------------- /src/xlsx.ts: -------------------------------------------------------------------------------- 1 | // @deno-types="https://deno.land/x/sheetjs/types/index.d.ts" 2 | import * as xlsx from 'https://deno.land/x/sheetjs@v0.18.3/xlsx.mjs'; 3 | import * as cptable from 'https://deno.land/x/sheetjs@v0.18.3/dist/cpexcel.full.mjs'; 4 | xlsx.set_cptable(cptable); 5 | 6 | // read more about the library here: https://github.com/SheetJS/sheetjs 7 | export { xlsx } 8 | 9 | // returns a Workbook 10 | export async function readXLSX(path: string) { 11 | const data = await Deno.readFile(path) 12 | const workbook = xlsx.read(data) 13 | return workbook 14 | } -------------------------------------------------------------------------------- /src/zip.ts: -------------------------------------------------------------------------------- 1 | import { join } from "https://deno.land/std@0.92.0/path/mod.ts"; 2 | import { exists } from "https://deno.land/std@0.92.0/fs/mod.ts"; 3 | 4 | // Great code modified from: https://deno.land/x/zip@v1.1.1 5 | // Modified because of an os.tmpDir error, version differences, 6 | // and overwriting limitations in Deno.run unzip command 7 | 8 | export async function unZipFromFile( 9 | filePath: string, 10 | destinationPath: string | null = "./", 11 | options: any = {}, 12 | ): Promise { 13 | if (!await exists(filePath)) { 14 | console.error("this file does not found"); 15 | return false; 16 | } 17 | 18 | if (!destinationPath) { 19 | destinationPath = "./"; 20 | } 21 | 22 | const fullFileName = filePath.split("/"); 23 | const fileNameWithOutExt = 24 | fullFileName[fullFileName.length - 1].split(".")[0]; 25 | const fullDestinationPath = options.includeFileName 26 | ? join(destinationPath, fileNameWithOutExt) 27 | : destinationPath; 28 | 29 | return await unzipProcess(filePath, fullDestinationPath) 30 | ? fullDestinationPath 31 | : false; 32 | } 33 | 34 | async function unzipProcess( 35 | zipSourcePath: string, 36 | destinationPath: string, 37 | ): Promise { 38 | 39 | const process = Deno.run({ 40 | cmd: ["unzip", "-o", zipSourcePath, "-d", destinationPath], 41 | stdout: "piped", 42 | stderr: "piped", 43 | }); 44 | 45 | const { success, code } = await process.status(); 46 | 47 | // Reading the outputs closes their pipes 48 | const rawOutput = await process.output(); 49 | const rawError = await process.stderrOutput(); 50 | 51 | if (!success) { 52 | const str = new TextDecoder().decode(rawError); 53 | throw new Error(`$Command failed: code ${code}, message: ${str}`); 54 | } else { 55 | const str = new TextDecoder().decode(rawOutput); 56 | } 57 | 58 | Deno.close(process.rid) // close the process 59 | 60 | return success 61 | } 62 | 63 | function getDirectory() { 64 | if (Deno.build.os === "windows") { 65 | return Deno.env.get("TMP") || Deno.env.get("TEMP") || Deno.env.get("USERPROFILE") || Deno.env.get("SystemRoot") || "" 66 | } 67 | 68 | return Deno.env.get("TMPDIR") || "/tmp" 69 | } 70 | 71 | export async function downloadFileToTemp(url: string): Promise { 72 | const response = await fetch(url) 73 | const blob = await response.blob() 74 | 75 | const arrayBufferFromBlobResponse = await blob.arrayBuffer() 76 | const uint8ArrayEncodeFileData = new Uint8Array(arrayBufferFromBlobResponse) 77 | 78 | const temporaryDirectory = Deno.realPathSync(getDirectory()) 79 | 80 | const tempFilePath = join(temporaryDirectory, "file.zip") 81 | 82 | const file = await Deno.create(tempFilePath) 83 | await Deno.writeAll(file, uint8ArrayEncodeFileData) 84 | 85 | Deno.close(file.rid) 86 | 87 | return tempFilePath 88 | } 89 | 90 | export async function unZipFromURL( 91 | fileURL: string, 92 | destinationPath: string | null = "./", 93 | options: any = {}, 94 | ): Promise{ 95 | const downloadedFilePath = await downloadFileToTemp(fileURL) 96 | 97 | const unZippingProcess = await unZipFromFile( 98 | downloadedFilePath, 99 | destinationPath, 100 | options, 101 | ) 102 | 103 | await Deno.remove(downloadedFilePath) 104 | 105 | return unZippingProcess 106 | } -------------------------------------------------------------------------------- /tests/csv-test.ts: -------------------------------------------------------------------------------- 1 | import { assertArrayIncludes } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { readCSV, writeCSV } from '../src/csv.ts' 3 | 4 | const csvReadPath = './examples/csv/prices.csv' 5 | const csvWritePath = './examples/csv/names.csv' 6 | 7 | Deno.test("reads a csv file", async () => { 8 | const csv = await readCSV(csvReadPath) 9 | 10 | assertArrayIncludes(csv, [{ Name: "One", Amount: "500", Price: "$0.5" }]); 11 | }) 12 | 13 | Deno.test("reads a csv file without header", async () => { 14 | const csv = await readCSV(csvReadPath, { 15 | skipFirstRow: true 16 | }) 17 | 18 | assertArrayIncludes(csv, [{ Name: "Two", Amount: "13", Price: "$10" }]); 19 | }) 20 | 21 | Deno.test("writes a csv file", async () => { 22 | const data = [ 23 | { 24 | age: 70, 25 | name: 'Rick' 26 | }, 27 | { 28 | age: 14, 29 | name: 'Smith' 30 | } 31 | ] 32 | await writeCSV(csvWritePath, data) 33 | const csv = await readCSV(csvWritePath) 34 | 35 | assertArrayIncludes(csv, [{ age: "70", name: "Rick" }]); 36 | }) 37 | 38 | Deno.test("appends to a csv file", async () => { 39 | const data = [ 40 | { 41 | age: 70, 42 | name: 'Rick' 43 | }, 44 | { 45 | age: 14, 46 | name: 'Smith' 47 | } 48 | ] 49 | await writeCSV(csvWritePath, data) 50 | const data2 = [ 51 | { 52 | age: 42, 53 | name: 'Jodi' 54 | } 55 | ] 56 | await writeCSV(csvWritePath, data2, { append: true }) 57 | const csv = await readCSV(csvWritePath) 58 | assertArrayIncludes(csv, [{ age: "70", name: "Rick" }]) 59 | assertArrayIncludes(csv, [{ age: "42", name: "Jodi" }]) 60 | }) -------------------------------------------------------------------------------- /tests/image-test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { readImageFromURL } from '../src/image.ts' 3 | 4 | const url = 'https://sheetjs.com/sketch128.png' 5 | 6 | Deno.test("loads an image", async () => { 7 | const image = await readImageFromURL(url) 8 | assertEquals(image.bytes.length, 23325) 9 | assertEquals(image.name, 'sketch128.png') 10 | }) -------------------------------------------------------------------------------- /tests/json-test.ts: -------------------------------------------------------------------------------- 1 | import { assertObjectMatch } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { readJSON, writeJSON } from '../src/json.ts' 3 | 4 | const jsonReadPath = './examples/json/data.json' 5 | const jsonWritePath = './examples/json/json-test.json' 6 | const jsonIndentWritePath = './examples/json/json-indent-test.json' 7 | 8 | Deno.test("reads a json file", async () => { 9 | const json = await readJSON(jsonReadPath) 10 | 11 | assertObjectMatch(json, { media_type: "image", service_version: "v1" }); 12 | }) 13 | 14 | Deno.test("writes a json file", async () => { 15 | const data = { 16 | prices: '$20', 17 | name: 'table' 18 | } 19 | await writeJSON(jsonWritePath, data) 20 | const json = await readJSON(jsonWritePath) 21 | 22 | assertObjectMatch(json, { prices: "$20", name: "table" }); 23 | }) 24 | 25 | Deno.test("writes a json file with space indent", async () => { 26 | const data = { 27 | prices: '$20', 28 | name: 'table' 29 | } 30 | await writeJSON(jsonIndentWritePath, data, null, 2) 31 | const json = await readJSON(jsonIndentWritePath) 32 | 33 | assertObjectMatch(json, { prices: "$20", name: "table" }); 34 | }) -------------------------------------------------------------------------------- /tests/remove-test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { existsSync } from 'https://deno.land/std/fs/mod.ts'; 3 | import { removeFile } from '../src/remove.ts' 4 | 5 | const path = './tests/test.txt' 6 | 7 | Deno.test("removes file correctly", async () => { 8 | await Deno.writeTextFile('./tests/test.txt', 'This is a test file') 9 | await removeFile(path) 10 | const doesFileExist = existsSync(path) 11 | 12 | assertEquals(doesFileExist, false) 13 | }) -------------------------------------------------------------------------------- /tests/txt-test.ts: -------------------------------------------------------------------------------- 1 | import { assertStringIncludes } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { readTXT, writeTXT } from '../src/txt.ts' 3 | 4 | const txtReadPath = './examples/txt/text.txt' 5 | const jsonWritePath = './examples/txt/text-write2.txt' 6 | 7 | Deno.test("reads a txt file", async () => { 8 | const txt = await readTXT(txtReadPath) 9 | console.log(txt) 10 | 11 | assertStringIncludes(txt, 'Test file to read'); 12 | }) 13 | 14 | Deno.test("writes a txt file", async () => { 15 | const content = 'Write to a file' 16 | await writeTXT(jsonWritePath, content) 17 | const txt = await readTXT(jsonWritePath) 18 | 19 | assertStringIncludes(txt, content); 20 | }) 21 | 22 | Deno.test("appends to a txt file", async () => { 23 | const content = 'Write to a file' 24 | const content2 = 'Append to a file' 25 | await writeTXT(jsonWritePath, content) 26 | await writeTXT(jsonWritePath, content2, { append: true }) 27 | const txt = await readTXT(jsonWritePath) 28 | 29 | assertStringIncludes(txt, content); 30 | assertStringIncludes(txt, content2); 31 | }) -------------------------------------------------------------------------------- /tests/xlsx-test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertStringIncludes } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { xlsx, readXLSX } from '../src/xlsx.ts' 3 | 4 | const xlsxReadPath = './examples/xlsx/prices.xlsx' 5 | 6 | Deno.test("xlsx library contains keys and functions", () => { 7 | assertEquals(typeof xlsx.utils.sheet_to_csv, "function") 8 | }) 9 | 10 | Deno.test("reads a XLSX file", async () => { 11 | const workbook = await readXLSX(xlsxReadPath) 12 | const sheetData = workbook.Sheets[workbook.SheetNames[0]] 13 | const csvString = await xlsx.utils.sheet_to_csv(sheetData) 14 | 15 | assertStringIncludes(csvString, "Two,13,$10") 16 | }) -------------------------------------------------------------------------------- /tests/zip-test.ts: -------------------------------------------------------------------------------- 1 | import { assertNotEquals } from "https://deno.land/std@0.92.0/testing/asserts.ts" 2 | import { unZipFromFile } from '../src/zip.ts' 3 | 4 | Deno.test("unzips a folder", async () => { 5 | const result = await unZipFromFile('./examples/zip/zip-folder.zip', './examples/zip') 6 | 7 | assertNotEquals(result, false) 8 | }) --------------------------------------------------------------------------------