parser = require('./tldr-parser.js').parser;
util = require('util');

module.exports.ERRORS = parser.ERRORS = {
  'TLDR001': 'File should contain no leading whitespace',
  'TLDR002': 'A single space should precede a sentence',
  'TLDR003': 'Descriptions should start with a capital letter',
  'TLDR004': 'Command descriptions should end in a period',
  'TLDR005': 'Example descriptions should end in a colon',
  'TLDR006': 'Command name and description should be separated by an empty line',
  'TLDR007': 'Example descriptions should be surrounded by empty lines',
  'TLDR008': 'File should contain no trailing whitespace',
  'TLDR009': 'Page should contain a newline at end of file',
  'TLDR010': 'Only Unix-style line endings allowed',
  'TLDR011': 'Page never contains more than a single empty line',
  'TLDR012': 'Page should contains no tabs',
  'TLDR013': 'Title should be alphanumeric with dashes, underscores or spaces',
  'TLDR014': 'Page should contain no trailing whitespace',

  'TLDR101': 'Command description probably not properly annotated',
  'TLDR102': 'Example description probably not properly annotated',
  'TLDR103': 'Command example is missing its closing backtick',
  'TLDR104': 'Example descriptions should prefer infinitive tense (e.g. write) over present (e.g. writes) or gerund (e.g. writing)',
  'TLDR105': 'There should be only one command per example',
  'TLDR106': 'Page title should start with a hash (\'#\')'
};

(function(parser) {
  // Prepares state for a single page. Should be called before a run.
  parser.init = function() {
    this.yy.errors = []
    this.yy.page = {
      description: [],    // can be multiple lines
      examples: []
    };
  };
  parser.finish = function() {
    
  };
  parser.yy.ERRORS = parser.ERRORS;
  parser.yy.error = function(location, error) {
    if (!parser.ERRORS[error]) {
      throw new Error("Linter done goofed. '" + error + "' does not exist.");
    }
    parser.yy.errors.push({
      locinfo: location,
      code: error,
      description: parser.ERRORS[error]
    });
  };
  parser.yy.setTitle = function(title) {
    parser.yy.page.title = title;
  };
  parser.yy.addDescription = function(description) {
    parser.yy.page.description.push(description);
  };
  parser.yy.addExample = function(description, commands) {
    parser.yy.page.examples.push({
      description: description,
      commands: commands
    });
  };
  // parser.yy.parseError = function(error, hash) {
  //   console.log(arguments); 
  // };
  parser.yy.createToken = function(token) {
    return {
      type: 'token',
      content: token
    };
  };
  parser.yy.createCommandText = function(text) {
    return {
      type: 'text',
      content: text
    };
  };
  parser.yy.initLexer = function(lexer) {
    lexer.pushState = function(key, condition) {
      if (!condition) {
        condition = {
          ctx: key, 
          rules: lexer._currentRules()
        }
      }
      lexer.conditions[key] = condition;
      lexer.conditionStack.push(key);
    };

    lexer.checkNewline = function(nl, locinfo) {
      if (nl.match(/\r/)) {
        parser.yy.error(locinfo, 'TLDR010')
      } 
    };
  };
})(parser);

var linter = module.exports;

linter.parse = function(page) {
  parser.init();
  parser.parse(page);
  parser.finish();
  return parser.yy.page;
};

linter.formatDescription = function(str) {
  return str[0].toUpperCase() + str.slice(1) + '.';
};

linter.formatExampleDescription = function(str) {
  return str[0].toUpperCase() + str.slice(1) + ':';
}

linter.format = function(parsedPage) {
  var str = '';
  str += util.format(`# ${parsedPage.title}`);
  str += '\n\n';
  parsedPage.description.forEach(function(line) {
    str += util.format(`> ${linter.formatDescription(line)}`);
    str += '\n';
  });
  parsedPage.examples.forEach(function(example) {
    str += '\n';
    str += util.format(`- ${linter.formatExampleDescription(example.description)}`);
    str += '\n\n';
    example.commands.forEach(function(command) {

      str += '`';
      command.forEach(function(textOrToken) {
        str += textOrToken.type === 'token' ? util.format(`{{${textOrToken.content}}}`) : textOrToken.content;
      })
      str += '`\n';
    });
  });
  return str;
};

linter.process = function(page, verbose, alsoFormat) {
  var success, page, result;
  try {
    page = linter.parse(page);
    success = true;
  } catch(err) {
    console.error(err.toString());
    success = false;
  }
  if (verbose) {
    console.log(parser.yy.page.description.length + " line(s) of description");
    console.log(parser.yy.page.examples.length + " examples");
  }
  // TODO add error when filename doesn't match page title

  result = {
    page: parser.yy.page,
    errors: parser.yy.errors,
    success: success
  };

  if (alsoFormat)
    result.formatted = linter.format(parser.yy.page);

  return result;
};

@@ -1720,13 +1720,9 @@
 ) %7B%0A
-    
 %0A
+
   %7D;
@@ -2482,17 +2482,16 @@
 uments);
- 
 %0A  // %7D;
@@ -2872,17 +2872,16 @@
 tx: key,
- 
 %0A       
@@ -3145,17 +3145,16 @@
 %0A      %7D
- 
 %0A    %7D;%0A
@@ -3604,13 +3604,16 @@
 mat(
-%60# $%7B
+'# %25s', 
 pars
@@ -3624,18 +3624,16 @@
 ge.title
-%7D%60
 );%0A  str
@@ -3721,13 +3721,16 @@
 mat(
-%60%3E $%7B
+'%3E %25s', 
 lint
@@ -3755,18 +3755,16 @@
 on(line)
-%7D%60
 );%0A    s
@@ -3875,13 +3875,16 @@
 mat(
-%60- $%7B
+'- %25s', 
 lint
@@ -3931,18 +3931,16 @@
 ription)
-%7D%60
 );%0A    s
@@ -4129,13 +4129,18 @@
 mat(
-%60%7B%7B$%7B
+'%7B%7B%25s%7D%7D', 
 text
@@ -4158,12 +4158,8 @@
 tent
-%7D%7D%7D%60
 ) : 

Replace ES6 template strings with good old util.format()