- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
295 lines
7.6 KiB
PHP
295 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console;
|
|
|
|
use App\Framework\Filesystem\FilePath;
|
|
|
|
/**
|
|
* Command history and favorites management for console TUI
|
|
*/
|
|
final class CommandHistory
|
|
{
|
|
private array $history = [];
|
|
|
|
private array $favorites = [];
|
|
|
|
private int $maxHistorySize;
|
|
|
|
private FilePath $historyFile;
|
|
|
|
private FilePath $favoritesFile;
|
|
|
|
public function __construct(
|
|
int $maxHistorySize = 100,
|
|
?FilePath $storageDirectory = null
|
|
) {
|
|
$this->maxHistorySize = $maxHistorySize;
|
|
|
|
$storageDir = $storageDirectory ?? FilePath::create(sys_get_temp_dir() . '/console-history');
|
|
$this->historyFile = $storageDir->join('command_history.json');
|
|
$this->favoritesFile = $storageDir->join('command_favorites.json');
|
|
|
|
$this->loadHistory();
|
|
$this->loadFavorites();
|
|
}
|
|
|
|
/**
|
|
* Add a command to history
|
|
*/
|
|
public function addToHistory(string $commandName): void
|
|
{
|
|
// Remove if already exists to move to top
|
|
$this->history = array_filter($this->history, fn ($entry) => $entry['command'] !== $commandName);
|
|
|
|
// Add to beginning
|
|
array_unshift($this->history, [
|
|
'command' => $commandName,
|
|
'timestamp' => time(),
|
|
'count' => $this->getCommandCount($commandName) + 1,
|
|
]);
|
|
|
|
// Limit history size
|
|
if (count($this->history) > $this->maxHistorySize) {
|
|
$this->history = array_slice($this->history, 0, $this->maxHistorySize);
|
|
}
|
|
|
|
$this->saveHistory();
|
|
}
|
|
|
|
/**
|
|
* Add command to favorites
|
|
*/
|
|
public function addToFavorites(string $commandName): void
|
|
{
|
|
if (! $this->isFavorite($commandName)) {
|
|
$this->favorites[] = [
|
|
'command' => $commandName,
|
|
'added_at' => time(),
|
|
];
|
|
$this->saveFavorites();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove command from favorites
|
|
*/
|
|
public function removeFromFavorites(string $commandName): void
|
|
{
|
|
$this->favorites = array_filter(
|
|
$this->favorites,
|
|
fn ($entry) => $entry['command'] !== $commandName
|
|
);
|
|
$this->favorites = array_values($this->favorites); // Re-index
|
|
$this->saveFavorites();
|
|
}
|
|
|
|
/**
|
|
* Toggle favorite status
|
|
*/
|
|
public function toggleFavorite(string $commandName): bool
|
|
{
|
|
if ($this->isFavorite($commandName)) {
|
|
$this->removeFromFavorites($commandName);
|
|
|
|
return false;
|
|
} else {
|
|
$this->addToFavorites($commandName);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if command is in favorites
|
|
*/
|
|
public function isFavorite(string $commandName): bool
|
|
{
|
|
return array_reduce(
|
|
$this->favorites,
|
|
fn ($carry, $entry) => $carry || $entry['command'] === $commandName,
|
|
false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get recent command history
|
|
*/
|
|
public function getRecentHistory(int $limit = 10): array
|
|
{
|
|
return array_slice($this->history, 0, $limit);
|
|
}
|
|
|
|
/**
|
|
* Get frequently used commands
|
|
*/
|
|
public function getFrequentCommands(int $limit = 10): array
|
|
{
|
|
$commands = $this->history;
|
|
|
|
// Sort by usage count (descending)
|
|
usort($commands, fn ($a, $b) => $b['count'] <=> $a['count']);
|
|
|
|
return array_slice($commands, 0, $limit);
|
|
}
|
|
|
|
/**
|
|
* Get all favorites
|
|
*/
|
|
public function getFavorites(): array
|
|
{
|
|
return $this->favorites;
|
|
}
|
|
|
|
/**
|
|
* Get command usage statistics
|
|
*/
|
|
public function getCommandStats(string $commandName): array
|
|
{
|
|
$entry = array_find($this->history, fn ($entry) => $entry['command'] === $commandName);
|
|
|
|
if (! $entry) {
|
|
return [
|
|
'command' => $commandName,
|
|
'count' => 0,
|
|
'last_used' => null,
|
|
'is_favorite' => $this->isFavorite($commandName),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'command' => $commandName,
|
|
'count' => $entry['count'],
|
|
'last_used' => $entry['timestamp'],
|
|
'is_favorite' => $this->isFavorite($commandName),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Clear all history
|
|
*/
|
|
public function clearHistory(): void
|
|
{
|
|
$this->history = [];
|
|
$this->saveHistory();
|
|
}
|
|
|
|
/**
|
|
* Clear all favorites
|
|
*/
|
|
public function clearFavorites(): void
|
|
{
|
|
$this->favorites = [];
|
|
$this->saveFavorites();
|
|
}
|
|
|
|
/**
|
|
* Get suggestions based on partial input
|
|
*/
|
|
public function getSuggestions(string $partial, int $limit = 5): array
|
|
{
|
|
$suggestions = [];
|
|
|
|
// Add matching favorites first
|
|
foreach ($this->favorites as $favorite) {
|
|
if (str_starts_with($favorite['command'], $partial)) {
|
|
$suggestions[] = [
|
|
'command' => $favorite['command'],
|
|
'type' => 'favorite',
|
|
'score' => 1000, // High priority for favorites
|
|
];
|
|
}
|
|
}
|
|
|
|
// Add matching recent commands
|
|
foreach ($this->history as $entry) {
|
|
$command = $entry['command'];
|
|
if (str_starts_with($command, $partial) && ! $this->isFavorite($command)) {
|
|
$suggestions[] = [
|
|
'command' => $command,
|
|
'type' => 'recent',
|
|
'score' => 500 + $entry['count'], // Score based on usage count
|
|
];
|
|
}
|
|
}
|
|
|
|
// Sort by score (descending) and limit
|
|
usort($suggestions, fn ($a, $b) => $b['score'] <=> $a['score']);
|
|
|
|
return array_slice($suggestions, 0, $limit);
|
|
}
|
|
|
|
/**
|
|
* Get command count from history
|
|
*/
|
|
private function getCommandCount(string $commandName): int
|
|
{
|
|
$entry = array_find($this->history, fn ($entry) => $entry['command'] === $commandName);
|
|
|
|
return $entry ? $entry['count'] : 0;
|
|
}
|
|
|
|
/**
|
|
* Load history from file
|
|
*/
|
|
private function loadHistory(): void
|
|
{
|
|
if ($this->historyFile->exists()) {
|
|
$content = file_get_contents($this->historyFile->toString());
|
|
$decoded = json_decode($content, true);
|
|
if (is_array($decoded)) {
|
|
$this->history = $decoded;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save history to file
|
|
*/
|
|
private function saveHistory(): void
|
|
{
|
|
$this->ensureDirectoryExists($this->historyFile->getDirectory());
|
|
file_put_contents(
|
|
$this->historyFile->toString(),
|
|
json_encode($this->history, JSON_PRETTY_PRINT)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Load favorites from file
|
|
*/
|
|
private function loadFavorites(): void
|
|
{
|
|
if ($this->favoritesFile->exists()) {
|
|
$content = file_get_contents($this->favoritesFile->toString());
|
|
$decoded = json_decode($content, true);
|
|
if (is_array($decoded)) {
|
|
$this->favorites = $decoded;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save favorites to file
|
|
*/
|
|
private function saveFavorites(): void
|
|
{
|
|
$this->ensureDirectoryExists($this->favoritesFile->getDirectory());
|
|
file_put_contents(
|
|
$this->favoritesFile->toString(),
|
|
json_encode($this->favorites, JSON_PRETTY_PRINT)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Ensure storage directory exists
|
|
*/
|
|
private function ensureDirectoryExists(FilePath $directory): void
|
|
{
|
|
if (! $directory->exists()) {
|
|
mkdir($directory->toString(), 0755, true);
|
|
}
|
|
}
|
|
}
|