From a7ed9d461a00b27b24e0a368dde7fcff03a39569 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 30 Jan 2012 13:18:18 -0700 Subject: [PATCH] added json2yaml --- README.md | 73 +++++++++++++++++++++++++++++++++++++ cli.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++ example.json | 12 ++++++ example.yml | 10 +++++ index.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 21 +++++++++++ test.sh | 13 +++++++ tests/array.json | 22 +++++++++++ tests/object.json | 17 +++++++++ 9 files changed, 354 insertions(+) create mode 100644 README.md create mode 100644 cli.js create mode 100644 example.json create mode 100644 example.yml create mode 100644 index.js create mode 100644 package.json create mode 100755 test.sh create mode 100644 tests/array.json create mode 100644 tests/object.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a3f9f0 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +JSON to YAML +=== + +The purpose of this utility is to pretty-print JSON in the human-readable YAML object notation +(ignore the misnomer, it's not a markup language at all) + +You see, JSON is a proper subset of YAML, The difference is that YAML can use whitespace instead of syntax, which is more human-readable. +Also, YAML supports comments. + +So, for all the times you want to turn JSON int YAML (YML): + + { + "foo": "bar", + "baz": [ + "qux", + "quxx" + ], + "corge": null, + "grault": 1, + "garply": true, + "waldo": "false", + "fred": "undefined" + } + +becomes + + --- + foo: "bar" + baz: + - "qux" + - "quxx" + corge: null + grault: 1 + garply: true + waldo: "false" + fred: "undefined" + +Usage +--- + +Specify a file: + + json2yaml ./example.json + + yaml2json ./example.kml | json2yaml + +Or pipe from stdin: + + curl -s http://foobar3000.com/echo/echo.json | json2yaml + + wget -qO- http://foobar3000.com/echo/echo.json | json2yaml + +Or require: + + (function () { + "use strict"; + + var YAML = require('json2yaml') + , ymlText + ; + + ymlText = YAML.stringify({ + "foo": "bar" + , "baz": "corge" + }); + + console.log(ymlText); + }()); + +Installation +--- + + npm install -g json2yaml diff --git a/cli.js b/cli.js new file mode 100644 index 0000000..0084ee8 --- /dev/null +++ b/cli.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node +(function () { + "use strict"; + + var fs = require('fs') + , filename = process.argv[2] + , YAML = require('./index') + ; + + /* + * + * Begin real handler + * + */ + + function printUsage() { + console.warn("Usages:"); + console.warn("json2yaml example.json"); + console.warn("cat example.json | json2yaml"); + } + + function handleInput(err, text) { + var data + ; + + if (err) { + printUsage(); + return; + } + + data = JSON.parse(text); + console.info(YAML.stringify(data, null, ' ')); + } + + /* + * + * End real handler + * + */ + + readInput(handleInput, filename); + + // + // this could (and probably should) be its own module + // + function readInput(cb, filename) { + + function readFile() { + fs.readFile(filename, 'utf8', function (err, text) { + if (err) { + console.error("[ERROR] couldn't read from '" + filename + "':"); + console.error(err.message); + return; + } + + cb(err, text); + }); + } + + function readStdin() { + var text = '' + , timeoutToken + , stdin = process.stdin + ; + + stdin.resume(); + + // how to tell piping vs waiting for user input? + timeoutToken = setTimeout(function () { + cb(new Error('no stdin data')); + stdin.pause(); + }, 1000); + + stdin.on('data', function (chunk) { + clearTimeout(timeoutToken); + text += chunk; + }); + + stdin.on('end', function () { + cb(null, text); + }); + } + + if (filename) { + readFile(); + } + else { + readStdin(); + } + + } + +}()); diff --git a/example.json b/example.json new file mode 100644 index 0000000..ff217aa --- /dev/null +++ b/example.json @@ -0,0 +1,12 @@ +{ + "foo": "bar", + "baz": [ + "qux", + "quxx" + ], + "corge": null, + "grault": 1, + "garply": true, + "waldo": "false", + "fred": "undefined" +} diff --git a/example.yml b/example.yml new file mode 100644 index 0000000..cd88ea5 --- /dev/null +++ b/example.yml @@ -0,0 +1,10 @@ +--- + foo: bar + baz: + - qux + - quxx + corge: null + grault: 1 + garply: true + waldo: "false" + fred: undefined diff --git a/index.js b/index.js new file mode 100644 index 0000000..ea2e8ab --- /dev/null +++ b/index.js @@ -0,0 +1,93 @@ +(function () { + "use strict"; + + var typeOf = require('remedial').typeOf + ; + + function stringify(data) { + var handlers + , indentLevel = '' + ; + + handlers = { + "undefined": function () { + // objects will not have `undefined` converted to `null` + // as this may have unintended consequences + // For arrays, however, this behavior seems appropriate + return 'null'; + } + , "null": function () { + return 'null'; + } + , "number": function (x) { + return x; + } + , "boolean": function (x) { + return x ? 'true' : 'false'; + } + , "string": function (x) { + // to avoid the string "true" being confused with the + // the literal `true`, we always wrap strings in quotes + return JSON.stringify(x); + } + , "array": function (x) { + var output = '' + ; + + indentLevel = indentLevel.replace(/$/, ' '); + x.forEach(function (y) { + // TODO how should `undefined` be handled? + var handler = handlers[typeOf(y)] + ; + + if (!handler) { + throw new Error('what the crap: ' + typeOf(y)); + } + + output += '\n' + indentLevel + '- ' + handler(y); + + }); + indentLevel = indentLevel.replace(/ /, ''); + + return output; + } + , "object": function (x) { + var output = '' + ; + + indentLevel = indentLevel.replace(/$/, ' '); + Object.keys(x).forEach(function (k) { + var val = x[k] + , handler = handlers[typeOf(val)] + ; + + if ('undefined' === typeof val) { + // the user should do + // delete obj.key + // and not + // obj.key = undefined + // but we'll error on the side of caution + return; + } + + if (!handler) { + throw new Error('what the crap: ' + typeOf(val)); + } + + output += '\n' + indentLevel + k + ': ' + handler(val); + }); + indentLevel = indentLevel.replace(/ /, ''); + + return output; + } + , "function": function () { + // TODO this should throw or otherwise be ignored + return '[object Function]'; + } + }; + + return '---' + handlers[typeOf(data)](data); + } + + module.exports.stringify = stringify; +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..332bef3 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "author": "AJ ONeal (http://coolaj86.info)", + "name": "json2yaml", + "description": "A commandline utility to convert JSON to YAML / YML", + "keywords": ["yml", "yaml", "json", "cli", "util"], + "version": "1.0.0", + "main": "index.js", + "bin": { + "json2yaml": "./cli.js", + "json2yml": "./cli.js" + }, + "engines": { + "node": ">= 0.2.0" + }, + "test": ["a", "b", "c"], + "dependencies": { + "remedial": "1.x" + }, + "devDependencies": {}, + "preferGlobal": true +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..ba7e381 --- /dev/null +++ b/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# npm install -g yaml2json +node cli.js tests/object.json | yaml2json > /dev/null +node cli.js tests/array.json | yaml2json > /dev/null +# These tests would probably fail and seem a moot point to me +# Why use YAML for literal values? It's general used for config +# files will multiple teirs of data +#node cli.js tests/string.json | yaml2json +#node cli.js tests/number.json | yaml2json +#node cli.js tests/boolean.json | yaml2json +#node cli.js tests/null.json | yaml2json +echo "Passed if no errors are listed above (and yaml2json is installed)" diff --git a/tests/array.json b/tests/array.json new file mode 100644 index 0000000..d38d42e --- /dev/null +++ b/tests/array.json @@ -0,0 +1,22 @@ +[ + "baz", + "qux", + "quxx", + null, + true, + { + "foo": "bar", + "corge": null, + "grault": 1, + "garply": true, + "waldo": "false", + "fred": "undefined" + }, + [ + "hello", + "world" + ], + 42, + [ + ] +] diff --git a/tests/object.json b/tests/object.json new file mode 100644 index 0000000..66b956c --- /dev/null +++ b/tests/object.json @@ -0,0 +1,17 @@ +{ + "foo": "bar", + "baz": [ + "qux", + "quxx" + ], + "corge": { + "grault": 1, + "garply": true, + "waldo": "false", + "fred": null + }, + "empty": { + }, + "hello": "world", + "answer": 42 +}