kube_ctlSourceKubeVClusterCommandTree

class CommandTree

Definitions

def initialize(data)

Parses vcluster.yaml's flat commands array (same format as helm.yaml).

Each entry has a name like "vcluster", "vcluster create", "vcluster platform create vcluster". We split on spaces, skip the leading "vcluster" token, and insert into a tree rooted at a synthetic "vcluster" node.

Implementation

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

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

    parts = name.split
    # Skip the root "vcluster" 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 = 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
      elsif 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

def evaluate(builder)

Evaluate a StringBuilder buffer against the vcluster command tree.

Classifies tokens as:

  • commands: matched subcommand path (e.g. ["platform", "create", "vcluster"])
  • positional: bare tokens after commands (vcluster names, URLs, etc.)
  • flags: tokens with arguments (--namespace test, --output json)

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)

    # If not found, try building a hyphenated name by looking ahead
    # past :dash tokens (e.g. current :dash user -> "current-user")
    unless child
      hyphenated = name
      peek = i + 1
      while peek < buffer.length && buffer[peek] == :dash
        peek2 = peek + 1
        break unless peek2 < buffer.length && buffer[peek2].is_a?(Array)

        next_name, next_args = buffer[peek2]
        break unless next_args.empty?

        hyphenated = "#{hyphenated}-#{next_name}"
        child = node.find_subcommand(hyphenated)
        if child
          name = hyphenated
          i = peek2 # will be incremented below
          break
        end
        peek = peek2 + 1
      end
    end

    break unless child

    commands << name
    node = child
    i += 1

    # Consume :dash separated subcommand parts (e.g. cluster-access-key)
    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)
      break unless child

      commands[-1] = hyphenated
      node = child
      i = next_i + 1

    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