185 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| var fs = require('fs');
 | |
| var sfs = require('safe-replace').create();
 | |
| 
 | |
| function snakeCase(key) {
 | |
|   // TODO let user supply list of exceptions
 | |
|   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.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) {
 | |
|       val = null;
 | |
|     }
 | |
|     else if (/,/.test(val) && !/^"[^"]*"$/.test(val)) {
 | |
|       val = val.split(',').map(function(x) { return x.trim(); });
 | |
|     }
 | |
|     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 || '' === 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)) {
 | |
|     val = val.join(',');
 | |
|     if (-1 === val.indexOf(',')) {
 | |
|       val += ','; // disambiguates value from array with one element
 | |
|     }
 | |
|     return val;
 | |
|   }
 | |
| 
 | |
|   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 ('undefined' === typeof pyval) {
 | |
|       if ('number' === typeof num) {
 | |
|         pyval = 'None';
 | |
|       } else {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if ('number' !== typeof num) {
 | |
|       obj.__lines.push(pykey + ' = ' + pyval);
 | |
|       obj.__keys[key] = obj.__lines.length - 1;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if ('[' === pykey[0]) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!obj.__lines[num] || !obj.__lines[num].indexOf) {
 | |
|       console.warn('[pyconf] WARN index past array length:');
 | |
|       console.log(obj.__lines.length, num, obj.__lines[num]);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // restore comments
 | |
|     if (-1 !== obj.__lines[num].indexOf('#')) {
 | |
|       comment = obj.__lines[num].replace(/.*?(\s*#.*)/, '$1');
 | |
|     }
 | |
| 
 | |
|     obj.__lines[num] = pykey + ' = ' + pyval + comment;
 | |
|   });
 | |
| 
 | |
|   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;
 |