Class: Kube::Helm::CommandTree

Inherits:
Object
  • Object
show all
Defined in:
lib/kube/helm/command_tree.rb

Defined Under Namespace

Classes: Result

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ CommandTree

Parses helm.yaml’s flat commands array.

Each entry has a ‘name` like “helm”, “helm install”, “helm get values”. We split on spaces, skip the leading “helm” token, and insert into a tree rooted at a synthetic “helm” node.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/kube/helm/command_tree.rb', line 24

def initialize(data)
  @root = Kube::Ctl::CommandTree::Node.new(name: "helm")

  data.fetch("commands", []).each do |cmd|
    name = cmd["name"]
    next unless name

    parts = name.split
    # Skip the root "helm" entry itself (no subcommand path)
    next if parts.size <= 1

    # Walk/create intermediate nodes, attach leaf with full metadata
    node = @root
    parts[1..].each_with_index do |part, idx|
      existing = node.find_subcommand(part)
      if idx == parts.size - 2
        # Leaf node — build with full options/inherited_options/usage
        if existing
          # Node was pre-created as an intermediate; we can't easily
          # replace it, but the intermediate was created bare. In
          # practice the YAML lists parent commands before children,
          # so the parent entry is processed first and the intermediate
          # won't exist yet when we hit the leaf. But just in case,
          # use the existing node.
          node = existing
        else
          leaf = Kube::Ctl::CommandTree::Node.new(
            name: part,
            options: cmd["options"] || [],
            inherited_options: cmd["inherited_options"] || [],
            usage: cmd["usage"]
          )
          node.add_subcommand(leaf)
          node = leaf
        end
      else
        if existing
          node = existing
        else
          # Create a bare intermediate node
          intermediate = Kube::Ctl::CommandTree::Node.new(name: part)
          node.add_subcommand(intermediate)
          node = intermediate
        end
      end
    end
  end
end

Instance Method Details

#evaluate(builder) ⇒ Object

Evaluate a StringBuilder buffer against the helm command tree.

Classifies tokens as:

- commands:   matched subcommand path (e.g. ["repo", "add"])
- positional: bare tokens after commands (release names, chart refs, URLs)
- flags:      tokens with arguments (--namespace default, --set foo=bar)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/kube/helm/command_tree.rb', line 79

def evaluate(builder)
  buffer = builder.to_a
  commands = []
  positional = []
  flags = []
  errors = []

  node = @root
  i = 0

  # 1. Walk commands/subcommands
  while i < buffer.length
    entry = buffer[i]
    break unless entry.is_a?(Array)
    name, args = entry
    break unless args.empty?

    child = node.find_subcommand(name)
    break unless child

    commands << name
    node = child
    i += 1

    # Consume :dash separated subcommand parts (e.g. insecure-skip-tls-verify)
    while i < buffer.length && buffer[i] == :dash
      next_i = i + 1
      break unless next_i < buffer.length && buffer[next_i].is_a?(Array)
      next_name, next_args = buffer[next_i]
      break unless next_args.empty?
      hyphenated = "#{commands.last}-#{next_name}"
      child = node.find_subcommand(hyphenated)
      if child
        commands[-1] = hyphenated
        node = child
        i = next_i + 1
      else
        break
      end
    end
  end

  if commands.empty? && buffer.any?
    first = buffer[0].is_a?(Array) ? buffer[0][0] : buffer[0].to_s
    errors << "invalid command start: `#{first}`"
  end

  # 2. Walk remaining buffer: classify as positional args or flags
  current_positional = nil

  while i < buffer.length
    entry = buffer[i]

    case entry
    when :dash
      current_positional = "#{current_positional}-" if current_positional
      i += 1
    when :slash
      current_positional = "#{current_positional}/" if current_positional
      i += 1
    when Array
      name, args = entry

      if args.nil? || args.empty?
        # Bare token — positional segment
        if current_positional && !current_positional.end_with?("-") && !current_positional.end_with?("/")
          # Flush previous positional and start a new one
          flush_positional(positional, current_positional)
          current_positional = name
        elsif current_positional
          # Continue building hyphenated/slashed positional
          current_positional = "#{current_positional}#{name}"
        else
          current_positional = name
        end
        i += 1
      else
        # Has args — it's a flag
        flush_positional(positional, current_positional)
        current_positional = nil
        flag_name = name.tr("_", "-")
        prefix = name.length == 1 ? "-" : "--"

        if args == [true]
          flags << "#{prefix}#{flag_name}"
        else
          value = args.map(&:to_s).join(",")
          flags << "#{prefix}#{flag_name} #{value}"
        end
        i += 1
      end
    else
      i += 1
    end
  end

  flush_positional(positional, current_positional)

  Result.new(commands, positional, flags, errors, errors.empty?)
end