kube_ctlSourceKubeHelmCommandTree

class CommandTree

Definitions

def initialize(data)

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.

Implementation

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

def evaluate(builder)

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)

Implementation

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