diff --git a/example.conf b/example.conf new file mode 100644 index 0000000..4ed27fa --- /dev/null +++ b/example.conf @@ -0,0 +1,62 @@ +cert = /etc/letsencrypt/live/example.com/cert.pem +privkey = /etc/letsencrypt/live/example.com/privkey.pem +chain = /etc/letsencrypt/live/example.com/chain.pem +fullchain = /etc/letsencrypt/live/example.com/fullchain.pem + +# Options and defaults used in the renewal process +[renewalparams] +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +uir = None +installer = none +config_dir = /etc/letsencrypt +text_mode = False +func = +prepare = False +work_dir = /var/lib/letsencrypt +tos = True +init = False +http01_port = 80 +duplicate = False +key_path = None +nginx = False +fullchain_path = /home/user/letsencrypt/chain.pem +email = user@example.com +csr = None +agree_dev_preview = None +redirect = None +verbose_count = -3 +config_file = None +renew_by_default = False +hsts = False +authenticator = webroot +domains = example.com, +rsa_key_size = 2048 +checkpoints = 1 +manual_test_mode = False +apache = False +cert_path = /home/user/letsencrypt/cert.pem +webroot_path = /srv/www/example.com/, +strict_permissions = False +apache_server_root = /etc/apache2 +account = f4c33502df3789849f617944253b35ae +manual_public_ip_logging_ok = False +chain_path = /home/user/letsencrypt/chain.pem +standalone = False +manual = False +server = https://acme-v01.api.letsencrypt.org/directory +standalone_supported_challenges = "http-01,tls-sni-01" +webroot = True +apache_init_script = None +user_agent = None +apache_ctl = apache2ctl +apache_le_vhost_ext = -le-ssl.conf +debug = False +tls_sni_01_port = 443 +logs_dir = /var/log/letsencrypt +configurator = None +[[webroot_map]] +example.com = /srv/www/example.com/ diff --git a/index.js b/index.js new file mode 100644 index 0000000..a96898d --- /dev/null +++ b/index.js @@ -0,0 +1,173 @@ +'use strict'; + +var fs = require('fs'); +var sfs = require('safe-replace').create(); + +function snakeCase(key) { + if ('tlsSni01Port' === key) { + return 'tls_sni_01_port'; + } + /* + else if ('http01Port' === key) { + return 'http01-port'; + } + */ + else { + return key.replace(/([A-Z])/g, '_$1').toLowerCase(); + } +} + +function uc(match, c) { + return c.toUpperCase(); +} + +function camelCase(key) { + return key.replace(/_([a-z0-9])/g, uc); +} + +function parsePythonConf(str, cb) { + var keys = {}; + var obj = {}; + var lines = str.split('\n'); + + lines.forEach(function (line, i) { + line = line.replace(/#.*/, '').trim(); + + if (!line) { return; } + + var parts = line.trim().split('='); + var pykey = parts.shift().trim(); + var key = camelCase(pykey); + var val = parts.join('=').trim(); + + if ('True' === val) { + val = true; + } + else if ('False' === val) { + val = false; + } + else if ('None' === val) { + val = null; + } + else if (/,/.test(val) && !/^"[^"]*"$/.test(val)) { + val = val.split(','); + } + else if (/^[0-9]+$/.test(val)) { + val = parseInt(val, 10); + } + + obj[key] = val; + if ('undefined' !== typeof keys[key]) { + console.warn("unexpected duplicate key '" + key + "': '" + val + "'"); + } + + keys[key] = i; + }); + + // we want to be able to rewrite the file with comments, etc + obj.__keys = keys; + obj.__lines = lines; + + cb(null, obj); +} + +function toPyVal(val) { + if (null === val) { + return 'None'; + } + else if (true === val) { + return 'True'; + } + else if (false === val) { + return 'False'; + } + else if ('string' === typeof val) { + return val; + } + else if ('number' === typeof val) { + return val; + } + else if (Array.isArray(val)) { + return val.join(','); + } + + return val && JSON.stringify(val); +} + +function stringifyPythonConf(obj, cb) { + var endline; + + // nix the final newline + if (!obj.__lines[obj.__lines.length - 1].trim()) { + endline = obj.__lines.pop(); + } + + Object.keys(obj).forEach(function (key) { + if ('__' === key.slice(0, 2)) { + return; + } + + var pykey = snakeCase(key); + var pyval = toPyVal(obj[key]); + var num = obj.__keys[key]; + var comment = ''; + + + if ('number' !== typeof num) { + obj.__lines.push(pykey + ' = ' + pyval); + return; + } + + // restore comments + if (-1 !== obj.__lines[num].indexOf('#')) { + comment = obj.__lines[num].replace(/.*?(\s*#.*)/, '$1'); + } + + if ('[' === pykey[0]) { + return; + } + + if ('undefined' === typeof pyval) { + obj.__lines[num] = "___DELETE_ME___"; + } + }); + + obj.__lines = obj.__lines.filter(function (line) { + if ("___DELETE_ME___" !== line) { + return true; + } + }); + + if ('string' === typeof endline) { + obj.__lines.push(endline); + } + + cb(null, obj.__lines.join('\n')); +} + +function writePythonConfFile(pathname, obj, cb) { + // TODO re-read file? + stringifyPythonConf(obj, function (err, text) { + sfs.writeFileAsync(pathname, text, 'utf8').then(function () { + cb(null, null); + }, function (err) { + cb(err); + }); + }); +} + +function parsePythonConfFile(pathname, cb) { + fs.readFile(pathname, 'utf8', function (err, text) { + if (err) { + cb(err); + return; + } + + parsePythonConf(text, cb); + }); +} + +module.exports.parse = parsePythonConf; +module.exports.readFile = parsePythonConfFile; +module.exports.stringify = stringifyPythonConf; +module.exports.writeFile = writePythonConfFile; diff --git a/package.json b/package.json new file mode 100644 index 0000000..407e784 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "pyconf", + "version": "1.0.0", + "description": "Read and write python config files non-destructively (preserves comments)", + "main": "index.js", + "scripts": { + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/coolaj86/node-config-python.git" + }, + "keywords": [ + "python", + "conf", + "config", + "comments" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "(MIT OR Apache-2.0)", + "bugs": { + "url": "https://github.com/coolaj86/node-config-python/issues" + }, + "homepage": "https://github.com/coolaj86/node-config-python#readme", + "dependencies": { + "safe-replace": "^1.0.2" + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..ace8c2e --- /dev/null +++ b/test.js @@ -0,0 +1,20 @@ +'use strict'; +//var pyconf = require('pyconf'); +var pyconf = require('./'); + +pyconf.readFile('example.conf', function (err, obj) { + if (err) { + console.error(err.stack); + return; + } + + pyconf.writeFile('example.conf.new', obj, function (err) { + if (err) { + console.error(err.stack); + return; + } + + console.log("Run this command to check that the outputs are the same:"); + console.log(" diff example.conf example.conf.new"); + }); +});