class Rubydex::CLI::Command
Base class for rdx subcommands. A subcommand parses its own options out of argv and, when it needs one, builds the workspace graph through {#build_graph}.
Subclasses live one per file in lib/rubydex/cli/command/ and describe themselves with a small DSL, which supplies both the name they are dispatched under and their entry in ‘rdx help`:
class Query < Command command "query" arguments "<CYPHER>" summary "Run a Cypher query against the workspace graph and print the result." end
There is no registration list to keep in step: {.all} loads every file in that directory and discovers the commands through ‘Class#subclasses`.
‘rdx console` — opens an IRB session with a populated graph for the current workspace.
‘rdx lint [PATH]` — discovers project and dependency rules and runs them against a workspace.
‘rdx mcp [PATH]` — runs the MCP server for AI assistants. The workspace defaults to the current directory.
‘rdx query <CYPHER>` — runs a Cypher query against the workspace graph and prints the result. --schema describes the queryable schema instead, which needs no graph.
Attributes
String?
The subcommand name declared by {.command}. nil for a class that declares none, which is how an abstract intermediate class opts out of being dispatched.
Public Class Methods
→ Array[singleton(Command)]
Source
# File lib/rubydex/cli/command.rb, line 79 def all Dir.glob(File.expand_path("command/*.rb", __dir__)).sort.each { |file| require(file) } declared end
Every subcommand, in alphabetical order. Loads the command files first, so the answer does not depend on what happens to have been required already.
(?String? spec) → String?
Source
# File lib/rubydex/cli/command.rb, line 47 def arguments(spec = nil) return @arguments unless spec @arguments = spec #: String? end
Declares the argument spec shown after the command name in ‘rdx help`, e.g. `“<CYPHER>”`. Called without an argument, returns the declared spec.
(String value) → void
Source
# File lib/rubydex/cli/command.rb, line 35 def command(value) # Deliberately checked against {.declared} rather than {.find}: this runs while a command # file is still being loaded, and `find` would glob in every other command file first. clash = declared.find { |other| !other.equal?(self) && other.command_name == value } raise ArgumentError, "command `#{value}` is already declared by #{clash}" if clash @command_name = value #: String? end
Declares the subcommand name this class is dispatched under.
Deliberately not called name: that would shadow ‘Class#name`, which Ruby uses when it builds error messages, so a NoMethodError on a command would report “an instance of query” instead of naming the class.
→ Array[singleton(Command)]
Source
# File lib/rubydex/cli/command.rb, line 72 def declared Command.subclasses.select(&:command_name).sort_by(&:command_name) end
The subcommands that are currently loaded, in alphabetical order. Loads nothing itself, so it is safe to call while a command file is still being evaluated.
(String? name) → singleton(Command)?
Source
# File lib/rubydex/cli/command.rb, line 87 def find(name) all.find { |command| command.command_name == name } end
The subcommand declared under name, if any.
(Array[String] argv) → void
Source
# File lib/rubydex/cli/command.rb, line 93 def initialize(argv) @argv = argv end
(?String? text) → String?
Source
# File lib/rubydex/cli/command.rb, line 57 def summary(text = nil) return @summary unless text @summary = text.strip #: String? end
Declares the description shown in ‘rdx help`. May span several lines, which are aligned under the first when the command list is rendered. Called without an argument, returns the declared description.
→ String
Source
# File lib/rubydex/cli/command.rb, line 65 def usage_form [command_name, arguments].compact.join(" ") end
How the command is spelled in a usage line, e.g. ‘“query <CYPHER>”`.
Public Instance Methods
→ void
Source
# File lib/rubydex/cli/command.rb, line 98 def run raise NotImplementedError, "#{self.class} must implement #run" end