icyrock.com

Home

PureScript experiments - jszip

2017-Jun-26 20:10
purescript-experimentspurescript

JSZip is a pure Javascript library for working with zip files. Here's how it can be used from Purescript.

First, create and initialize a Purescript project and install libraries we will need:

1
2
3
4
5
6
$ mkdir purescript-jszip
$ cd purescript-jszip
$ pulp init
$ bower install --save purescript-aff-promise purescript-node-fs-aff
$ npm init -y
$ npm install --save jszip

Then create a Javascript part of the foreign interface in src/Jsz.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const toJson = function(obj) {
  return JSON.stringify(obj, null, 2)
}
 
exports.jsZipToJson = toJson
exports.zipObjectToJson = toJson
 
exports.file = function(jsZip) {
  return function(name) {
    return function() {
      return jsZip.file(name)
    }
  }
}
 
exports.loadAsyncPromise = function(jsZip) {
  return function(data) {
    return jsZip.loadAsync(data)
  }
}
 
exports.newJsZip = function() {
  const JsZip = require('jszip')
  return new JsZip()
}
 
exports.asyncPromise = function(zipObject) {
  return zipObject.async('nodebuffer')
}

The defined functions are:

You can read about the API details on the JSZip API page.

After that, create the Purescript counterpart, src/Jsz.purs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module Jsz where
 
import Prelude
import Control.Monad.Aff (Aff)
import Control.Monad.Eff (Eff)
import Control.Promise as Promise
import Control.Promise (Promise)
import Node.Buffer (Buffer)
 
foreign import data JsZip :: Type
 
foreign import jsZipToJson :: JsZip -> String
 
instance jsZipShow :: Show JsZip where
  show = jsZipToJson
 
foreign import data ZipObject :: Type
 
foreign import zipObjectToJson :: ZipObject -> String
 
instance zipObjectShow :: Show ZipObject where
  show = zipObjectToJson
 
foreign import file
  :: forall eff
    . JsZip
  -> String
  -> Eff eff ZipObject
 
foreign import loadAsyncPromise
  :: JsZip
  -> Buffer
  -> Promise JsZip
 
loadAsync :: forall eff. JsZip -> Buffer -> Aff eff JsZip
loadAsync jz buf = Promise.toAff $ loadAsyncPromise jz buf
 
foreign import newJsZip
  :: forall eff
   . Eff eff JsZip
 
foreign import asyncPromise
  :: ZipObject
  -> Promise Buffer
 
async :: forall eff. ZipObject -> Aff eff Buffer
async zo = Promise.toAff $ asyncPromise zo

Here we see the same functions repeated as foreign imports. On top of that, we have:

Finally, create a sample example in src/Main.purs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module Main where
 
import Prelude
 
import Control.Monad.Aff (runAff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE, log, logShow)
import Control.Monad.Eff.Exception (EXCEPTION)
import Jsz (async, file, loadAsync, newJsZip)
import Node.Buffer (BUFFER, toString)
import Node.Encoding (Encoding(..))
import Node.FS (FS)
import Node.FS.Aff (readFile)
 
main
  :: forall eff
   . Eff ( buffer :: BUFFER
         , console :: CONSOLE
         , exception :: EXCEPTION
         , fs :: FS
         | eff) Unit
main = void $ runAff logShow logShow do
  bufZip <- readFile "var/sample.zip"
 
  jz1 <- liftEff $ newJsZip
  jz2 <- loadAsync jz1 bufZip
 
  zo <- liftEff $ file jz2 "sample.txt"
   
  bufTxt <- async zo
  str <- liftEff $ toString UTF8 bufTxt
  liftEff $ log str

Here we use runAff to run the rest in Aff monad. This allows for logging exceptions if there are any. The steps are:

Sample setup and invocation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ mkdir var
$ cd var
$ cat > sample.txt
When you come to a fork in a road, take it.
-- Yogi Berra
^D
$ zip sample.zip sample.txt
  adding: sample.txt (stored 0%)
$ cd ..
$ pulp run
... <lines ommited>
* Build successful.
When you come to a fork in a road, take it.
-- Yogi Berra
 
unit
$