diff --git a/bin/oconf-tree b/bin/oconf-tree new file mode 100755 index 0000000..b3ac932 --- /dev/null +++ b/bin/oconf-tree @@ -0,0 +1,153 @@ +#!/usr/bin/env node +// ex: filetype=javascript + +var oconf = require('../lib/index'), + util = require('util'), + path = require('path'), + cjson = require('cjson'), + optimist = require('optimist'); + +var argv = optimist + .demand(1) + .usage('$0 ') + .options('r', { + alias: 'reverse', + default: false, + description: 'Start at the root (needs to include all files on command-line)' + }) + /*.options('t', { + alias: 'top-down', + default: true, + description: 'Show from the top down' + })*/ + .boolean('t', 'b') + .argv; + +/* shortestFilename([base,] name) + */ +function shortestFilename(base, name) { + if (arguments.length === 1) { + name = base; + base = process.cwd(); + } + var rel = path.relative(base, name), + abs = path.resolve(base, name); + + // Ensure rel has ./ prefixed if nothing else + if (path.basename(rel) === rel) { + rel = './' + rel; + } + + return rel.length > abs.length ? abs : rel; +} + +var fileList = argv._.map(function (filename) { + return shortestFilename(filename); +}); + +var filesToLookAt = fileList, + filesLookedAt = []; + +function discoverIncludes(obj, currentFile, loadedFiles) { + //console.warn("→ discoverIncludes(" + i(obj) + ", " + currentFile + ", " + loadedFiles + ")"); + var includes = []; + + if (Array.isArray(obj)) { + obj.forEach(function (element) { + discoverIncludes(element, currentFile, loadedFiles).forEach(function (n) { + includes.push(n); + }); + }); + } else if (typeof obj === 'object' && obj !== null) { + // If there's any data to mix in, we do that. + if (obj['#include'] !== undefined) { + (Array.isArray(obj['#include']) ? obj['#include'].reverse() : [obj['#include']]).forEach(function (include) { + // Figure out the target filename. + var dirname = path.dirname(currentFile), + newFilename = shortestFilename(path.resolve(dirname, include)); + includes.push(newFilename); + + // Should we look at this new file? + if ( + filesToLookAt.indexOf(newFilename) === -1 && + filesLookedAt.indexOf(newFilename) === -1 + ) { + filesToLookAt.push(newFilename); + } + }); + } + + // Discover any and all sub-includes + Object.keys(obj).forEach(function (key) { + discoverIncludes(obj[key], currentFile, loadedFiles).forEach(function (newInclude) { + includes.push(newInclude); + }); + }); + } + + //console.warn("← discoverIncludes(" + i(includes) + ")"); + return includes; +} + +// Lookup-tables for resolving includes +var includes = {}, + included = {}; + +// Load all the given oconf files and record what they are supposed to load +while(filesToLookAt.length > 0) { + var filename = filesToLookAt.pop(); + filesLookedAt.push(filename); + + //var fullFilename = path.normalize(path.resolve(filename)); + var fullFilename = path.relative(process.cwd(), filename); + + var data = cjson.load(fullFilename), + inc = discoverIncludes(data, filename, []); + + //console.log("%s\tincludes %s", fullFilename, inc); + + inc.forEach(function (i) { + // Note what this file includes + if (filename in includes) { includes[filename].push(i) } + else { includes[filename] = [i] } + + // Reverse lookups + if (i in included) { included[i].push(filename) } + else { included[i] = [filename] } + }); +} + + +// OUTPUT +function printTree(indent, branch, parentPath, childLookup) { + console.log( + "%s%s", + Array(indent).join(" "), + shortestFilename(parentPath, branch) + ) + childLookup(branch).sort().forEach(function (child) { + printTree(indent + 2, child, path.dirname(branch), childLookup); + }); +} + +// Print reversen tree +if (argv['reverse']) { + //console.log(included) + filesLookedAt.filter(function (filename) { + return !(filename in includes); + }).sort().forEach(function (filename) { + printTree(1, filename, process.cwd(), function (branch) { + return included[branch] || []; + }); + }); +} else { + // Print top-down tree + //console.log(includes); + filesLookedAt.filter(function (filename) { + return !(filename in included); + }).sort().forEach(function (filename) { + printTree(1, filename, process.cwd(), function (branch) { + return includes[branch] || []; + }); + }); +}