Command line utility to output modified Markdown What this command line utility does is to take a text file and update the content a bit before turning it into Markdown. For example, the first for loop searches all the straight quotes and replace them for curly quotes. The second loop searches for hr tags and replace them for * * *, then adds the noind class to the p that follows that hr tag.  

#!/usr/bin/env node

var fs = require('fs')
var program = require('commander')
var markdown = require('markdown').markdown

program
  .version('0.0.1')
  .usage('&lt;keywords&gt;')
  .parse(process.argv)

if (!program.args.length)
  program.help()

var filename = program.args[0]

fs.readFile(filename, 'utf8', function(err, data) {
  if (err) throw err

  var tree = markdown.parse(data)

  ;(function updateTree(jsonml) {

    // replace straight quotes for curly quotes 
    for (i = 1; i &lt; jsonml.length; i++) {
      if (typeof jsonml[i][1] === "string" &amp;&amp; jsonml[i][1].match(/"/g)) {
        var p = jsonml[i]

        for (j = 1; j &lt; p.length; j++) {
          if (typeof p[j] === "string" &amp;&amp; p[j].match(/"/g)) {
            p[j] = p[j].replace(/"(?=\b)/g, "“")
                       .replace(/"(?!\b)/g, "”")
          }
        }
      }

      if (typeof jsonml[i][1] === "string" &amp;&amp; jsonml[i][1].match(/'/g)) {
        var p = jsonml[i]

        for (j = 1; j &lt; p.length; j++) {
          if (typeof p[j] === "string" &amp;&amp; p[j].match(/'/g)) {
            p[j] = p[j].replace(/'(?=\b)/g, "‘")
                       .replace(/'(?!\b)/g, "’")
          }
        }
      }
    }

    // replace hr for * * * and add noind class to the p tag that follows it
    for (i = 1; i &lt; jsonml.length; i++) {
      if (jsonml[i][0] === 'hr') {
        var hr = jsonml[i]
        var p = jsonml[i + 1]

        hr.splice(0, 1, 'para', '* * *')
        p.splice(1, 0, {'class': 'noind'})
      }       
    }
  })(tree)

  var html = markdown.renderJsonML(markdown.toHTMLTree(tree))
  console.log(html)
})


So my question is, how can I reduce the indentation level?
Have you looked at https://github.com/caolan/async? It can really help with organizing your code. As the name implies, async was created to help with writing asynchronous code. While you are only calling one asynchronous function, we can still use it to help us structure our entire program.

Async aside, the general idea is to use functions to help us break down our problem. Instead of a loop within a loop, we can think about what that inner loop is doing and create a function to replace it (for example, our replaceChar function below).

With this in mind and using https://github.com/caolan/async#waterfall, we could write something like:

#!/usr/bin/env node

var fs = require('fs')
var program = require('commander')
var markdown = require('markdown').markdown
var async = require('async');

function printResult(e, r) {
    if (e) {
        throw e;
    }
    else {
        console.log(r);
    }
}

function parseMarkdown(data, cb) {
    cb(null, markdown.parse(data));
}

function replaceChar(node, search, replace) {
    var i;
    var re0 = new RegExp(search + '(?=\b)', 'g');
    var re1 = new RegExp(search + '(?!\b)', 'g');
    for (i = 1; i &lt; node.length; ++i) {
        node[i] = node[i].replace(re0, replace).replace(re1, replace);
    }
    return node;
}

function replaceQuotes(tree, cb) {
    var i;
    for (i = 1; i &lt; tree.length; i++) {
        tree[i] = replaceChar(tree[i], '"', '“');
        tree[i] = replaceChar(tree[i], "'", "‘");
    }
    cb(null, tree);
}

function replaceHr(tree, cb) {
    var i;
    for (i = 1; i &lt; tree.length; i++) {
        if (tree[i][0] === 'hr') {
            var hr = tree[i]
            var p = tree[i + 1]

            hr.splice(0, 1, 'para', '* * *')
            p.splice(1, 0, {'class': 'noind'})
        }
    }
    cb(null, tree);
}

function convertToHtml(tree, cb) {
    cb(null, markdown.renderJsonML(markdown.toHTMLTree(tree)));
}

program
  .version('0.0.1')
  .usage('&lt;keywords&gt;')
  .parse(process.argv)

if (!program.args.length)
  program.help()

var filename = program.args[0]

async.waterfall([
    fs.readFile.bind(fs, filename, 'utf8'),
    parseMarkdown,
    replaceQuotes,
    replaceHr,
    convertToHtml
], printResult);