Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tls = ["dep:tokio-rustls"]

[dependencies]
clap = { version = "4.4", features = ["derive"] }
clap_complete = "4.4"
liquid = "0.26"
liquid-core = "0.26"
liquid-lib = { version = "0.26", features = ["stdlib"] }
Expand Down
99 changes: 99 additions & 0 deletions design/CLI_REFACTOR_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Plano de Refatoração e Expansão da CLI do Rustyll

Este documento apresenta um plano detalhado para aprimorar a interface de linha de comando (CLI) do Rustyll. As propostas a seguir foram pensadas sob a perspectiva de um engenheiro multipapel – contemplando design de UX/CLI, arquitetura de software, desenvolvimento e garantia de qualidade.

## 1. Análise de Usabilidade e Consistência

### 1.1 Revisão de Nomenclatura
- **Padronização dos comandos**: priorizar nomes completos (`build`, `serve`, etc.), mantendo _aliases_ curtos apenas quando realmente úteis e sem conflito.
- **Opções coerentes**: garantir que opções semelhantes mantenham a mesma forma em todos os comandos (ex.: `--verbose` e `--quiet`), evitando sinônimos desnecessários.

### 1.2 Estrutura de Comandos e Subcomandos
- Avaliar a criação de subcomandos agrupadores, como `rustyll config` (subcomandos `get`, `set`, `list`) e `rustyll cache` (`status`, `clear`).
- Organizar opções globais (ex.: `--config`, `--source`) no nível superior da CLI, válidas para qualquer comando.

### 1.3 Feedback ao Usuário
- Mensagens padronizadas de sucesso e erro, utilizando cores e ícones simples para melhor leitura.
- Exibir progresso em operações demoradas (barra ou _spinner_). Em builds longos, mostrar etapas concluídas.

### 1.4 Discoverability
- Tornar `rustyll help` mais completo, trazendo exemplos de uso.
- Gerar scripts de _autocomplete_ para Bash, Zsh e Fish.

### 1.5 Convenções Comuns
- Manter atalhos amplamente reconhecidos como `-h/--help`, `-V/--version`, `--dry-run` e `--force`.

## 2. Refatoração e Design de Opções

### 2.1 Consolidação de Opções
- Agrupar opções de paralelismo em um bloco único (`--parallel [markdown|sass|all]`, `--threads N`).
- Introduzir perfis de performance (`--mode dev|prod|ultra-fast`) que ajustam múltiplos parâmetros de maneira pré-definida.

### 2.2 Opções Globais vs. Locais
- Definir claramente quais opções afetam todos os comandos (`--source`, `--destination`, `--config`, `--verbose`, `--quiet`, `--trace`).
- Manter específicas de cada comando apenas as realmente necessárias, evitando duplicação desnecessária.

### 2.3 Configuração via CLI e Arquivo
- Estabelecer ordem de precedência: **CLI > _config.yml > padrão**.
- Documentar no help como as opções da CLI podem sobrescrever o arquivo de configuração.

### 2.4 Remodelagem das Opções de Performance
- Criar subcomandos ou _flags_ dedicados para cache (`rustyll cache clear`, `rustyll build --cache [markdown|sass|liquid]`).
- Melhorar a opção de _benchmark/profile_, permitindo `build --profile --output report.json`.

## 3. Expansão para Novas Funcionalidades

### 3.1 Migradores
- Novo comando: `rustyll migrate <PLATAFORMA> <ORIGEM> <DESTINO>`.
- Opções: `--force-overwrite`, `--keep-original-assets`, `--dry-run`, `--report-file`.
- Subcomando auxiliar: `rustyll migrate list-platforms`.

### 3.2 Gerenciamento de Temas
- Extensão do `new-theme` para `rustyll theme` com subcomandos:
- `install <URL|NOME>`
- `list`
- `apply <NOME>`

### 3.3 Suporte a Plugins
- Prever `rustyll plugin` com `install`, `list`, `enable` e `disable`.
- Permitir repositórios de plugins em configuração.

### 3.4 Aprimoramentos do `doctor`
- Validar configurações e sugerir correções automáticas ou links para documentação.

## 4. Garantia de Qualidade e Testabilidade

### 4.1 Cobertura de Testes para CLI
- Implementar testes automatizados que executem comandos em ambiente isolado, verificando combinações de opções e saídas esperadas.
- Utilizar crates como `assert_cmd` e `escargot` para orquestrar execucoes de binarios durante os testes.

