Tree.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. final class Tree<Element> {
  12. var element: Element
  13. weak var parent: Tree?
  14. var children: [Tree]
  15. var isRoot: Bool { parent == nil }
  16. var isLeaf: Bool { children.isEmpty }
  17. var hasChildren: Bool { !isLeaf }
  18. init(_ element: Element) {
  19. self.element = element
  20. self.parent = nil
  21. self.children = []
  22. }
  23. func addChild(_ tree: Tree) {
  24. children.append(tree)
  25. tree.parent = self
  26. }
  27. }
  28. extension Tree: Hashable {
  29. static func == (lhs: Tree<Element>, rhs: Tree<Element>) -> Bool {
  30. lhs === rhs
  31. }
  32. func hash(into hasher: inout Hasher) {
  33. hasher.combine(ObjectIdentifier(self))
  34. }
  35. }
  36. extension Tree {
  37. /// Returns a path of tree nodes that traverses from this node to the first
  38. /// node (breadth-first) that matches the given predicate.
  39. func path(toFirstWhere predicate: (Element) -> Bool) -> [Tree] {
  40. var visited: Set<Tree> = []
  41. var toVisit: [Tree] = [self]
  42. var currentIndex = 0
  43. // For each node, the neighbor that is most efficiently used to reach
  44. // that node.
  45. var cameFrom: [Tree: Tree] = [:]
  46. while let current = toVisit[currentIndex...].first {
  47. currentIndex += 1
  48. if predicate(current.element) {
  49. // Reconstruct the path from `self` to `current`.
  50. return sequence(first: current, next: { cameFrom[$0] }).reversed()
  51. }
  52. visited.insert(current)
  53. for child in current.children where !visited.contains(child) {
  54. if !toVisit.contains(child) {
  55. toVisit.append(child)
  56. }
  57. // Coming from `current` is the best path to `neighbor`.
  58. cameFrom[child] = current
  59. }
  60. }
  61. // Didn't find a path!
  62. return []
  63. }
  64. }
  65. extension Tree where Element == ParsableCommand.Type {
  66. func path(to element: Element) -> [Element] {
  67. path(toFirstWhere: { $0 == element }).map { $0.element }
  68. }
  69. func firstChild(equalTo element: Element) -> Tree? {
  70. children.first(where: { $0.element == element })
  71. }
  72. func firstChild(withName name: String) -> Tree? {
  73. children.first(where: {
  74. $0.element._commandName == name
  75. || $0.element.configuration.aliases.contains(name)
  76. })
  77. }
  78. convenience init(root command: ParsableCommand.Type) throws {
  79. self.init(command)
  80. for subcommand in command.configuration.subcommands {
  81. if subcommand == command {
  82. throw InitializationError.recursiveSubcommand(subcommand)
  83. }
  84. // We don't allow an alias that has the same name as the command itself.
  85. if subcommand.configuration.aliases.contains(subcommand._commandName) {
  86. throw InitializationError.aliasMatchingCommand(subcommand)
  87. }
  88. try addChild(Tree(root: subcommand))
  89. }
  90. }
  91. enum InitializationError: Error {
  92. case recursiveSubcommand(ParsableCommand.Type)
  93. case aliasMatchingCommand(ParsableCommand.Type)
  94. }
  95. }