237 lines
6.1 KiB
Plaintext
237 lines
6.1 KiB
Plaintext
// cell graph [<locator>] - Emit dependency graph
|
|
//
|
|
// Usage:
|
|
// cell graph Graph current directory package
|
|
// cell graph . Graph current directory package
|
|
// cell graph <locator> Graph specific package
|
|
// cell graph --world Graph all packages in shop (world set)
|
|
//
|
|
// Options:
|
|
// --format <fmt> Output format: tree (default), dot, json
|
|
// --resolved Show resolved view with links applied (default)
|
|
// --locked Show lock view without links
|
|
// --world Graph all packages in shop
|
|
|
|
var shop = use('internal/shop')
|
|
var pkg = use('package')
|
|
var link = use('link')
|
|
var fd = use('fd')
|
|
var json = use('json')
|
|
|
|
var target_locator = null
|
|
var format = 'tree'
|
|
var show_locked = false
|
|
var show_world = false
|
|
|
|
for (var i = 0; i < length(args); i++) {
|
|
if (args[i] == '--format' || args[i] == '-f') {
|
|
if (i + 1 < length(args)) {
|
|
format = args[++i]
|
|
if (format != 'tree' && format != 'dot' && format != 'json') {
|
|
log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
|
|
$stop()
|
|
}
|
|
} else {
|
|
log.error('--format requires a format type')
|
|
$stop()
|
|
}
|
|
} else if (args[i] == '--resolved') {
|
|
show_locked = false
|
|
} else if (args[i] == '--locked') {
|
|
show_locked = true
|
|
} else if (args[i] == '--world') {
|
|
show_world = true
|
|
} else if (args[i] == '--help' || args[i] == '-h') {
|
|
log.console("Usage: cell graph [<locator>] [options]")
|
|
log.console("")
|
|
log.console("Emit the dependency graph.")
|
|
log.console("")
|
|
log.console("Options:")
|
|
log.console(" --format <fmt> Output format: tree (default), dot, json")
|
|
log.console(" --resolved Show resolved view with links applied (default)")
|
|
log.console(" --locked Show lock view without links")
|
|
log.console(" --world Graph all packages in shop")
|
|
$stop()
|
|
} else if (!starts_with(args[i], '-')) {
|
|
target_locator = args[i]
|
|
}
|
|
}
|
|
|
|
var links = show_locked ? {} : link.load()
|
|
|
|
// Get effective locator (after links)
|
|
function get_effective(locator) {
|
|
return links[locator] || locator
|
|
}
|
|
|
|
// Build graph data structure
|
|
var nodes = {}
|
|
var edges = []
|
|
|
|
function add_node(locator) {
|
|
if (nodes[locator]) return
|
|
|
|
var lock = shop.load_lock()
|
|
var lock_entry = lock[locator]
|
|
var link_target = links[locator]
|
|
var info = shop.resolve_package_info(locator)
|
|
|
|
nodes[locator] = {
|
|
id: locator,
|
|
effective: get_effective(locator),
|
|
linked: link_target != null,
|
|
local: info == 'local',
|
|
commit: lock_entry && lock_entry.commit ? text(lock_entry.commit, 0, 8) : null
|
|
}
|
|
}
|
|
|
|
function gather_graph(locator, visited) {
|
|
if (visited[locator]) return
|
|
visited[locator] = true
|
|
|
|
add_node(locator)
|
|
|
|
try {
|
|
var deps = pkg.dependencies(locator)
|
|
if (deps) {
|
|
arrfor(array(deps), function(alias) {
|
|
var dep_locator = deps[alias]
|
|
add_node(dep_locator)
|
|
push(edges, { from: locator, to: dep_locator, alias: alias })
|
|
gather_graph(dep_locator, visited)
|
|
})
|
|
}
|
|
} catch (e) {
|
|
// Package might not have dependencies
|
|
}
|
|
}
|
|
|
|
// Gather graph from roots
|
|
var roots = []
|
|
|
|
if (show_world) {
|
|
// Use all packages in shop as roots
|
|
var packages = shop.list_packages()
|
|
arrfor(packages, function(p) {
|
|
if (p != 'core') {
|
|
push(roots, p)
|
|
}
|
|
})
|
|
} else {
|
|
// Default to current directory
|
|
if (!target_locator) {
|
|
target_locator = '.'
|
|
}
|
|
|
|
// Resolve local paths
|
|
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
|
|
var resolved = fd.realpath(target_locator)
|
|
if (resolved) {
|
|
target_locator = resolved
|
|
}
|
|
}
|
|
|
|
push(roots, target_locator)
|
|
}
|
|
|
|
arrfor(roots, function(root) {
|
|
gather_graph(root, {})
|
|
})
|
|
|
|
// Output based on format
|
|
if (format == 'tree') {
|
|
function print_tree(locator, prefix, is_last, visited) {
|
|
if (visited[locator]) {
|
|
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
|
|
return
|
|
}
|
|
visited[locator] = true
|
|
|
|
var node = nodes[locator]
|
|
var suffix = ""
|
|
if (node.linked) suffix += " -> " + node.effective
|
|
if (node.commit) suffix += " @" + node.commit
|
|
if (node.local) suffix += " (local)"
|
|
|
|
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
|
|
|
|
// Get children
|
|
var children = []
|
|
arrfor(edges, function(e) {
|
|
if (e.from == locator) {
|
|
push(children, e)
|
|
}
|
|
})
|
|
|
|
for (var i = 0; i < length(children); i++) {
|
|
var child_prefix = prefix + (is_last ? " " : "| ")
|
|
print_tree(children[i].to, child_prefix, i == length(children) - 1, visited)
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < length(roots); i++) {
|
|
log.console(roots[i])
|
|
|
|
var children = []
|
|
arrfor(edges, function(e) {
|
|
if (e.from == roots[i]) {
|
|
push(children, e)
|
|
}
|
|
})
|
|
|
|
for (var j = 0; j < length(children); j++) {
|
|
print_tree(children[j].to, "", j == length(children) - 1, {})
|
|
}
|
|
|
|
if (i < length(roots) - 1) log.console("")
|
|
}
|
|
|
|
} else if (format == 'dot') {
|
|
log.console("digraph dependencies {")
|
|
log.console(" rankdir=TB;")
|
|
log.console(" node [shape=box];")
|
|
log.console("")
|
|
|
|
// Node definitions
|
|
arrfor(array(nodes), function(id) {
|
|
var node = nodes[id]
|
|
var label = id
|
|
if (node.commit) label += "\\n@" + node.commit
|
|
var attrs = 'label="' + label + '"'
|
|
if (node.linked) attrs += ', style=dashed'
|
|
if (node.local) attrs += ', color=blue'
|
|
|
|
// Safe node ID for dot
|
|
var safe_id = replace(id, /[^a-zA-Z0-9]/g, '_')
|
|
log.console(' ' + safe_id + ' [' + attrs + '];')
|
|
})
|
|
|
|
log.console("")
|
|
|
|
// Edges
|
|
arrfor(edges, function(e) {
|
|
var from_id = replace(e.from, /[^a-zA-Z0-9]/g, '_')
|
|
var to_id = replace(e.to, /[^a-zA-Z0-9]/g, '_')
|
|
var label = e.alias != e.to ? 'label="' + e.alias + '"' : ''
|
|
log.console(' ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';')
|
|
})
|
|
|
|
log.console("}")
|
|
|
|
} else if (format == 'json') {
|
|
var output = {
|
|
nodes: [],
|
|
edges: []
|
|
}
|
|
|
|
arrfor(array(nodes), function(id) {
|
|
push(output.nodes, nodes[id])
|
|
})
|
|
|
|
output.edges = edges
|
|
|
|
log.console(json.encode(output))
|
|
}
|
|
|
|
$stop()
|