### 4.2 Testes de Regressão
- Garantir paridade com comportamentos já existentes e com o Jekyll sempre que aplicável.
- Manter suite de testes que previna quebra de compatibilidade.

### 4.3 Testes de Borda e Robustez
- Casos com caminhos inexistentes, permissões insuficientes e entradas inválidas.
- Uso de `--dry-run` para simular operações sem alterações reais.

### 4.4 Saídas Consistentes
- Mensagens de erro e sucesso padronizadas em todas as ferramentas.
- Estrutura de logs configurável (níveis: error, warn, info, debug, trace).

## 5. Documentação e Exemplos

### 5.1 Estrutura da Documentação
- Criar seção dedicada à CLI no manual do projeto, listando cada comando, opções, exemplos e notas de compatibilidade Jekyll.

### 5.2 Receitas (Cookbooks)
- Exemplos prontos demonstrando combinações de opções, como "Build de produção otimizado" ou "Servidor de desenvolvimento com live reload".

### 5.3 Fluxos de Trabalho Típicos
- Descrever passo a passo desde a criação de um novo site até a publicação, usando os novos comandos.

### 5.4 Atualização do TODO.md
- Registrar as tarefas propostas para acompanhamento da equipe.

## Conclusão

O plano acima visa tornar o Rustyll mais acessível, previsível e poderoso para usuários de todos os níveis, mantendo a compatibilidade com o ecossistema Jekyll sempre que benéfico. A refatoração estruturada da CLI, aliada a novas funcionalidades e ampla cobertura de testes, garantirá uma experiência robusta e moderna para a comunidade.

14 changes: 14 additions & 0 deletions design/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# TODO

- [ ] Padronizar nomes de comandos e opções, mantendo aliases apenas quando necessários.
- [x] Introduzir subcomandos `config` e `cache` para organizar funcionalidades.
- [ ] Padronizar mensagens e feedback visual (sucesso, erro, progresso).
- [x] Implementar scripts de autocompletar para Bash, Zsh e Fish.
- [ ] Consolidar opções de paralelismo em `--parallel` e criar perfis `--mode`.
- [ ] Definir claramente opções globais e locais, documentando precedência CLI > _config.yml > padrão.
- [x] Implementar novos comandos `migrate`, `theme` e `plugin` conforme plano.
- [ ] Tornar o `doctor` mais proativo com sugestões de correção.
- [ ] Desenvolver suíte de testes de CLI abrangente, incluindo casos de borda.
- [ ] Implementar testes automatizados usando `assert_cmd` e `escargot`.
- [ ] Criar seção de documentação detalhada da CLI com exemplos e receitas.

58 changes: 58 additions & 0 deletions src/cli/commands/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::cli::types::{Commands, CacheAction};
use crate::directory::utils::clean_directory;
use std::path::PathBuf;

pub async fn handle_cache_command(command: &Commands) {
if let Commands::Cache { action } = command {
match action {
CacheAction::Clear { kind } => {
clear_caches(kind.as_deref());
}
CacheAction::Status {} => {
show_cache_status();
}
}
}
}

const CACHE_DIRS: &[&str] = &[".jekyll-cache", ".sass-cache"];
const CACHE_FILES: &[&str] = &[".jekyll-metadata"];

fn clear_caches(kind: Option<&str>) {
match kind {
Some("sass") => remove_path(".sass-cache"),
Some("jekyll") => remove_path(".jekyll-cache"),
Some("metadata") => remove_path(".jekyll-metadata"),
Some(_) => println!("Unknown cache type"),
None => {
for d in CACHE_DIRS.iter().chain(CACHE_FILES.iter()) {
remove_path(d);
}
}
}
}

fn show_cache_status() {
for d in CACHE_DIRS.iter().chain(CACHE_FILES.iter()) {
let path = PathBuf::from(d);
if path.exists() {
println!("{}: present", d);
} else {
println!("{}: not found", d);
}
}
}

fn remove_path(path: &str) {
let p = PathBuf::from(path);
if p.is_dir() {
if let Err(e) = clean_directory(&p) {
println!("Failed to clean {}: {}", path, e);
}
} else if p.exists() {
if let Err(e) = std::fs::remove_file(&p) {
println!("Failed to remove {}: {}", path, e);
}
}
}

10 changes: 10 additions & 0 deletions src/cli/commands/completions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use clap_complete::{generate, Shell};
use clap::{CommandFactory};
use crate::cli::types::{Commands, Cli};

