mecano
Version:
Common functions for system deployment.
1,253 lines (1,207 loc) • 38.1 kB
JavaScript
// Generated by CoffeeScript 1.7.1
var Stream, buffer, connect, crypto, each, exec, fs, ini, jsesc, misc, path, rimraf, ssh2fs, tilde, util,
__slice = [].slice;
crypto = require('crypto');
fs = require('fs');
path = require('path');
each = require('each');
util = require('util');
Stream = require('stream');
exec = require('ssh2-exec');
connect = require('ssh2-connect');
buffer = require('buffer');
rimraf = require('rimraf');
ini = require('ini');
tilde = require('tilde-expansion');
ssh2fs = require('ssh2-fs');
jsesc = require('jsesc');
misc = module.exports = {
array: {
intersect: function(array) {
var argument, i, item, j, result, _i, _j, _len, _len1;
if (array === null) {
return [];
}
result = [];
for (i = _i = 0, _len = array.length; _i < _len; i = ++_i) {
item = array[i];
if (result.indexOf(item) !== -1) {
continue;
}
for (j = _j = 0, _len1 = arguments.length; _j < _len1; j = ++_j) {
argument = arguments[j];
if (argument.indexOf(item) === -1) {
break;
}
}
if (j === arguments.length) {
result.push(item);
}
}
return result;
}
},
regexp: {
escape: function(str) {
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
},
object: {
equals: function(obj1, obj2, keys) {
var k, keys1, keys2, _i, _len;
keys1 = Object.keys(obj1);
keys2 = Object.keys(obj2);
if (keys) {
keys1 = keys1.filter(function(k) {
return keys.indexOf(k) !== -1;
});
keys2 = keys2.filter(function(k) {
return keys.indexOf(k) !== -1;
});
} else {
keys = keys1;
}
if (keys1.length !== keys2.length) {
return false;
}
for (_i = 0, _len = keys.length; _i < _len; _i++) {
k = keys[_i];
if (obj1[k] !== obj2[k]) {
return false;
}
}
return true;
}
},
path: {
normalize: function(location, callback) {
return tilde(location, function(location) {
return callback(path.normalize(location));
});
},
resolve: function() {
var callback, locations, normalized, _i;
locations = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), callback = arguments[_i++];
normalized = [];
return each(locations).on('item', function(location, next) {
return misc.path.normalize(location, function(location) {
normalized.push(location);
return next();
});
}).on('end', function() {
return callback(path.resolve.apply(path, normalized));
});
}
},
iptables: {
add_properties: ['-p', '-s', '-d', '-j', '-g', '-i', '-o', '-f'],
modify_properties: ['-c', 'state|--state', 'comment|--comment', 'tcp|--source-port', 'tcp|--sport', 'tcp|--destination-port', 'tcp|--dport', 'tcp|--tcp-flags', 'tcp|--syn', 'tcp|--tcp-option', 'udp|--source-port', 'udp|--sport', 'udp|--destination-port', 'udp|--dport'],
commands: {
'-A': ['chain'],
'-D': ['chain'],
'-I': ['chain'],
'-R': ['chain'],
'-P': ['chain', 'target'],
'-L': true,
'-S': true,
'-F': true,
'-Z': true,
'-N': true,
'-X': true,
'-E': true
},
parameters: ['-p', '-s', '-d', '-j', '-g', '-i', '-o', '-f', '-c'],
parameters_inverted: {
'--protocol': '-p',
'--source': '-s',
'--destination': '-d',
'--jump': '-j',
'--goto': '-g',
'--in-interface': '-i',
'--out-interface': '-o',
'--fragment': '-f',
'--set-counters': '-c'
},
protocols: {
tcp: ['--source-port', '--sport', '--destination-port', '--dport', '--tcp-flags', '--syn', '--tcp-option'],
udp: ['--source-port', '--sport', '--destination-port', '--dport'],
udplite: [],
icmp: [],
esp: [],
ah: [],
sctp: [],
all: []
},
modules: {
state: ['--state'],
comment: ['--comment']
},
cmd_args: function(cmd, rule) {
var arg, k, match, module, v;
for (k in rule) {
v = rule[k];
if (['chain', 'rulenum'].indexOf(k) !== -1) {
continue;
}
if (match = /^([\w]+)\|([-\w]+)$/.exec(k)) {
module = match[1];
arg = match[2];
cmd += " -m " + module;
cmd += " " + arg + " " + v;
} else {
cmd += " " + k + " " + v;
}
}
return cmd;
},
cmd_modify: function(rule) {
if (rule.rulenum == null) {
rule.rulenum = 1;
}
return misc.iptables.cmd_args("iptables -R " + rule.chain + " " + rule.rulenum, rule);
},
cmd_add: function(rule) {
if (rule.rulenum == null) {
rule.rulenum = 1;
}
return misc.iptables.cmd_args("iptables -I " + rule.chain + " " + rule.rulenum, rule);
},
cmd: function(newrules, oldrules) {
var add_properties, cmds, create, newrule, oldrule, _i, _j, _len, _len1;
cmds = [];
for (_i = 0, _len = newrules.length; _i < _len; _i++) {
newrule = newrules[_i];
create = true;
add_properties = misc.array.intersect(misc.iptables.add_properties, Object.keys(newrule));
for (_j = 0, _len1 = oldrules.length; _j < _len1; _j++) {
oldrule = oldrules[_j];
if (misc.object.equals(newrule, oldrule, add_properties)) {
create = false;
if (!misc.object.equals(newrule, oldrule, misc.iptables.modify_properties)) {
cmds.push(misc.iptables.cmd_modify(newrule));
}
}
}
if (create) {
cmds.push(misc.iptables.cmd_add(newrule));
}
}
return cmds;
},
normalize: function(rules) {
var k, mk, mv, mvs, nk, nrule, nrules, protocol, rule, v, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
nrules = [];
for (_i = 0, _len = rules.length; _i < _len; _i++) {
rule = rules[_i];
nrule = {};
for (k in rule) {
v = rule[k];
if (typeof v === 'number') {
v = rule[k] = "" + v;
}
if (k === 'chain' || k === 'rulenum') {
if (nk === 'rulenum' && typeof v === 'object') {
v = misc.iptables.normalize(v);
}
nk = k;
} else if (k.slice(0, 2) === '--') {
nk = misc.iptables.parameters_inverted[nk];
} else if (k[0] !== '-') {
nk = misc.iptables.parameters_inverted["--" + k];
} else if (misc.iptables.parameters.indexOf(k) !== -1) {
nk = k;
}
if (nk) {
nrule[nk] = v;
rule[k] = null;
}
}
if (protocol = nrule['-p']) {
_ref = misc.iptables.protocols[protocol];
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
k = _ref[_j];
if (rule[k]) {
nrule["" + protocol + "|k"] = rule[k];
rule[k] = null;
} else if (rule[k.slice(2)]) {
nrule["" + protocol + "|" + k] = rule[k.slice(2)];
rule[k.slice(2)] = null;
}
}
}
for (k in rule) {
v = rule[k];
if (!v) {
continue;
}
if (k.slice(0, 3) !== '--') {
k = "--" + k;
}
_ref1 = misc.iptables.modules;
for (mk in _ref1) {
mvs = _ref1[mk];
for (_k = 0, _len2 = mvs.length; _k < _len2; _k++) {
mv = mvs[_k];
if (k === mv) {
nrule["" + mk + "|" + k] = v;
rule[k] = null;
}
}
}
}
for (k in nrule) {
v = nrule[k];
if (!/^[A-Za-z0-9_\/-]+$/.test(v)) {
nrule[k] = jsesc(v, {
quotes: 'double',
wrap: true
});
}
}
nrules.push(nrule);
}
return nrules;
},
/*
Parse the result of `iptables -S`
*/
parse: function(stdout) {
var char, command, command_index, forceflush, i, j, key, line, module, newarg, rule, rules, v, value, _i, _j, _len, _len1, _ref, _ref1;
rules = [];
command = null;
command_index = 1;
_ref = stdout.split(/\r\n|[\n\r\u0085\u2028\u2029]/g);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
command_index++;
rule = {};
i = 0;
key = '';
value = '';
module = null;
while (i <= line.length) {
char = line[i];
forceflush = i === line.length;
newarg = (i === 0 && char === '-') || line.slice(i - 1, +i + 1 || 9e9) === ' -';
if (newarg || forceflush) {
if (value) {
value = value.trim();
if (key === '-m') {
module = value;
} else {
if (module) {
key = "" + module + "|" + key;
}
rule[key] = value;
}
if (misc.iptables.commands[key]) {
if (key !== command) {
command = key;
command_index = 1;
}
rule.rulenum = command_index;
if (Array.isArray(misc.iptables.commands[key])) {
_ref1 = value.split(' ');
for (j = _j = 0, _len1 = _ref1.length; _j < _len1; j = ++_j) {
v = _ref1[j];
rule[misc.iptables.commands[key][j]] = v;
}
}
}
key = '';
value = '';
if (forceflush) {
break;
}
}
key += char;
while ((char = line[++i]) !== ' ') {
key += char;
}
if (misc.iptables.parameters.indexOf(key) !== -1) {
module = null;
}
continue;
}
if (char === '"') {
while ((char = line[++i]) !== '"') {
value += char;
}
i++;
continue;
}
while ((char = line[++i]) !== '-' && i < line.length) {
if (!char) {
process.exit();
}
value += char;
}
}
rules.push(rule);
}
return rules;
}
},
file: {
copyFile: function(ssh, source, destination, callback) {
var s;
s = function(ssh, callback) {
if (!ssh) {
return callback(null, fs);
} else {
return ssh.sftp(callback);
}
};
return s(ssh, function(err, fs) {
var rs, ws;
rs = fs.createReadStream(source);
ws = rs.pipe(fs.createWriteStream(destination));
ws.on('close', function() {
var modified;
if (fs.end) {
fs.end();
}
modified = true;
return callback();
});
return ws.on('error', callback);
});
},
/*
Compare modes
-------------
*/
cmpmod: function() {
var i, l, mode, modes, ref, _i, _ref;
modes = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
ref = modes[0];
if (typeof ref === 'number') {
ref = ref.toString(8);
}
for (i = _i = 1, _ref = modes.length; 1 <= _ref ? _i < _ref : _i > _ref; i = 1 <= _ref ? ++_i : --_i) {
mode = modes[i];
if (typeof mode === 'number') {
mode = mode.toString(8);
}
l = Math.min(ref.length, mode.length);
if (mode.substr(-l) !== ref.substr(-l)) {
return false;
}
}
return true;
},
copy: function(ssh, source, destination, callback) {
if (!ssh) {
source = fs.createReadStream(u.pathname);
source.pipe(destination);
destination.on('close', callback);
return destination.on('error', callback);
} else {
return callback(new Error('Copy over SSH not yet implemented'));
}
},
/*
`files.hash(file, [algorithm], callback)`
-----------------------------------------
Retrieve the hash of a supplied file in hexadecimal
form. If the provided file is a directory, the returned hash
is the sum of all the hashs of the files it recursively
contains. The default algorithm to compute the hash is md5.
Throw an error if file does not exist unless it is a directory.
misc.file.hash ssh, '/path/to/file', (err, md5) ->
md5.should.eql '287621a8df3c3f6c99c7b7645bd09ffd'
*/
hash: function(ssh, file, algorithm, callback) {
var hasher, hashs;
if (arguments.length === 3) {
callback = algorithm;
algorithm = 'md5';
}
hasher = function(ssh, path, callback) {
var shasum;
shasum = crypto.createHash(algorithm);
return ssh2fs.createReadStream(ssh, path, function(err, stream) {
if (err) {
return callback(err);
}
return stream.on('data', function(data) {
return shasum.update(data);
}).on('error', function(err) {
if (err.code === 'EISDIR') {
return callback();
}
return callback(err);
}).on('end', function() {
return callback(err, shasum.digest('hex'));
});
});
};
hashs = [];
return ssh2fs.stat(ssh, file, function(err, stat) {
if ((err != null ? err.code : void 0) === 'ENOENT') {
return callback(new Error("Does not exist: " + file));
}
if (err) {
return callback(err);
}
if (stat.isDirectory()) {
file += '/**';
}
if (ssh && stat.isFile()) {
return hasher(ssh, file, callback);
}
return each().files(file).on('item', function(item, next) {
return hasher(ssh, item, function(err, h) {
if (err) {
return next(err);
}
if (h != null) {
hashs.push(h);
}
return next();
});
}).on('error', function(err) {
return callback(err);
}).on('end', function() {
switch (hashs.length) {
case 0:
if (stat.isFile()) {
return callback(new Error("Does not exist: " + file));
} else {
return callback(null, crypto.createHash(algorithm).update('').digest('hex'));
}
break;
case 1:
return callback(null, hashs[0]);
default:
hashs = crypto.createHash(algorithm).update(hashs.join('')).digest('hex');
return callback(null, hashs);
}
});
});
},
/*
`files.compare(files, callback)`
--------------------------------
Compare the hash of multiple file. Return the file md5
if the file are the same or false otherwise.
*/
compare: function(ssh, files, callback) {
var result;
if (files.length < 2) {
return callback(new Error('Minimum of 2 files'));
}
result = null;
return each(files).parallel(true).on('item', function(file, next) {
return misc.file.hash(ssh, file, function(err, md5) {
if (err) {
return next(err);
}
if (result === null) {
result = md5;
} else if (result !== md5) {
result = false;
}
return next();
});
}).on('error', function(err) {
return callback(err);
}).on('end', function() {
return callback(null, result);
});
},
/*
remove(ssh, path, callback)
---------------------------
Remove a file or directory
*/
remove: function(ssh, path, callback) {
var child;
if (!ssh) {
return rimraf(path, callback);
} else {
child = exec(ssh, "rm -rdf " + path);
return child.on('exit', function(code) {
return callback(null, code);
});
}
}
},
string: {
escapeshellarg: function(arg) {
var result;
result = arg.replace(/[^\\]'/g, function(match) {
return match.slice(0, 1) + '\\\'';
});
return "'" + result + "'";
},
/*
`string.hash(file, [algorithm], callback)`
------------------------------------------
Output the hash of a supplied string in hexadecimal
form. The default algorithm to compute the hash is md5.
*/
hash: function(data, algorithm) {
if (arguments.length === 1) {
algorithm = 'md5';
}
return crypto.createHash(algorithm).update(data).digest('hex');
},
repeat: function(str, l) {
return Array(l + 1).join(str);
},
/*
`string.endsWith(search, [position])`
-------------------------------------
Determines whether a string ends with the characters of another string,
returning true or false as appropriate.
This method has been added to the ECMAScript 6 specification and its code
was borrowed from [Mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)
*/
endsWith: function(str, search, position) {
var lastIndex;
position = position || str.length;
position = position - search.length;
lastIndex = str.lastIndexOf(search);
return lastIndex !== -1 && lastIndex === position;
}
},
ssh: {
/*
passwd(ssh, [user], callback)
----------------------
Return information present in '/etc/passwd' and cache the
result in the provided ssh instance as "passwd".
Result look like:
{ root: {
uid: '0',
gid: '0',
comment: 'root',
home: '/root',
shell: '/bin/bash' }, ... }
*/
passwd: function(ssh, username, callback) {
if (arguments.length === 3) {
if (!username) {
return callback(null, null);
}
if (ssh.passwd && ssh.passwd[username]) {
return callback(null, ssh.passwd[username]);
}
ssh.passwd = null;
return misc.ssh.passwd(ssh, function(err, users) {
var user;
if (err) {
return callback(err);
}
user = users[username];
return callback(null, user);
});
}
callback = username;
username = null;
if (ssh.passwd) {
return callback(null, ssh.passwd);
}
return ssh2fs.readFile(ssh, '/etc/passwd', 'ascii', function(err, lines) {
var info, line, passwd, _i, _len, _ref;
if (err) {
return callback(err);
}
passwd = [];
_ref = lines.split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
info = /(.*)\:\w\:(.*)\:(.*)\:(.*)\:(.*)\:(.*)/.exec(line);
if (!info) {
continue;
}
passwd[info[1]] = {
uid: parseInt(info[2]),
gid: parseInt(info[3]),
comment: info[4],
home: info[5],
shell: info[6]
};
}
ssh.passwd = passwd;
return callback(null, passwd);
});
},
/*
group(ssh, [group], callback)
----------------------
Return information present in '/etc/group' and cache the
result in the provided ssh instance as "group".
Result look like:
{ root: {
password: 'x'
gid: 0,
user_list: [] },
bin: {
password: 'x',
gid: 1,
user_list: ['bin','daemon'] } }
*/
group: function(ssh, group, callback) {
if (arguments.length === 3) {
if (!group) {
return callback(null, null);
}
if (ssh.cache_group && ssh.cache_group[group]) {
return callback(null, ssh.cache_group[group]);
}
ssh.cache_group = null;
return misc.ssh.group(ssh, function(err, groups) {
var gid;
if (err) {
return err;
}
gid = groups[group];
return callback(null, gid);
});
}
callback = group;
group = null;
if (ssh.cache_group) {
return callback(null, ssh.cache_group);
}
return ssh2fs.readFile(ssh, '/etc/group', 'ascii', function(err, lines) {
var info, line, _i, _len, _ref;
if (err) {
return callback(err);
}
group = [];
_ref = lines.split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
info = /(.*)\:(.*)\:(.*)\:(.*)/.exec(line);
if (!info) {
continue;
}
group[info[1]] = {
password: info[2],
gid: parseInt(info[3]),
user_list: info[4] ? info[4].split(',') : []
};
}
ssh.cache_group = group;
return callback(null, group);
});
}
},
/*
`pidfileStatus(ssh, pidfile, [options], callback)`
---------------------------------------
Return a status code after reading a status file. Any existing
pidfile referencing a dead process will be removed.
The callback is called with an error and a status code. Values
expected as status code are:
* 0 if pidfile math a running process
* 1 if pidfile does not exists
* 2 if pidfile exists but match no process
*/
pidfileStatus: function(ssh, pidfile, options, callback) {
if (arguments.length === 3) {
callback = options;
options = {};
}
return ssh2fs.readFile(ssh, pidfile, 'ascii', function(err, pid) {
var run, stdout;
if (err && err.code === 'ENOENT') {
return callback(null, 1);
}
if (err) {
return callback(err);
}
stdout = [];
run = exec({
cmd: "ps aux | grep " + (pid.trim()) + " | grep -v grep | awk '{print $2}'",
ssh: ssh
});
run.stdout.on('data', function(data) {
return stdout.push(data);
});
if (options.stdout) {
run.stdout.pipe(options.stdout);
}
if (options.stderr) {
run.stderr.pipe(options.stderr);
}
return run.on("exit", function(code) {
stdout = stdout.join('');
if (stdout !== '') {
return callback(null, 0);
}
return misc.file.remove(ssh, pidfile, function(err, removed) {
if (err) {
return callback(err);
}
return callback(null, 2);
});
});
});
},
/*
`isPortOpen(port, host, callback)`: Check if a port is already open
*/
isPortOpen: function(port, host, callback) {
if (arguments.length === 2) {
callback = host;
host = '127.0.0.1';
}
return exec("nc " + host + " " + port + " < /dev/null", function(err, stdout, stderr) {
if (!err) {
return callback(null, true);
}
if (err.code === 1) {
return callback(null, false);
}
return callback(err);
});
},
/*
`merge([inverse], obj1, obj2, ...]`: Recursively merge objects
--------------------------------------------------------------
On matching keys, the last object take precedence over previous ones
unless the inverse arguments is provided as true. Only objects are
merge, arrays are overwritten.
Enrich an existing object with a second one:
obj1 = { a_key: 'a value', b_key: 'b value'}
obj2 = { b_key: 'new b value'}
result = misc.merge obj1, obj2
assert.eql result, obj1
assert.eql obj1.b_key, 'new b value'
Create a new object from two objects:
obj1 = { a_key: 'a value', b_key: 'b value'}
obj2 = { b_key: 'new b value'}
result = misc.merge {}, obj1, obj2
assert.eql result.b_key, 'new b value'
Using inverse:
obj1 = { b_key: 'b value'}
obj2 = { a_key: 'a value', b_key: 'new b value'}
misc.merge true, obj1, obj2
assert.eql obj1.a_key, 'a value'
assert.eql obj1.b_key, 'b value'
*/
merge: function() {
var clone, copy, from, i, inverse, name, options, src, target, to, _i;
target = arguments[0];
from = 1;
to = arguments.length;
if (typeof target === 'boolean') {
inverse = !!target;
target = arguments[1];
from = 2;
}
if (typeof target !== "object" && typeof target !== 'function') {
target = {};
}
for (i = _i = from; from <= to ? _i < to : _i > to; i = from <= to ? ++_i : --_i) {
if ((options = arguments[i]) !== null) {
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) {
continue;
}
if ((copy != null) && typeof copy === 'object' && !Array.isArray(copy) && !(copy instanceof RegExp)) {
clone = src && (src && typeof src === 'object' ? src : {});
target[name] = misc.merge(false, clone, copy);
} else if (copy !== void 0) {
if (!(inverse && typeof target[name] !== 'undefined')) {
target[name] = copy;
}
}
}
}
}
return target;
},
kadmin: function(options, cmd) {
var realm;
realm = options.realm ? "-r " + options.realm : '';
if (options.kadmin_principal) {
return "kadmin " + realm + " -p " + options.kadmin_principal + " -s " + options.kadmin_server + " -w " + options.kadmin_password + " -q '" + cmd + "'";
} else {
return "kadmin.local " + realm + " -q '" + cmd + "'";
}
},
ini: {
safe: function(val) {
if (typeof val !== "string" || val.match(/[\r\n]/) || val.match(/^\[/) || (val.length > 1 && val.charAt(0) === "\"" && val.slice(-1) === "\"") || val !== val.trim()) {
return JSON.stringify(val);
} else {
return val.replace(/;/g, '\\;');
}
},
dotSplit: function (str) {
return str.replace(/\1/g, '\2LITERAL\\1LITERAL\2')
.replace(/\\\./g, '\1')
.split(/\./).map(function (part) {
return part.replace(/\1/g, '\\.')
.replace(/\2LITERAL\\1LITERAL\2/g, '\1')
})
},
parse: function(content, options) {
return ini.parse(content);
},
/*
Each category is surrounded by one or several square brackets. The number of brackets indicates
the depth of the category.
Options are
* `comment` Default to ";"
*/
parse_multi_brackets: function(str, options) {
var comment, current, data, lines, stack;
if (options == null) {
options = {};
}
lines = str.split(/[\r\n]+/g);
current = data = {};
stack = [current];
comment = options.comment || ';';
lines.forEach(function(line, _, __) {
var depth, match, parent;
if (!line || line.match(/^\s*$/)) {
return;
}
if (match = line.match(/^\s*(\[+)(.+?)(\]+)\s*$/)) {
depth = match[1].length;
if (depth === stack.length) {
parent = stack[depth - 1];
parent[match[2]] = current = {};
stack.push(current);
}
if (depth > stack.length) {
throw new Error("Invalid child " + match[2]);
}
if (depth < stack.length) {
stack.splice(depth, stack.length - depth);
parent = stack[depth - 1];
parent[match[2]] = current = {};
return stack.push(current);
}
} else if (comment && (match = line.match(RegExp("^\\s*(" + comment + ".*)$")))) {
return current[match[1]] = null;
} else if (match = line.match(/^\s*(.+?)\s*=\s*(.+)\s*$/)) {
return current[match[1]] = match[2];
} else if (match = line.match(/^\s*(.+?)\s*$/)) {
return current[match[1]] = null;
}
});
return data;
},
stringify: function(obj, section, options) {
var children, dotSplit, eol, out, safe;
if (arguments.length === 2) {
options = section;
section = void 0;
}
if (options.separator == null) {
options.separator = ' = ';
}
eol = process.platform === "win32" ? "\r\n" : "\n";
safe = misc.ini.safe;
dotSplit = misc.ini.dotSplit;
children = [];
out = "";
Object.keys(obj).forEach(function(k, _, __) {
var val;
val = obj[k];
if (val && Array.isArray(val)) {
return val.forEach(function(item) {
return out += safe("" + k + "[]") + options.separator + safe(item) + "\n";
});
} else if (val && typeof val === "object") {
return children.push(k);
} else {
return out += safe(k) + options.separator + safe(val) + eol;
}
});
if (section && out.length) {
out = "[" + safe(section) + "]" + eol + out;
}
children.forEach(function(k, _, __) {
var child, nk;
nk = dotSplit(k).join('\\.');
child = misc.ini.stringify(obj[k], (section ? section + "." : "") + nk, options);
if (out.length && child.length) {
out += eol;
}
return out += child;
});
return out;
},
stringify_square_then_curly: function(content, depth, options) {
var element, i, indent, isArray, isBoolean, isNull, isObj, isUndefined, k, out, prefix, v, _i, _j, _len;
if (depth == null) {
depth = 0;
}
if (options == null) {
options = {};
}
if (arguments.length === 2) {
options = depth;
depth = 0;
}
if (options.separator == null) {
options.separator = ' = ';
}
out = '';
indent = ' ';
prefix = '';
for (i = _i = 0; 0 <= depth ? _i < depth : _i > depth; i = 0 <= depth ? ++_i : --_i) {
prefix += indent;
}
for (k in content) {
v = content[k];
isUndefined = typeof v === 'undefined';
isBoolean = typeof v === 'boolean';
isNull = v === null;
isArray = Array.isArray(v);
isObj = typeof v === 'object' && !isNull && !isArray;
if (isObj) {
if (depth === 0) {
out += "" + prefix + "[" + k + "]\n";
out += misc.ini.stringify_square_then_curly(v, depth + 1, options);
out += "\n";
} else {
out += "" + prefix + k + options.separator + "{\n";
out += misc.ini.stringify_square_then_curly(v, depth + 1, options);
out += "" + prefix + "}\n";
}
} else {
if (isArray) {
out = [];
for (_j = 0, _len = v.length; _j < _len; _j++) {
element = v[_j];
out.push("" + prefix + k + options.separator + element);
}
out = out.join('\n');
} else if (isNull) {
out += "" + prefix + k + options.separator + "null";
} else if (isBoolean) {
out += "" + prefix + k + options.separator + (v ? 'true' : 'false');
} else {
out += "" + prefix + k + options.separator + v;
}
out += '\n';
}
}
return out;
},
/*
Each category is surrounded by one or several square brackets. The number of brackets indicates
the depth of the category.
*/
stringify_multi_brackets: function(content, depth, options) {
var i, indent, isBoolean, isNull, isObj, isUndefined, k, out, prefix, v, _i;
if (depth == null) {
depth = 0;
}
if (options == null) {
options = {};
}
if (arguments.length === 2) {
options = depth;
depth = 0;
}
if (options.separator == null) {
options.separator = ' = ';
}
out = '';
indent = ' ';
prefix = '';
for (i = _i = 0; 0 <= depth ? _i < depth : _i > depth; i = 0 <= depth ? ++_i : --_i) {
prefix += indent;
}
for (k in content) {
v = content[k];
isUndefined = typeof v === 'undefined';
isBoolean = typeof v === 'boolean';
isNull = v === null;
isObj = typeof v === 'object' && !isNull;
if (isObj) {
continue;
}
if (isNull) {
out += "" + prefix + k;
} else if (isBoolean) {
out += "" + prefix + k + options.separator + (v ? 'true' : 'false');
} else {
out += "" + prefix + k + options.separator + v;
}
out += '\n';
}
for (k in content) {
v = content[k];
isNull = v === null;
isObj = typeof v === 'object' && !isNull;
if (!isObj) {
continue;
}
out += "" + prefix + (misc.string.repeat('[', depth + 1)) + k + (misc.string.repeat(']', depth + 1)) + "\n";
out += misc.ini.stringify_multi_brackets(v, depth + 1, options);
}
return out;
}
},
args: function(args, overwrite_goptions) {
if (overwrite_goptions == null) {
overwrite_goptions = {};
}
if (args.length === 2 && typeof args[1] === 'function') {
args[2] = args[1];
args[1] = args[0];
args[0] = null;
} else if (args.length === 1) {
args[1] = args[0];
args[0] = null;
}
if (args[0] == null) {
args[0] = misc.merge({
parallel: true
}, overwrite_goptions);
}
return args;
},
/*
`options(options, callback)`
----------------------------
Normalize options. An ssh connection is needed if the key "ssh"
hold a configuration object. The 'uid' and 'gid' fields will
be converted to integer if they match a username or a group.
`callback` Received parameters are:
* `err` Error object if any.
* `options` Sanitized options.
*/
options: function(options, callback) {
if (!Array.isArray(options)) {
options = [options];
}
return each(options).on('item', function(options, next) {
var connection, destination, el, gid, i, mode, source, uid, v, _i, _j, _len, _len1, _ref, _ref1;
if ((options["if"] != null) && !Array.isArray(options["if"])) {
options["if"] = [options["if"]];
}
if ((options.if_exists != null) && !Array.isArray(options.if_exists)) {
options.if_exists = [options.if_exists];
}
if ((options.not_if_exists != null) && !Array.isArray(options.not_if_exists)) {
options.not_if_exists = [options.not_if_exists];
}
if (options.if_exists) {
_ref = options.if_exists;
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
el = _ref[i];
if (el === true && options.destination) {
options.if_exists[i] = options.destination;
}
}
}
if (options.not_if_exists) {
_ref1 = options.not_if_exists;
for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
v = _ref1[i];
if (v === true && options.destination) {
options.not_if_exists[i] = options.destination;
}
}
}
if (options.chmod) {
if (options.mode == null) {
options.mode = options.chmod;
}
}
connection = function() {
if (!options.ssh) {
return source();
}
if (options.ssh._host) {
return source();
}
return connect(options.ssh, function(err, ssh) {
if (err) {
return next(err);
}
options.ssh = ssh;
return source();
});
};
source = function() {
if (options.source == null) {
return destination();
}
if (/^\w+:/.test(options.source)) {
return destination();
}
return tilde(options.source, function(source) {
options.source = source;
return destination();
});
};
destination = function() {
if (options.destination == null) {
return mode();
}
if (typeof options.destination !== 'string') {
return mode();
}
if (/^\w+:/.test(options.source)) {
return mode();
}
return tilde(options.destination, function(destination) {
options.destination = destination;
return mode();
});
};
mode = function() {
if (typeof options.mode === 'string') {
options.mode = parseInt(options.mode, 8);
}
return uid();
};
uid = function() {
if (!options.uid) {
return gid();
}
if (typeof options.uid === 'number' || /\d+/.test(options.uid)) {
return gid();
}
return misc.ssh.passwd(options.ssh, options.uid, function(err, user) {
if (err) {
return next(err);
}
if (user) {
options.uid = user.uid;
if (options.gid == null) {
options.gid = user.gid;
}
}
return gid();
});
};
gid = function() {
if (!options.gid) {
return next();
}
if (typeof options.gid === 'number' || /\d+/.test(options.gid)) {
return next();
}
return misc.ssh.group(options.ssh, options.gid, function(err, group) {
if (err) {
return next(err);
}
if (group) {
options.gid = group.gid;
}
return next();
});
};
return connection();
}).on('both', function(err) {
return callback(err, options);
});
}
};