javascriptfunctional-programmingramda.jsfantasylandfolktale

Mapping over an array of Tasks in Javascript


So I've started looking at Ramda / Folktale. I'm having an issue trying to map over an array of Tasks that comes from a directory. I'm trying to parse file contents.

var fs = require('fs');
var util = require('util');
var R = require('ramda');
var Task = require('data.task');

var compose = R.compose;
var map = R.map;
var chain = R.chain;


function parseFile(data) {
      console.log("Name: " + data.match(/\$name:(.*)/)[1]);
      console.log("Description: " + data.match(/\$description:(.*)/)[1]);
      console.log("Example path: " + data.match(/\$example:(.*)/)[1]);
}

// String => Task [String]
function readDirectories(path) {
    return new Task(function(reject, resolve) {
        fs.readdir(path, function(err, files) {
            err ? reject(err) : resolve(files);
        })
    })
}

// String => Task String
function readFile(file) {
    return new Task(function(reject, resolve) {
        fs.readFile('./src/less/' + file, 'utf8', function(err, data) {
            err ? reject(err) : resolve(data);
        })
    })
}

var app = compose(chain(readFile), readDirectories);

app('./src/less').fork(
    function(error) { throw error },
    function(data)  { util.log(data) }
);

I'm reading the files in a directory and returning a Task. When this resolves it should go into the readFile function (which returns a new task). Once it reads the file I want it to just parse some bits out of there.

With the following:

var app = compose(chain(readFile), readDirectories);

It gets into the readFile function but 'file' is an array of files so it errors.

With:

var app = compose(chain(map(readFile)), readDirectories);

We never get into fs.readfile(), but 'file' is the actual file name.

I'm pretty stumped on this and the documentation is baffling. Any suggestions welcome.

Thanks


Solution

  • 'use strict';
    
    const fs = require('fs');
    
    const Task = require('data.task');
    const R = require('ramda');
    
    
    //    parseFile :: String -> { name :: String
    //                           , description :: String
    //                           , example :: String }
    const parseFile = data => ({
      name:         R.nth(1, R.match(/[$]name:(.*)/, data)),
      description:  R.nth(1, R.match(/[$]description:(.*)/, data)),
      example:      R.nth(1, R.match(/[$]example:(.*)/, data)),
    });
    
    //    readDirectories :: String -> Task (Array String)
    const readDirectories = path =>
      new Task((reject, resolve) => {
        fs.readdir(path, (err, filenames) => {
          err == null ? resolve(filenames) : reject(err);
        })
      });
    
    //    readFile :: String -> Task String
    const readFile = filename =>
      new Task(function(reject, resolve) {
        fs.readFile('./src/less/' + filename, 'utf8', (err, data) => {
          err == null ? resolve(data) : reject(err);
        })
      });
    
    //    dirs :: Task (Array String)
    const dirs = readDirectories('./src/less');
    
    //    files :: Task (Array (Task String))
    const files = R.map(R.map(readFile), dirs);
    
    //    sequenced :: Task (Task (Array String))
    const sequenced = R.map(R.sequence(Task.of), files);
    
    //    unnested :: Task (Array String)
    const unnested = R.unnest(sequenced);
    
    //    parsed :: Task (Array { name :: String
    //                          , description :: String
    //                          , example :: String })
    const parsed = R.map(R.map(parseFile), unnested);
    
    parsed.fork(err => {
                  process.stderr.write(err.message);
                  process.exit(1);
                },
                data => {
                  process.stdout.write(R.toString(data));
                  process.exit(0);
                });
    

    I wrote each of the transformations on a separate line so I could include type signatures which make the nested maps easier to understand. These could of course be combined into a pipeline via R.pipe.

    The most interesting steps are using R.sequence to transform Array (Task String) into Task (Array String), and using R.unnest to transform Task (Task (Array String)) into Task (Array String).

    I suggest having a look at plaid/async-problem if you have not already done so.