pub async fn handle_completions_command(command: &Commands) {
if let Commands::Completions { shell } = command {
let mut cmd = Cli::command();
generate(*shell, &mut cmd, "rustyll", &mut std::io::stdout());
}
}
51 changes: 51 additions & 0 deletions src/cli/commands/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::cli::types::{Commands, ConfigAction};
use crate::config;
use serde_yaml::Value;
use std::path::PathBuf;

pub async fn handle_config_command(command: &Commands) {
if let Commands::Config { action } = command {
match action {
ConfigAction::Get { key } => {
if let Ok(cfg) = config::load_config(PathBuf::from("."), None) {
let yaml = serde_yaml::to_value(&cfg).unwrap_or(Value::Null);
if let Some(v) = get_nested_value(&yaml, key) {
println!("{}", serde_yaml::to_string(v).unwrap_or_default());
} else {
println!("Key not found: {}", key);
}
} else {
println!("Failed to load configuration");
}
}
ConfigAction::Set { key, value } => {
println!("Config set: {} = {}", key, value);
// TODO implement writing back to config file
}
ConfigAction::List {} => {
match config::load_config(PathBuf::from("."), None) {
Ok(cfg) => {
let yaml = serde_yaml::to_string(&cfg).unwrap_or_default();
println!("{}", yaml);
}
Err(_) => println!("Failed to load configuration"),
}
}
}
}
}

fn get_nested_value<'a>(value: &'a Value, key: &str) -> Option<&'a Value> {
let mut current = value;
for part in key.split('.') {
match current {
Value::Mapping(map) => {
let part_key = Value::String(part.to_string());
current = map.get(&part_key)?;
}
_ => return None,
}
}
Some(current)
}

12 changes: 9 additions & 3 deletions src/cli/commands/migrate.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::path::PathBuf;
use crate::cli::types::Commands;
use crate::cli::types::{Commands, MigrateCommands};
use crate::migrate;

pub async fn handle_migrate_command(
command: &Commands,
source_dir: Option<&PathBuf>,
destination_dir: Option<&PathBuf>
) {
if let Commands::Migrate { source, destination, engine, verbose, clean } = command {
if let Commands::Migrate(MigrateCommands::Run { source, destination, engine, verbose, clean }) = command {
// Determine source and destination directories
let source_dir = if let Some(s) = source {
s.clone()
Expand Down Expand Up @@ -86,5 +86,11 @@ pub async fn handle_migrate_command(
}
}
}
} else if let Commands::Migrate(MigrateCommands::ListPlatforms {}) = command {
let engines = migrate::list_engines();
println!("Available engines:");
for e in engines {
println!("- {}", e);
}
}
}
}
13 changes: 12 additions & 1 deletion src/cli/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,21 @@ mod clean;
mod report;
mod migrate;
mod new;
mod config;
mod cache;
mod theme;
mod plugin;
mod completions;

pub use build::handle_build_command;
pub use serve::handle_serve_command;
pub use clean::handle_clean_command;
pub use report::handle_report_command;
pub use migrate::handle_migrate_command;
pub use new::handle_new_command;
pub use new::handle_new_command;
pub use config::handle_config_command;
pub use cache::handle_cache_command;
pub use theme::handle_theme_command;
pub use plugin::handle_plugin_command;
pub use completions::handle_completions_command;

18 changes: 18 additions & 0 deletions src/cli/commands/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::cli::types::{Commands, PluginAction};

pub async fn handle_plugin_command(command: &Commands) {
if let Commands::Plugin { action } = command {
match action {
PluginAction::Install { name } => {
println!("Plugin install {}", name);
}
PluginAction::List {} => {
println!("Plugin list");
}
PluginAction::Enable { name } => {
println!("Plugin enable {}", name);
}
}
}
}

18 changes: 18 additions & 0 deletions src/cli/commands/theme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::cli::types::{Commands, ThemeAction};

pub async fn handle_theme_command(command: &Commands) {
if let Commands::Theme { action } = command {
match action {
ThemeAction::Install { name_or_url } => {
println!("Theme install {}", name_or_url);
}
ThemeAction::List {} => {
println!("Theme list");
}
ThemeAction::Apply { name } => {
println!("Theme apply {}", name);
}
}
}
}

2 changes: 1 addition & 1 deletion src/cli/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ pub fn configure_backtrace(trace: bool) {
if trace {
std::env::set_var("RUST_BACKTRACE", "1");
}
}
}
Loading