appium/grunt-helpers.js

805 строки
26 KiB
JavaScript

"use strict";
var _ = require("underscore")
, server = require('./lib/server/main.js')
, rimraf = require('rimraf')
, path = require('path')
// , temp = require('temp')
, mkdirp = require('mkdirp')
, ncp = require('ncp')
// , difflib = require('difflib')
// , prompt = require('prompt')
, exec = require('child_process').exec
, spawn = require('win-spawn')
, parser = require('./lib/server/parser.js')
, namp = require('namp')
, parseXmlString = require('xml2js').parseString
, appiumVer = require('./package.json').version
, fs = require('fs')
, xcode = require('./lib/future.js').xcode
, isWindows = require('appium-support').system.isWindows()
, MAX_BUFFER_SIZE = 524288
, SELENDROID_MAX_BUFFER_SIZE = 4 * MAX_BUFFER_SIZE;
module.exports.startAppium = function (appName, verbose, readyCb, doneCb) {
var app;
if (appName) {
app = (fs.existsSync(appName)) ? appName:
path.resolve(__dirname, "sample-code", "apps", appName, "build", "Release-iphonesimulator", appName + ".app");
} else {
app = null;
}
return server.run({
app: app
, udid: null
, quiet: !verbose
, port: 4723
, nativeInstrumentsLib: false
, fullReset: false
, noReset: false
, launch: app ? true : false
, log: path.resolve(__dirname, "appium.log")
, address: '127.0.0.1'
, androidDeviceReadyTimeout: 5
, nodeconfig: null
, robotPort: -1
, robotAddresss: '0.0.0.0'
, keepArtifacts: false
, ipa: null
, avd: null
}
, readyCb
, doneCb
);
};
var execWithOutput = function (cmd, cb) {
exec(cmd, function (err, stdout, stderr) {
if (err) {
console.error("Command failed");
console.error("stdout:");
console.error(stdout);
console.error("stderr:");
console.error(stderr);
}
cb(err, stdout, stderr);
});
};
module.exports.getSampleCode = function (grunt, hardcore, cb) {
var submodulesDir = path.resolve(__dirname, "submodules");
var sampleCodeGit = path.resolve(submodulesDir, "sample-code");
var sampleCodeDir = path.resolve(__dirname, "sample-code");
var sampleCodeExists = fs.existsSync(sampleCodeDir);
var updateCmd = "git submodule update --init \"" + sampleCodeGit + "\"";
console.log("Cloning/updating Appium sample-code submodule");
execWithOutput(updateCmd, function (err, stdout, stderr) {
if (err) return cb(err);
var updated = false;
if (stdout + stderr !== "") {
// there were submodule updates
console.log("There were updates to the submodule");
updated = true;
}
if (hardcore || updated) {
console.log("Removing old sample-code");
console.log("Please remember to rebuild test apps");
rimraf.sync(sampleCodeDir);
}
if (hardcore || updated || !sampleCodeExists) {
console.log("Copying sample-code out for use");
ncp(path.resolve(sampleCodeGit, "sample-code"), sampleCodeDir, function (err) {
if (err) return cb(err);
console.log("Test apps are ready for building");
cb();
});
} else {
console.log("Sample code was not updated, doing nothing");
}
});
};
module.exports.runTestsWithServer = function (grunt, appName, testType, deviceType, verbose, cb) {
if (typeof verbose === "undefined") {
verbose = false;
}
var exitCode = null;
var appServer = module.exports.startAppium(appName, verbose, function () {
module.exports.runMochaTests(grunt, appName, testType, deviceType, function (code) {
appServer.close();
exitCode = code;
});
}, function () {
console.log("Appium server exited");
cb(exitCode === 0);
});
};
module.exports.runMochaTests = function (grunt, appName, testType, deviceType, cb) {
// load the options if they are specified
var options = grunt.config(['mochaTestConfig', testType, 'options']);
if (typeof options !== 'object') {
options = grunt.config(['mochaTestConfig', 'options']);
}
if (typeof options.timeout === "undefined") {
options.timeout = 60000;
}
if (typeof options.reporter === "undefined") {
options.reporter = "tap";
}
var args = ['-t', options.timeout, '-R', options.reporter, '--colors'];
var mochaFiles = [];
var fileConfig = grunt.config(['mochaTestWithServer']);
var configAppDevice, nameOk, deviceOk, configAppTests;
_.each(fileConfig, function (config, configAppName) {
configAppDevice = config[0];
configAppTests = config[1];
nameOk = !appName || appName === configAppName;
deviceOk = !deviceType || deviceType === configAppDevice;
if (nameOk && deviceOk) {
_.each(configAppTests, function (testFiles, testKey) {
if (testType === "*" || testType === testKey) {
_.each(testFiles, function (file) {
_.each(grunt.file.expand(file), function (file) {
mochaFiles.push(file);
});
});
}
});
}
});
var exitCodes = [];
var runMochaProc = function () {
var file = mochaFiles.shift();
if (typeof file !== "undefined") {
var mochaProc = spawn('mocha', args.concat(file), {cwd: __dirname});
mochaProc.on("error", function () {
grunt.fatal("Unable to spawn mocha process: mocha not installed?");
});
mochaProc.stdout.setEncoding('utf8');
mochaProc.stderr.setEncoding('utf8');
mochaProc.stdout.on('data', function (data) {
grunt.log.write(data);
});
mochaProc.stderr.on('data', function (data) {
grunt.log.write(data);
});
mochaProc.on('exit', function (code) {
exitCodes.push(code);
runMochaProc();
});
} else {
cb(_.max(exitCodes));
}
};
runMochaProc();
};
module.exports.tail = function (grunt, filename, cb) {
var proc = spawn('tail', ['-f', filename]);
proc.on("error", function (err) {
grunt.fatal("Unable to spawn \"tail\": " + err.message);
});
proc.stdout.setEncoding('utf8');
proc.stdout.on('data', function (data) {
grunt.log.write(data);
});
proc.on('exit', function (code) {
cb(code);
});
};
module.exports.setDeviceConfigVer = function (grunt, device, cb) {
var value = {version: appiumVer};
exports.writeConfigKey(grunt, device, value, cb);
};
module.exports.writeConfigKey = function (grunt, key, value, cb) {
var configPath = path.resolve(__dirname, ".appiumconfig.json");
fs.readFile(configPath, function (err, data) {
var writeConfig = function (config) {
config[key] = value;
grunt.log.write("\n");
grunt.log.write(JSON.stringify(config));
fs.writeFile(configPath, JSON.stringify(config), cb);
};
if (err) {
grunt.log.write("Config file doesn't exist, creating it");
var config = {};
writeConfig(config);
} else {
grunt.log.write("Config file exists, updating it");
writeConfig(JSON.parse(data.toString('utf8')));
}
});
};
module.exports.setGitRev = function (grunt, rev, cb) {
exports.writeConfigKey(grunt, "git-sha", rev, cb);
};
module.exports.setBuildTime = function (grunt, cb) {
var time = new Date();
exports.writeConfigKey(grunt, "built", time.toISOString(), cb);
};
var auth_enableDevTools = function (grunt, cb) {
grunt.log.writeln("Enabling DevToolsSecurity");
exec('DevToolsSecurity --enable', function (err) {
if (err) grunt.fatal(err);
cb();
});
};
var auth_updateSecurityDb = function (grunt, insecure, cb) {
grunt.log.writeln("Updating security db for " + (insecure ? "insecure" :
"developer") + " access");
var cmd = "security authorizationdb write system.privilege.taskport " +
(insecure ? "allow" : "is-developer");
exec(cmd, function (err) {
if (err) grunt.fatal(err);
cb();
});
};
var auth_chmodApps = function (grunt, cb) {
grunt.log.writeln("Granting access to built-in simulator apps");
var user;
if (!process.env.HOME) {
grunt.fatal(new Error("Could not determine your $HOME"));
} else {
user = /\/([^\/]+)$/.exec(process.env.HOME)[1];
}
xcode.getPath(function (err, xcodeDir) {
if (err) return cb(err);
var glob = path.resolve(xcodeDir, "Platforms/iPhoneSimulator.platform/" +
"Developer/SDKs/iPhoneSimulator*.sdk/Applications");
glob += " ";
glob += path.resolve("/Library/Developer/CoreSimulator/" +
"Profiles/Runtimes/iOS\\ *.simruntime/" +
"Contents/Resources/RuntimeRoot/Applications/");
var cmd = "chown -R " + user + ": " + glob;
exec(cmd, function (err) {
if (err) {
grunt.log.writeln("Encountered an issue chmodding iOS sim app dirs. " +
"This may be because they don't exist on your " +
"system, which is not necessarily a problem. The " +
"error was: " + err.message);
}
cb();
});
});
};
module.exports.authorize = function (grunt, insecure, cb) {
auth_enableDevTools(grunt, function () {
auth_updateSecurityDb(grunt, insecure, function () {
auth_chmodApps(grunt, cb);
});
});
};
module.exports.build = function (appRoot, cb, sdk, xcconfig) {
var next = function () {
var cmd = 'xcodebuild -sdk ' + sdk + ' clean';
console.log('Using sdk: ' + sdk + '...');
console.log("Cleaning build...");
var xcode = exec(cmd, {cwd: appRoot, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) {
if (err) {
console.log("Failed cleaning app, maybe it doesn't exist?");
return cb(stdout + "\n" + stderr);
}
console.log("Building app...");
var args = ['-sdk', sdk];
if (typeof xcconfig !== "undefined") {
args = args.concat(['-xcconfig', xcconfig]);
}
xcode = spawn('xcodebuild', args, {
cwd: appRoot
});
xcode.on("error", function (err) {
cb(new Error("Failed spawning xcodebuild: " + err.message));
});
var output = '';
var collect = function (data) { output += data; };
xcode.stdout.on('data', collect);
xcode.stderr.on('data', collect);
xcode.on('exit', function (code) {
if (code === 0) {
cb(null);
} else {
console.log("Failed building app, maybe it doesn't exist?");
cb(output);
}
});
});
};
if (typeof sdk === "undefined") {
xcode.getVersion(function (err, version) {
if (err) return cb(err);
var sdkVersion = version[0] === "5" ? "7.0" : "6.1";
sdk = 'iphonesimulator' + sdkVersion;
next();
});
} else {
next();
}
};
module.exports.buildApp = function (appDir, cb, sdk) {
var appRoot = path.resolve(__dirname, "sample-code", "apps", appDir);
module.exports.build(appRoot, function (err) {
if (err !== null) {
console.log(err);
cb(false);
} else {
cb(true);
}
}, sdk);
};
module.exports.signApp = function (appName, certName, cb) {
var appPath = path.resolve(__dirname, "sample-code", "apps", appName,
"build", "Release-iphonesimulator");
exec("codesign -f -s \"" + certName + "\" -v " + appName + ".app", {cwd: appPath, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) {
console.log(stdout);
console.log(stderr);
if (err) {
cb(false);
} else {
cb(true);
}
});
};
module.exports.buildSafariLauncherApp = function (cb, sdk, xcconfig) {
var appRoot = path.resolve(__dirname, "submodules", "SafariLauncher");
module.exports.build(appRoot, function (err) {
if (err !== null) {
console.log(err);
cb(false);
} else {
cb(true);
}
}, sdk, xcconfig);
};
var setupAndroidProj = function (grunt, projPath, args, cb) {
if (!process.env.ANDROID_HOME) {
grunt.fatal("Could not find Android SDK, make sure to export ANDROID_HOME");
}
var tool = "android";
if (isWindows) {
tool = "android.bat";
}
var cmd = path.resolve(process.env.ANDROID_HOME, "tools", tool);
if (!fs.existsSync(cmd)) {
grunt.fatal("The `android` command was not found at \"" + cmd + "\", are you sure ANDROID_HOME is set properly?");
}
var proc = spawn(cmd, args, {cwd: projPath});
proc.on("error", function (err) {
grunt.fatal("Unable to spawn android: " + err.message);
});
proc.stdout.setEncoding('utf8');
proc.stderr.setEncoding('utf8');
proc.stdout.on('data', function (data) {
grunt.log.write(data);
});
proc.stderr.on('data', function (data) {
grunt.log.write(data);
});
proc.on('exit', function (code) {
cb(code === 0 ? null : new Error("Setup cmd " + cmd + " failed with code " + code));
});
};
module.exports.setupAndroidBootstrap = function (grunt, cb) {
var projPath = path.resolve(__dirname, "lib", "devices", "android",
"bootstrap");
var args = ["create", "uitest-project", "-n", "AppiumBootstrap", "-t",
"android-19", "-p", "."];
// TODO: possibly check output of `android list target` to make sure api level 19 is available?
setupAndroidProj(grunt, projPath, args, cb);
};
module.exports.setupAndroidApp = function (grunt, appName, cb) {
var appPath = path.resolve(__dirname, "sample-code", "apps", appName);
var args = ["update", "project", "--subprojects", "-t", "android-19", "-p", ".", "-n", appName];
setupAndroidProj(grunt, appPath, args, cb);
};
var buildAndroidProj = function (grunt, projPath, target, cb) {
var cmdName = 'ant';
if (!fs.existsSync(path.resolve(projPath, "build.xml")) &&
fs.existsSync(path.resolve(projPath, "pom.xml"))) {
cmdName = 'mvn';
}
var whichCmd = 'which ';
if (isWindows) {
whichCmd = 'where ';
}
exec(whichCmd + cmdName, { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) {
if (err) {
grunt.fatal("Error finding " + cmdName + " binary, is it on your path?");
} else {
if (stdout) {
var cmd = stdout.split('\r\n')[0].trim();
if (isWindows && cmdName === 'ant') {
cmd = cmd + '.bat';
}
grunt.log.write("Using " + cmdName + " found at " + cmd + "\n");
var proc = spawn(cmd, [target], {cwd: projPath});
proc.on("error", function () {
grunt.fatal("Unable to spawn \"" + cmdName + "\"");
});
proc.stdout.setEncoding('utf8');
proc.stderr.setEncoding('utf8');
proc.stdout.on('data', function (data) {
grunt.log.write(data);
});
proc.stderr.on('data', function (data) {
grunt.log.write(data);
});
proc.on('exit', function (code) {
cb(code ? new Error("Building project exited with " + code) : null);
});
} else {
grunt.fatal("Could not find " + cmdName + " installed; please make sure it's on PATH");
}
}
});
};
module.exports.buildAndroidBootstrap = function (grunt, cb) {
var projPath = path.resolve(__dirname, "lib", "devices", "android",
"bootstrap");
var binSrc = path.resolve(projPath, "bin", "AppiumBootstrap.jar");
var binDestDir = path.resolve(__dirname, "build", "android_bootstrap");
var binDest = path.resolve(binDestDir, "AppiumBootstrap.jar");
buildAndroidProj(grunt, projPath, "build", function (err) {
if (err) {
console.log("Could not build android bootstrap");
return cb(err);
}
mkdirp(binDestDir, function (err) {
if (err) {
console.log("Could not mkdirp " + binDestDir);
return cb(err);
}
rimraf(binDest, function (err) {
if (err) {
console.log("Could not delete old " + binDest);
return cb(err);
}
ncp(binSrc, binDest, function (err) {
if (err) {
console.log("Could not copy " + binSrc + " to " + binDest);
return cb(err);
}
cb();
});
});
});
});
};
var fixSelendroidAndroidManifest = function (dstManifest, cb) {
console.log("Modifying manifest for no icons");
fs.readFile(dstManifest, function (err, data) {
if (err) {
console.error("Could not open new manifest");
return cb(err);
}
data = data.toString('utf8');
console.log(data);
var iconRe = /application[\s\S]+android:icon="[^"]+"/;
data = data.replace(iconRe, "application");
fs.writeFile(dstManifest, data, function (err) {
if (err) {
console.error("Could not write modified manifest");
return cb(err);
}
cb(null);
});
});
};
module.exports.fixSelendroidAndroidManifest = fixSelendroidAndroidManifest;
module.exports.buildSelendroidServer = function (cb) {
getSelendroidVersion(function (err, version) {
if (err) return cb(err);
console.log("Installing Cordova");
var cordovaDir = path.resolve(__dirname, "submodules", "selendroid",
"third-party", "cordova-3.7.0");
var cmd = "mvn install:install-file -Dfile=classes.jar -DgroupId=org.apache.cordova " +
"-DartifactId=cordova -Dversion=3.7.0 -Dclassifier=android -Dpackaging=jar";
exec(cmd, {cwd: cordovaDir}, function (err, stdout, stderr) {
if (err) {
console.error("Unable to install Cordova. Stdout was: ");
console.error(stdout);
console.error(stderr);
return cb(err);
}
});
console.log("Building selendroid server");
var buildDir = path.resolve(__dirname, "submodules", "selendroid");
var target = path.resolve(buildDir, "selendroid-server", "target",
"selendroid-server-" + version + ".apk");
var destDir = path.resolve(__dirname, "build", "selendroid");
var destBin = path.resolve(destDir, "selendroid.apk");
var srcManifest = path.resolve(__dirname, "submodules", "selendroid",
"selendroid-server", "AndroidManifest.xml");
var dstManifest = path.resolve(destDir, "AndroidManifest.xml");
cmd = "mvn clean package -DskipTests=true";
exec(cmd, {cwd: buildDir, maxBuffer: SELENDROID_MAX_BUFFER_SIZE}, function (err, stdout, stderr) {
if (err) {
console.error("Unable to build selendroid server. Stdout was: ");
console.error(stdout);
console.error(stderr);
return cb(err);
}
console.log("Making sure target exists");
fs.stat(target, function (err) {
if (err) {
console.error("Selendroid doesn't exist! Not sure what to do.");
return cb(err);
}
console.log("Selendroid server built successfully, copying to build/selendroid");
rimraf(destDir, function (err) {
if (err) {
console.error("Could not remove " + destDir);
return cb(err);
}
mkdirp(destDir, function (err) {
if (err) {
console.error("Could not create " + destDir);
return cb(err);
}
ncp(target, destBin, function (err) {
if (err) {
console.error("Could not copy " + target + " to " + destBin);
return cb(err);
}
console.log("Copying selendroid manifest as well");
ncp(srcManifest, dstManifest, function (err) {
if (err) {
console.error("Could not copy manifest");
return cb(err);
}
fixSelendroidAndroidManifest(dstManifest, cb);
});
});
});
});
});
});
});
};
var getSelendroidVersion = function (cb) {
console.log("Getting Selendroid version");
var pomXml = path.resolve(__dirname, "submodules", "selendroid",
"selendroid-server", "pom.xml");
fs.readFile(pomXml, function (err, xmlData) {
if (err) {
console.error("Could not find selendroid's pom.xml at");
return cb(err);
}
parseXmlString(xmlData.toString('utf8'), function (err, res) {
if (err) {
console.error("Error parsing selendroid's pom.xml");
return cb(err);
}
var version = res.project.parent[0].version[0];
if (typeof version === "string") {
console.log("Selendroid version is " + version);
cb(null, version);
} else {
cb(new Error("Version " + version + " was not valid"));
}
});
});
};
module.exports.buildAndroidApp = function (grunt, appName, cb) {
var appPath = path.resolve(__dirname, "sample-code", "apps", appName);
buildAndroidProj(grunt, appPath, "clean", function (err) {
if (err) return cb(err);
buildAndroidProj(grunt, appPath, "debug", cb);
});
};
module.exports.buildSelendroidAndroidApp = function (grunt, appName, cb) {
var appPath = path.resolve(__dirname, "sample-code", "apps" + appName);
buildAndroidProj(grunt, appPath, "package", cb);
};
module.exports.installAndroidApp = function (grunt, appName, cb) {
var pkgMap = {'ApiDemos': 'com.example.android.apis'};
if (!_.has(pkgMap, appName)) {
var msg = "We don't know about appName " + appName + ", please edit " +
"grunt-helpers.js:installAndroidApp() to add it and its " +
"package identifier";
grunt.fatal(new Error(msg));
}
var appPath = path.resolve(__dirname, "sample-code", "apps", appName,
"bin/" + appName + "-debug.apk");
exec("adb uninstall " + pkgMap[appName], { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) {
if (err) return grunt.fatal(err);
grunt.log.write(stdout);
exec("adb install -r " + appPath, { maxBuffer: MAX_BUFFER_SIZE }, function (err, stdout) {
if (err) return grunt.fatal(err);
grunt.log.write(stdout);
cb();
});
});
};
module.exports.generateServerDocs = function (grunt, cb) {
var p = parser();
var docFile = path.resolve(__dirname, "docs/en/writing-running-appium/server-args.md");
var md = "# Appium server arguments\n\n";
md += "Usage: `node . [flags]`\n\n";
md += "## Server flags\n";
md += "All flags are optional, but some are required in conjunction with " +
"certain others.\n\n";
md += "\n\n<expand_table>\n\n";
md += "|Flag|Default|Description|Example|\n";
md += "|----|-------|-----------|-------|\n";
_.each(p.rawArgs, function (arg) {
var argNames = arg[0];
var exampleArg = typeof arg[0][1] === "undefined" ? arg[0][0] : arg[0][1];
var argOpts = arg[1];
md += "|`" + argNames.join("`, `") + "`";
md += "|" + ((typeof argOpts.defaultValue === "undefined") ? "" : argOpts.defaultValue);
md += "|" + argOpts.help;
md += "|" + ((typeof argOpts.example === "undefined") ? "" : "`" + exampleArg + " " + argOpts.example + "`");
md += "|\n";
});
fs.writeFile(docFile, md, function (err) {
if (err) {
console.log(err.stack);
grunt.fatal(err);
} else {
grunt.log.write("New docs written! Don't forget to commit and push");
cb();
}
});
};
module.exports.generateAppiumIo = function (grunt, cb) {
getAppiumIoFiles(function (err, template, readme) {
if (err) {
return grunt.fatal(err);
}
var readmeLex = namp.lexer(readme)
, headers = getMarkdownHeaders(readmeLex)
, sidebarHtml = generateSidebarHtml(headers)
, warning = "<!-- THIS FILE IS AUTOMATICALLY GENERATED DO NOT EDIT -->\n"
, bodyHtml = generateBodyHtml(readmeLex, headers)
, submod = path.resolve(__dirname, "submodules", "appium.io")
, outfile = submod + "/getting-started.html";
var newDoc = template.replace("{{ SIDENAV }}", sidebarHtml)
.replace("{{ README_SECTIONS }}", bodyHtml)
.replace("{{ WARNING }}", warning);
fs.writeFile(outfile, newDoc, function (err) {
if (err) {
grunt.fatal(err);
} else {
grunt.log.write("Pushing changes to appium.io...");
var cmd = 'git commit -am "updating getting-started via grunt" && ' +
'git pull --rebase origin master && ' +
'git push origin master && ' +
'git checkout gh-pages && ' +
'git pull origin gh-pages && ' +
'git merge master && ' +
'git push origin gh-pages && ' +
'git checkout master';
exec(cmd, {cwd: submod, maxBuffer: MAX_BUFFER_SIZE}, function (err, stdout, stderr) {
if (err) {
console.log(stdout);
console.log(stderr);
grunt.fatal(err);
} else {
grunt.log.write("success!");
cb();
}
});
}
});
});
};
var getAppiumIoFiles = function (cb) {
var templateFile = path.resolve(__dirname, "submodules", "appium.io", "getting-started-template.html")
, readmeFile = path.resolve(__dirname, "README.md");
fs.readFile(templateFile, function (err, templateData) {
if (err) {
cb(err);
} else {
fs.readFile(readmeFile, function (err, readmeData) {
if (err) cb(err); else cb(null, templateData.toString(), readmeData.toString());
});
}
});
};
var getMarkdownHeaders = function (mdTree) {
var headers = {};
_.each(mdTree, function (mdObj) {
if (mdObjIsHeader(mdObj)) {
headers[mdObj.text] = sanitizeMdHeader(mdObj.text);
}
});
return headers;
};
var mdObjIsHeader = function (mdObj) {
return mdObj.depth === 2 && mdObj.type === 'heading';
};
var sanitizeMdHeader = function (header) {
var re = new RegExp(/[^a-zA-Z0-9]/g);
return header.replace(re, "-")
.replace(/-$/, "")
.replace(/-+/g, "-")
.toLowerCase();
};
var generateSidebarHtml = function (headers) {
var html = '';
_.each(headers, function (link, header) {
header = namp(header).html;
header = header.replace(/<[^>]+>/ig, "");
html += '<li><a href="#' + link + '"><i class="icon-chevron-right"></i> ' +
header + '</a></li>\n';
});
return html;
};
var generateBodyHtml = function (readmeMd, headers) {
var html = ''
, inBetweens = []
, inBetweenHtml = ''
, inSection = false;
var addInBetweenHtml = function () {
if (inBetweens.length) {
inBetweenHtml = namp.parser(inBetweens)[0];
html += inBetweenHtml;
inBetweens = [];
}
};
for (var i = 1; i < readmeMd.length; i++) {
var mdObj = readmeMd[i];
if (mdObjIsHeader(mdObj)) {
addInBetweenHtml();
var headerHtml = namp.parser([mdObj])[0];
headerHtml = headerHtml.replace(/<.?p>/ig, '');
if (inSection) {
html += '</section>\n';
}
html += '<section id="' + headers[mdObj.text] + '">\n';
html += '<h2>\n<a href="#' + headers[mdObj.text] + '" class="anchor" ' +
'name="requirements">\n<span class="mini-icon mini-icon-link">' +
'</span>\n</a>\n' + headerHtml + '\n</h2>\n\n';
inSection = true;
} else if (inSection) {
inBetweens.push(mdObj);
}
if (i === readmeMd.length - 1) {
addInBetweenHtml();
}
}
html += '</section>';
return html;
};