text: #!/usr/bin/env python3
  
  from executils import captureStdout
  
  from collections import defaultdict, namedtuple
  from os.path import normpath
  import re, sys
  
  
  Symbol = namedtuple('Symbol', ('name', 'typ', 'size', 'source', 'lineNo'))
  
  _reSymbolInfo = re.compile(
  	r'^([0-9a-f]+\s)?([0-9a-f]+\s)?\s*([A-Za-z])\s([^\t]+)(\t[^\t]+)?$'
  	)
  def parseSymbolSize(objectFile):
  	text = captureStdout(sys.stderr, 'nm -CSl "%s"' % objectFile)
  	if text is not None:
  		for line in text.split('\n'):
  			if line:
  				match = _reSymbolInfo.match(line)
  				assert match is not None, line
  				addr_, sizeStr, typ, name, originStr = match.groups()
  				if sizeStr is None:
  					continue
  				if typ in 'Bb':
  					# Symbols in BSS (uninitialized data section) do not
  					# contribute to executable size, so ignore them.
  					continue
  				if originStr is None:
  					source = lineNo = None
  				else:
  					source, lineNo = originStr.lstrip().rsplit(':', 1)
  					source = normpath(source)
  					lineNo = int(lineNo)
  				yield Symbol(name, typ, int(sizeStr, 16), source, lineNo)
  
  if __name__ == '__main__':
  	if len(sys.argv) == 2:
  		executable = sys.argv[1]
  
  		# Get symbol information.
  		symbolsBySource = defaultdict(list)
  		for symbol in parseSymbolSize(executable):
  			symbolsBySource[symbol.source].append(symbol)
  
  		# Build directory tree.
  		def newDict():
  			return defaultdict(newDict)
  		dirTree = newDict()
  		for source, symbols in symbolsBySource.items():
  			if source is None:
  				parts = [ '(no source)' ]
  			else:
  				assert source[0] == '/'
  				parts = source[1 : ].split('/')
  				parts[0] = '/' + parts[0]
  			node = dirTree
  			for part in parts[ : -1]:
  				node = node[part + '/']
  			node[parts[-1]] = symbols
  
  		# Combine branches without forks.
  		def compactTree(node):
  			names = set(node.keys())
  			while names:
  				name = names.pop()
  				content = node[name]
  				if isinstance(content, dict) and len(content) == 1:
  					subName, subContent = next(iter(content.items()))
  					if isinstance(subContent, dict):
  						# A directory containing a single directory.
  						del node[name]
  						node[name + subName] = subContent
  						names.add(name + subName)
  			for content in node.values():
  				if isinstance(content, dict):
  					compactTree(content)
  		compactTree(dirTree)
  
  		# Compute size of all nodes in the tree.
  		def buildSizeTree(node):
  			if isinstance(node, dict):
  				newNode = {}
  				for name, content in node.items():
  					newNode[name] = buildSizeTree(content)
  				nodeSize = sum(size for size, subNode in newNode.values())
  				return nodeSize, newNode
  			else:
  				nodeSize = sum(symbol.size for symbol in node)
  				return nodeSize, node
  		totalSize, sizeTree = buildSizeTree(dirTree)
  
  		# Output.
  		def printTree(size, node, indent):
  			if isinstance(node, dict):
  				for name, (contentSize, content) in sorted(
  						node.items(),
  						key=lambda item: (-item[1][0], item[0])
  						):
  					print('%s%8d %s' % (indent, contentSize, name))
  					printTree(contentSize, content, indent + '  ')
  			else:
  				for symbol in sorted(
  						node,
  						key=lambda symbol: (-symbol.size, symbol.name)
  						):
  					lineNo = symbol.lineNo
  					print('%s%8d %s %s %s' % (
  						indent, symbol.size, symbol.typ, symbol.name,
  						'' if lineNo is None else '(line %d)' % lineNo
  						))
  		printTree(totalSize, sizeTree, '')
  	else:
  		print('Usage: python3 sizestats.py executable', file=sys.stderr)
  		sys.exit(2)
  