-- -- tree.lua -- Functions for working with the source code tree. -- Copyright (c) 2009-2013 Jason Perkins and the Premake project -- local p = premake p.tree = {} local tree = p.tree -- -- Create a new tree. -- -- @param n -- The name of the tree, applied to the root node (optional). -- function tree.new(n) local t = { name = n, children = {} } return t end -- -- Add a new node to the tree, or returns the current node if it already exists. -- -- @param tr -- The tree to contain the new node. -- @param p -- The path of the new node. -- @param extraFields -- A table containing key-value pairs to be added to any new nodes. -- @returns -- The new tree node. -- function tree.add(tr, p, extraFields) -- Special case "." refers to the current node if p == "." or p == "/" then return tr end -- Look for the immediate parent for this new node, creating it if necessary. -- Recurses to create as much of the tree as necessary. local parentnode = tree.add(tr, path.getdirectory(p), extraFields) -- Create the child if necessary local childname = path.getname(p) local childnode = parentnode.children[childname] if not childnode or childnode.path ~= p then childnode = tree.insert(parentnode, tree.new(childname)) childnode.path = p if extraFields then for k,v in pairs(extraFields) do childnode[k] = v end end end return childnode end -- -- Insert one tree into another. -- -- @param parent -- The parent tree, to contain the child. -- @param child -- The child tree, to be inserted. -- function tree.insert(parent, child) table.insert(parent.children, child) if child.name then parent.children[child.name] = child end child.parent = parent return child end -- -- Gets the node's relative path from it's parent. If the parent does not have -- a path set (it is the root or other container node) returns the full node path. -- -- @param node -- The node to query. -- function tree.getlocalpath(node) if node.parent.path then return node.name elseif node.cfg then return node.cfg.name else return node.path end end -- -- Determines if the tree contains any branch nodes, or only leaves. -- -- @param tr -- The root node of the tree to query. -- @return -- True if a node below the root contains children, false otherwise. -- function tree.hasbranches(tr) local n = #tr.children if n > 0 then for i = 1, n do if #tr.children[i].children > 0 then return true end end end return false end -- -- Determines if one node is a parent if another. -- -- @param n -- The node being tested for parentage. -- @param child -- The child node being testing against. -- @return -- True if n is a parent of child. -- function tree.isparent(n, child) local p = child.parent while p do if p == n then return true end p = p.parent end return false end -- -- Remove a node from a tree. -- -- @param node -- The node to remove. -- function tree.remove(node) local children = node.parent.children for i = 1, #children do if children[i] == node then table.remove(children, i) end end node.children = {} end -- -- Sort the nodes of a tree in-place. -- -- @param tr -- The tree to sort. -- @param fn -- An optional comparator function. -- function tree.sort(tr, fn) if not fn then fn = function(a,b) return a.name < b.name end end tree.traverse(tr, { onnode = function(node) table.sort(node.children, fn) end }, true) end -- -- Traverse a tree. -- -- @param t -- The tree to traverse. -- @param fn -- A collection of callback functions, which may contain any or all of the -- following entries. Entries are called in this order. -- -- onnode - called on each node encountered -- onbranchenter - called on branches, before processing children -- onbranch - called only on branch nodes -- onleaf - called only on leaf nodes -- onbranchexit - called on branches, after processing children -- -- Callbacks receive two arguments: the node being processed, and the -- current traversal depth. -- -- @param includeroot -- True to include the root node in the traversal, otherwise it will be skipped. -- @param initialdepth -- An optional starting value for the traversal depth; defaults to zero. -- function tree.traverse(t, fn, includeroot, initialdepth) -- forward declare my handlers, which call each other local donode, dochildren -- process an individual node donode = function(node, fn, depth) if node.isremoved then return end if fn.onnode then fn.onnode(node, depth) end if #node.children > 0 then if fn.onbranchenter then fn.onbranchenter(node, depth) end if fn.onbranch then fn.onbranch(node, depth) end dochildren(node, fn, depth + 1) if fn.onbranchexit then fn.onbranchexit(node, depth) end else if fn.onleaf then fn.onleaf(node, depth) end end end -- this goofy iterator allows nodes to be removed during the traversal dochildren = function(parent, fn, depth) local i = 1 while i <= #parent.children do local node = parent.children[i] donode(node, fn, depth) if node == parent.children[i] then i = i + 1 end end end -- set a default initial traversal depth, if one wasn't set if not initialdepth then initialdepth = 0 end if includeroot then donode(t, fn, initialdepth) else dochildren(t, fn, initialdepth) end end -- -- Starting at the top of the tree, remove nodes that contain only a single -- item until I hit a node that has multiple items. This is used to remove -- superfluous folders from the top of the source tree. -- function tree.trimroot(tr) local trimmed -- start by removing single-children folders from the top of the tree while #tr.children == 1 do local node = tr.children[1] -- if this node has no children (it is the last node in the tree) I'm done if #node.children == 0 or node.trim == false then break end -- remove this node from the tree, and move its children up a level trimmed = true local numChildren = #node.children for i = 1, numChildren do local child = node.children[i] child.parent = node.parent tr.children[i] = child end end -- found the top, now remove any single-children ".." folders from here local dotdot local count = #tr.children repeat dotdot = false for i = 1, count do local node = tr.children[i] if node.name == ".." and #node.children == 1 then local child = node.children[1] child.parent = node.parent tr.children[i] = child trimmed = true dotdot = true end end until not dotdot -- if nodes were removed, adjust the paths on all remaining nodes if trimmed then tree.traverse(tr, { onnode = function(node) if node.parent.path then node.path = path.join(node.parent.path, node.name) else node.path = node.name end end }, false) end end