Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
409 lines
12 KiB
PHP
Executable File
409 lines
12 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\Commands\Rsx;
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
class Man_Command extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'rsx:man {term? : The documentation term to look up (partial matches allowed)} {--agent-helper-message : Output agent helper message with available topics}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Display RSX documentation from man pages (pretty formatted)';
|
|
|
|
/**
|
|
* The documentation directory paths.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $docs_dirs;
|
|
|
|
/**
|
|
* Create a new command instance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->docs_dirs = [
|
|
base_path('docs/man'),
|
|
base_path('app/RSpade/man'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function handle()
|
|
{
|
|
// Check if agent helper message was requested
|
|
if ($this->option('agent-helper-message')) {
|
|
return $this->show_agent_helper_message();
|
|
}
|
|
|
|
// Create docs/man directory if it doesn't exist (first dir only)
|
|
if (!is_dir($this->docs_dirs[0])) {
|
|
mkdir($this->docs_dirs[0], 0755, true);
|
|
}
|
|
|
|
$term = $this->argument('term');
|
|
|
|
// If no term provided, list all available documentation
|
|
if (!$term) {
|
|
return $this->list_all_documentation();
|
|
}
|
|
|
|
// Find matching documentation files
|
|
$matches = $this->find_matching_files($term);
|
|
|
|
if (count($matches) === 0) {
|
|
$this->error("No documentation found for '{$term}'");
|
|
$this->line('');
|
|
$this->line('Available documentation:');
|
|
$this->list_all_documentation();
|
|
return 1;
|
|
}
|
|
|
|
if (count($matches) === 1) {
|
|
// Display the single matching file
|
|
return $this->display_documentation($matches[0]);
|
|
}
|
|
|
|
// Multiple matches found - list them
|
|
$this->warn("Multiple matches found for '{$term}':");
|
|
$this->line('');
|
|
foreach ($matches as $file) {
|
|
$name = basename($file, '.txt');
|
|
$this->line(" • <info>{$name}</info>");
|
|
}
|
|
$this->line('');
|
|
$this->line('Please specify a more specific term.');
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* List all available documentation files.
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function list_all_documentation()
|
|
{
|
|
$files = [];
|
|
foreach ($this->docs_dirs as $dir) {
|
|
if (is_dir($dir)) {
|
|
$dir_files = glob($dir . '/*.txt');
|
|
if ($dir_files) {
|
|
$files = array_merge($files, $dir_files);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($files)) {
|
|
$this->warn('No documentation files found in:');
|
|
foreach ($this->docs_dirs as $dir) {
|
|
$this->line(' - ' . $dir);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
$this->info('Available Documentation:');
|
|
$this->line('');
|
|
|
|
$docs = [];
|
|
foreach ($files as $file) {
|
|
$name = basename($file, '.txt');
|
|
// Read file to extract description from NAME section
|
|
$content = file_get_contents($file);
|
|
$description = '';
|
|
|
|
// Look for NAME section in man page format
|
|
if (preg_match('/^NAME\s*\n\s*(.+?)(?:\n|$)/m', $content, $matches)) {
|
|
$description = trim($matches[1]);
|
|
// Remove the name part, keep just description after dash
|
|
if (str_contains($description, ' - ')) {
|
|
$parts = explode(' - ', $description, 2);
|
|
$description = trim($parts[1]);
|
|
}
|
|
} elseif (preg_match('/^#\s*(.+?)(?:\n|$)/m', $content, $matches)) {
|
|
// @PHP-FALLBACK-01-EXCEPTION - Fallback* parsing for man page description extraction
|
|
// Fallback to first header line
|
|
$description = trim($matches[1]);
|
|
} else {
|
|
// Fallback to first non-empty line
|
|
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
if (!empty($lines)) {
|
|
foreach ($lines as $line) {
|
|
if (!str_starts_with($line, '#') && !str_starts_with($line, '=')) {
|
|
$description = trim($line);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strlen($description) > 60) {
|
|
$description = substr($description, 0, 57) . '...';
|
|
}
|
|
$docs[] = ['name' => $name, 'description' => $description];
|
|
}
|
|
|
|
// Sort alphabetically
|
|
usort($docs, function($a, $b) {
|
|
return strcasecmp($a['name'], $b['name']);
|
|
});
|
|
|
|
// Display in a nice table format
|
|
foreach ($docs as $doc) {
|
|
$this->line(sprintf(" <info>%-25s</info> %s", $doc['name'], $doc['description']));
|
|
}
|
|
|
|
$this->line('');
|
|
$this->line('Usage: <comment>php artisan rsx:man <term></comment>');
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Find documentation files matching the given term.
|
|
*
|
|
* @param string $term
|
|
* @return array
|
|
*/
|
|
protected function find_matching_files(string $term): array
|
|
{
|
|
$files = [];
|
|
foreach ($this->docs_dirs as $dir) {
|
|
if (is_dir($dir)) {
|
|
$dir_files = glob($dir . '/*.txt');
|
|
if ($dir_files) {
|
|
$files = array_merge($files, $dir_files);
|
|
}
|
|
}
|
|
}
|
|
|
|
$term_lower = strtolower($term);
|
|
$matches = [];
|
|
|
|
foreach ($files as $file) {
|
|
$filename = basename($file, '.txt');
|
|
$filename_lower = strtolower($filename);
|
|
|
|
// Exact match (case-insensitive)
|
|
if ($filename_lower === $term_lower) {
|
|
return [$file]; // Return immediately for exact match
|
|
}
|
|
|
|
// Partial match
|
|
if (str_contains($filename_lower, $term_lower)) {
|
|
$matches[] = $file;
|
|
}
|
|
}
|
|
|
|
// If no partial matches, try fuzzy matching with underscores/hyphens
|
|
if (empty($matches)) {
|
|
$term_normalized = str_replace(['-', '_', ' '], '', $term_lower);
|
|
|
|
foreach ($files as $file) {
|
|
$filename = basename($file, '.txt');
|
|
$filename_normalized = str_replace(['-', '_', ' '], '', strtolower($filename));
|
|
|
|
if (str_contains($filename_normalized, $term_normalized)) {
|
|
$matches[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $matches;
|
|
}
|
|
|
|
/**
|
|
* Display the contents of a documentation file with pretty formatting.
|
|
*
|
|
* @param string $file_path
|
|
* @return int
|
|
*/
|
|
protected function display_documentation(string $file_path): int
|
|
{
|
|
if (!file_exists($file_path)) {
|
|
$this->error("Documentation file not found: {$file_path}");
|
|
return 1;
|
|
}
|
|
|
|
$content = file_get_contents($file_path);
|
|
|
|
// Check if this is a man page format
|
|
$is_man_page = preg_match('/^[A-Z_]+\(\d+\)/', $content);
|
|
|
|
$lines = explode("\n", $content);
|
|
|
|
$this->newLine();
|
|
|
|
$in_code_block = false;
|
|
$in_section = false;
|
|
$in_example = false;
|
|
|
|
foreach ($lines as $line) {
|
|
// Skip man page headers/footers
|
|
if ($is_man_page && preg_match('/^[A-Z_]+\(\d+\)\s+.*\s+[A-Z_]+\(\d+\)$/', $line)) {
|
|
continue;
|
|
}
|
|
|
|
// Detect code examples in man pages (indented 4+ spaces)
|
|
if ($is_man_page && !$in_code_block) {
|
|
if (preg_match('/^ /', $line)) {
|
|
// Just display indented code without boxes
|
|
$this->line(' ' . substr($line, 4));
|
|
continue;
|
|
}
|
|
}
|
|
// Handle code blocks
|
|
if (str_starts_with($line, '```')) {
|
|
$in_code_block = !$in_code_block;
|
|
continue;
|
|
}
|
|
|
|
if ($in_code_block) {
|
|
$this->line(' ' . $line);
|
|
continue;
|
|
}
|
|
|
|
// Handle man page section headers (all caps)
|
|
if ($is_man_page && preg_match('/^[A-Z][A-Z\s_]+$/', $line) && strlen($line) < 40) {
|
|
if ($in_section) {
|
|
$this->newLine();
|
|
}
|
|
$this->line(strtoupper($line));
|
|
$in_section = true;
|
|
continue;
|
|
}
|
|
|
|
// Handle headers
|
|
if (str_starts_with($line, '# ')) {
|
|
$header = substr($line, 2);
|
|
$this->newLine();
|
|
$this->line(strtoupper($header));
|
|
$this->newLine();
|
|
$in_section = true;
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with($line, '## ')) {
|
|
$header = substr($line, 3);
|
|
if ($in_section) {
|
|
$this->newLine();
|
|
}
|
|
$this->line($header);
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with($line, '### ')) {
|
|
$header = substr($line, 4);
|
|
$this->line($header);
|
|
continue;
|
|
}
|
|
|
|
// Handle lists
|
|
if (preg_match('/^(\s*)[•\-\*]\s+(.+)/', $line, $matches)) {
|
|
$indent = str_repeat(' ', strlen($matches[1]));
|
|
$this->line($indent . '- ' . $matches[2]);
|
|
continue;
|
|
}
|
|
|
|
// Handle numbered lists
|
|
if (preg_match('/^(\s*)(\d+)\.\s+(.+)/', $line, $matches)) {
|
|
$indent = str_repeat(' ', strlen($matches[1]));
|
|
$this->line($indent . $matches[2] . '. ' . $matches[3]);
|
|
continue;
|
|
}
|
|
|
|
// Handle separator lines
|
|
if (preg_match('/^[\-=]{3,}$/', $line)) {
|
|
$this->line('');
|
|
continue;
|
|
}
|
|
|
|
// Regular text
|
|
if (trim($line) === '') {
|
|
$this->line('');
|
|
} else {
|
|
$this->line($line);
|
|
}
|
|
}
|
|
|
|
$this->newLine();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Show agent helper message with available topics
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function show_agent_helper_message(): int
|
|
{
|
|
$this->line('Comprehensive documentation is available for the following commands, and can be reviewed by calling "php artisan rsx:man (topic)". This text should be referenced whenever doing advanced integration work with these subsections to get necessary context on how the subsystems work before proceeding with edits, implementations, or generally using the subsystems.');
|
|
$this->line('');
|
|
|
|
// Get all available topics
|
|
$topics = $this->get_all_topic_names();
|
|
$this->line(implode(' ', $topics));
|
|
|
|
$this->line('');
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get all available topic names
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_all_topic_names(): array
|
|
{
|
|
$files = [];
|
|
foreach ($this->docs_dirs as $dir) {
|
|
if (is_dir($dir)) {
|
|
$dir_files = glob($dir . '/*.txt');
|
|
if ($dir_files) {
|
|
$files = array_merge($files, $dir_files);
|
|
}
|
|
}
|
|
}
|
|
|
|
$topics = [];
|
|
foreach ($files as $file) {
|
|
$topics[] = basename($file, '.txt');
|
|
}
|
|
|
|
// Remove duplicates and sort
|
|
$topics = array_unique($topics);
|
|
sort($topics);
|
|
|
|
return $topics;
|
|
}
|
|
|
|
/**
|
|
* Format inline markdown elements in text.
|
|
*
|
|
* @param string $text
|
|
* @return string
|
|
*/
|
|
protected function format_inline(string $text): string
|
|
{
|
|
// Just return plain text - no formatting
|
|
return $text;
|
|
}
|
|
} |