From a76ac7863f56fdc19c186392f86a9eb9abd8e30e Mon Sep 17 00:00:00 2001 From: Alessandro Maestri Date: Tue, 11 Nov 2025 14:33:21 +0100 Subject: [PATCH 1/2] refactor(cli): reorganize command index and localize help headings (v0.4.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Changed - Reordered command index for a clearer logical grouping in the main help output. - Localized section titles (`help_heading`) such as “Global options”, “List-specific options”, etc. - Improved overall help readability and consistency across commands. - Updated display order values to match the new grouping structure. --- CHANGELOG.md | 23 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 +- src/cli.rs | 230 +++++++++++++++++++-------------------- src/i18n/locales/en.json | 12 +- src/i18n/locales/it.json | 12 +- 7 files changed, 165 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ad7ba..3347055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. +## [0.4.6] - 2025-11-12 + +### 🔧 Changed + +- **Reorganized command index** in the CLI help output for better logical grouping and readability. + Commands are now displayed in a clearer order, following a structured hierarchy (Book commands, App commands, etc.). + +- **Localized help section titles (`help_heading`)** across the entire CLI, including: + - “Global options” + - “List-specific options” + - “Import-specific options” + - “Export-specific options” + - and other command-related help blocks + +- Improved overall **consistency and clarity** of command descriptions and section headings in both English and Italian. + +### 🧩 Internal + +- Updated `display_order` values to reflect the new command hierarchy. +- Refined `cli.rs` layout for easier maintenance of localized help headings. + +--- + ## [0.4.5] - 2025-11-11 ### Added diff --git a/Cargo.lock b/Cargo.lock index f8c1fd2..f5d9afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "librius" -version = "0.4.5" +version = "0.4.6" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index cd21897..95820b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "librius" -version = "0.4.5" +version = "0.4.6" edition = "2024" authors = ["Alessandro Maestri "] description = "A personal library manager CLI written in Rust." diff --git a/README.md b/README.md index a541ea0..4a0c935 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,9 @@ cargo install rtimelogger |:-------------------------|:---------------------------------|:---------------------------------------------------------------------------------------------------------------| | **List** | `librius list` | Display all books stored in the local database, in full or compact view | | **Search** | `librius search ` | Full-text search across title, author, editor, genre, and language fields; supports `--short` for compact view | +| **Add book** | `librius add book --isbn ` | Add new books using ISBN lookup via Google Books API | +| **Edit book** | `librius edit book ` | Edit existing records by ID or ISBN; dynamic field generation, language conversion, and plural-aware messages | +| **Delete book** | `del ` | Delete books by ID or ISBN, with interactive confirmation, `--force` flag, and logged deletions | | **Config management** | `librius config` | Manage YAML configuration via `--print`, `--init`, `--edit`, `--editor` | | **Backup** | `librius backup` | Create plain or compressed database backups (`.sqlite`, `.zip`, `.tar.gz`) | | **Export** | `librius export` | Export data in CSV, JSON, or XLSX format | @@ -87,9 +90,6 @@ cargo install rtimelogger | **Database migrations** | *(automatic)* | Automatic schema upgrades and integrity checks at startup | | **Logging system** | *(internal)* | Records all operations and migrations in an internal log table | | **Multilanguage (i18n)** | `librius --lang ` | Fully localized CLI (commands, help, messages); `--lang` flag and config key | -| **Add book** | `librius add book --isbn ` | Add new books using ISBN lookup via Google Books API | -| **Edit book** | `librius edit book ` | Edit existing records by ID or ISBN; dynamic field generation, language conversion, and plural-aware messages | -| **Delete book** | `del ` | Delete books by ID or ISBN, with interactive confirmation, `--force` flag, and logged deletions | | **Dynamic help system** | `librius help ` | Ordered and grouped help output using `display_order()` and `next_help_heading()` | --- diff --git a/src/cli.rs b/src/cli.rs index 3aa6e34..517664b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,7 +19,7 @@ pub fn build_cli() -> Command { .help(tr_s("help_flag_about")) .action(ArgAction::Help) .global(true) - .help_heading("Global options") + .help_heading(tr_s("help.global_options")) .display_order(1), ) .arg( @@ -29,7 +29,7 @@ pub fn build_cli() -> Command { .help(tr_s("help_verbose")) .action(ArgAction::SetTrue) .global(true) - .help_heading("Global options") + .help_heading(tr_s("help.global_options")) .display_order(2), ) .arg( @@ -39,7 +39,7 @@ pub fn build_cli() -> Command { .help(tr_s("help_lang")) .global(true) .num_args(1) - .help_heading("Global options") + .help_heading(tr_s("help.global_options")) .display_order(3), ) // 📘 list command @@ -52,7 +52,7 @@ pub fn build_cli() -> Command { .long("short") .help(tr_s("help.list.short")) .action(ArgAction::SetTrue) - .help_heading("List-specific options") + .help_heading(tr_s("help.list_specific_options")) .display_order(11), ) .arg( @@ -62,7 +62,7 @@ pub fn build_cli() -> Command { .value_name("ID") .num_args(1) .value_parser(clap::value_parser!(i32)) - .help_heading("List-specific options") + .help_heading(tr_s("help.list_specific_options")) .display_order(12), ) .arg( @@ -70,7 +70,7 @@ pub fn build_cli() -> Command { .long("details") .help(tr_s("help.list.details")) .action(ArgAction::SetTrue) - .help_heading("List-specific options") + .help_heading(tr_s("help.list_specific_options")) .display_order(13), ), ) @@ -85,7 +85,7 @@ pub fn build_cli() -> Command { .required(true) .value_name("QUERY") .num_args(1) - .help_heading("Search-specific options") + .help_heading(tr_s("help.search_specific_options")) .display_order(16), ) .arg( @@ -93,38 +93,115 @@ pub fn build_cli() -> Command { .long("short") .help(tr_s("search_short_help")) .action(ArgAction::SetTrue) - .help_heading("Search-specific options") + .help_heading(tr_s("help.search_specific_options")) .display_order(17), ), ) + // ➕ add book command + .subcommand( + Command::new("add") + .about(tr("help.add.about")) + .display_order(20) + .subcommand( + Command::new("book") + .about(tr("help.add.book.about")) + .display_order(21) + .arg( + Arg::new("isbn") + .long("isbn") + .help(tr_s("help.add.book.isbn")) + .required(true) + .value_name("ISBN") + .help_heading(tr_s("help.add_specific_options")) + .display_order(22), + ), + ), + ) + // ✏️ edit book command (aggiornato con logica ibrida ID/ISBN) + .subcommand( + Command::new("edit") + .about(tr("help.edit.about")) + .display_order(30) + .subcommand({ + let mut cmd = Command::new("book") + .about(tr("help.edit.book.about")) + .display_order(31) + .arg( + Arg::new("key") + .help(tr_s("help.edit.book.key")) + .required(true) + .num_args(1) + .help_heading(tr_s("help.edit_specific_options")) + .display_order(32), + ); + + // ✅ Aggiunta dinamica di tutti i campi editabili + for (i, (name, help, short)) in EDITABLE_FIELDS.iter().enumerate() { + cmd = cmd.arg( + Arg::new(*name) + .long(*name) + .short(*short) + .help(tr_s(help)) + .num_args(1) + .action(ArgAction::Set) + .help_heading(tr_s("help.edit_specific_options")) + .display_order(40 + i), + ); + } + + cmd + }), + ) + .subcommand( + Command::new("del") + .about(tr("help.del.about")) + .display_order(50) + .arg( + Arg::new("key") + .help(tr("help.del.key")) // e.g. "Book ID or ISBN to delete" + .required(true) + .value_name("ID|ISBN") + .num_args(1) + .display_order(51), + ) + .arg( + Arg::new("force") + .long("force") + .short('f') + .help(tr("help.del.force")) // e.g. "Force deletion without confirmation" + .action(ArgAction::SetTrue) + .num_args(0) + .display_order(52), + ), + ) // ⚙️ config command .subcommand( Command::new("config") .about(tr_s("config_about")) - .display_order(20) + .display_order(60) .arg( Arg::new("init") .long("init") .help(tr_s("config_init_help")) .action(ArgAction::SetTrue) - .help_heading("Config-specific options") - .display_order(21), + .help_heading(tr_s("help.config_specific_options")) + .display_order(61), ) .arg( Arg::new("print") .long("print") .help(tr_s("config_print_help")) .action(ArgAction::SetTrue) - .help_heading("Config-specific options") - .display_order(22), + .help_heading(tr_s("help.config_specific_options")) + .display_order(62), ) .arg( Arg::new("edit") .long("edit") .help(tr_s("config_edit_help")) .action(ArgAction::SetTrue) - .help_heading("Config-specific options") - .display_order(23), + .help_heading(tr_s("help.config_specific_options")) + .display_order(63), ) .arg( Arg::new("editor") @@ -132,37 +209,37 @@ pub fn build_cli() -> Command { .requires("edit") .num_args(1) .help(tr_s("config_editor_help")) - .help_heading("Config-specific options") - .display_order(24), + .help_heading(tr_s("help.config_specific_options")) + .display_order(64), ), ) // 💾 backup command .subcommand( Command::new("backup") .about(tr_s("backup_about")) - .display_order(30) + .display_order(70) .arg( Arg::new("compress") .long("compress") .help(tr_s("backup_compress_help")) .action(ArgAction::SetTrue) - .help_heading("Backup-specific options") - .display_order(31), + .help_heading(tr_s("help.backup_specific_options")) + .display_order(71), ), ) // 📤 export command .subcommand( Command::new("export") .about(tr_s("export_about")) - .display_order(40) + .display_order(80) .arg( Arg::new("csv") .long("csv") .help(tr_s("export_csv_help")) .action(ArgAction::SetTrue) .conflicts_with_all(["xlsx", "json"]) - .help_heading("Export-specific options") - .display_order(41), + .help_heading(tr_s("help.export_specific_options")) + .display_order(81), ) .arg( Arg::new("xlsx") @@ -170,8 +247,8 @@ pub fn build_cli() -> Command { .help(tr_s("export_xlsx_help")) .conflicts_with_all(["csv", "json"]) .action(ArgAction::SetTrue) - .help_heading("Export-specific options") - .display_order(42), + .help_heading(tr_s("help.export_specific_options")) + .display_order(82), ) .arg( Arg::new("json") @@ -179,8 +256,8 @@ pub fn build_cli() -> Command { .help(tr_s("export_json_help")) .conflicts_with_all(["csv", "xlsx"]) .action(ArgAction::SetTrue) - .help_heading("Export-specific options") - .display_order(43), + .help_heading(tr_s("help.export_specific_options")) + .display_order(83), ) .arg( Arg::new("output") @@ -189,15 +266,15 @@ pub fn build_cli() -> Command { .help(tr_s("export_output_help")) .value_name("FILE") .required(false) - .help_heading("Export-specific options") - .display_order(44), + .help_heading(tr_s("help.export_specific_options")) + .display_order(84), ), ) // 📥 import command .subcommand( Command::new("import") .about(tr_s("import_about")) - .display_order(50) + .display_order(90) .arg( Arg::new("file") .short('f') @@ -205,8 +282,8 @@ pub fn build_cli() -> Command { .help(tr_s("import_file_help")) .required(true) .value_name("PATH") - .help_heading("Import-specific options") - .display_order(51), + .help_heading(tr_s("help.import_specific_options")) + .display_order(91), ) .arg( Arg::new("csv") @@ -214,8 +291,8 @@ pub fn build_cli() -> Command { .help(tr_s("import_csv_help")) .action(ArgAction::SetTrue) .conflicts_with("json") - .help_heading("Import-specific options") - .display_order(52), + .help_heading(tr_s("help.import_specific_options")) + .display_order(92), ) .arg( Arg::new("json") @@ -223,8 +300,8 @@ pub fn build_cli() -> Command { .help(tr_s("import_json_help")) .conflicts_with("csv") .action(ArgAction::SetTrue) - .help_heading("Import-specific options") - .display_order(53), + .help_heading(tr_s("help.import_specific_options")) + .display_order(93), ) .arg( Arg::new("delimiter") @@ -235,85 +312,8 @@ pub fn build_cli() -> Command { .value_name("CHAR") .required(false) .value_parser(clap::builder::NonEmptyStringValueParser::new()) - .help_heading("Import-specific options") - .display_order(54), - ), - ) - // ➕ add book command - .subcommand( - Command::new("add") - .about(tr("help.add.about")) - .display_order(60) - .subcommand( - Command::new("book") - .about(tr("help.add.book.about")) - .display_order(61) - .arg( - Arg::new("isbn") - .long("isbn") - .help(tr_s("help.add.book.isbn")) - .required(true) - .value_name("ISBN") - .help_heading("Add Book specific options") - .display_order(62), - ), - ), - ) - // ✏️ edit book command (aggiornato con logica ibrida ID/ISBN) - .subcommand( - Command::new("edit") - .about(tr("help.edit.about")) - .display_order(70) - .subcommand({ - let mut cmd = Command::new("book") - .about(tr("help.edit.book.about")) - .display_order(71) - .arg( - Arg::new("key") - .help(tr_s("help.edit.book.key")) - .required(true) - .num_args(1) - .help_heading("Edit Book required option") - .display_order(72), - ); - - // ✅ Aggiunta dinamica di tutti i campi editabili - for (i, (name, help, short)) in EDITABLE_FIELDS.iter().enumerate() { - cmd = cmd.arg( - Arg::new(*name) - .long(*name) - .short(*short) - .help(tr_s(help)) - .num_args(1) - .action(ArgAction::Set) - .help_heading("Edit Book specific options") - .display_order(80 + i), - ); - } - - cmd - }), - ) - .subcommand( - Command::new("del") - .about(tr("help.del.about")) - .display_order(90) - .arg( - Arg::new("key") - .help(tr("help.del.key")) // e.g. "Book ID or ISBN to delete" - .required(true) - .value_name("ID|ISBN") - .num_args(1) - .display_order(91), - ) - .arg( - Arg::new("force") - .long("force") - .short('f') - .help(tr("help.del.force")) // e.g. "Force deletion without confirmation" - .action(ArgAction::SetTrue) - .num_args(0) - .display_order(92), + .help_heading(tr_s("help.import_specific_options")) + .display_order(94), ), ) .subcommand( diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 342226d..09921aa 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -172,5 +172,15 @@ "search_about": "Search for books by title, author, editor, genre or language.", "search_query_help": "The keyword or phrase to search for.", "search_short_help": "Show compact view (ID, Title, Author, Editor, Year, ISBN).", - "search.no_results": "No results found." + "search.no_results": "No results found.", + "help.global_options": "Global Options", + "help.list_specific_options": "List-specific options", + "help.search_specific_options": "Search-specific options", + "help.add_specific_options": "Add Book specific options", + "help.edit_specific_options": "Edit Book specific options", + "help.del_specific_options": "Delete Book specific options", + "help.config_specific_options": "Config-specific options", + "help.import_specific_options": "Import-specific options", + "help.export_specific_options": "Export-specific options", + "help.backup_specific_options": "Backup-specific options" } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 31edac6..cbcfb63 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -172,5 +172,15 @@ "search_about": "Cerca libri per titolo, autore, editore, genere o lingua.", "search_query_help": "La parola o frase da cercare.", "search_short_help": "Mostra vista compatta (ID, Titolo, Autore, Editore, Anno, ISBN).", - "search.no_results": "Nessun risultato trovato." + "search.no_results": "Nessun risultato trovato.", + "help.global_options": "Opzioni generali", + "help.list_specific_options": "Opzioni specifiche comando 'list'", + "help.search_specific_options": "Opzioni specifiche comando 'search'", + "help.add_specific_options": "Opzioni specifiche comando 'add'", + "help.edit_specific_options": "Opzioni specifiche comando 'edit'", + "help.del_specific_options": "Opzioni specifiche comando 'del'", + "help.config_specific_options": "Opzioni specifiche comando 'config'", + "help.import_specific_options": "Opzioni specifiche comando 'import'", + "help.export_specific_options": "Opzioni specifiche comando 'export'", + "help.backup_specific_options": "Opzioni specifiche comando 'backup'" } From 7606391953c1b036f8a21a844c9c367823f0c9ad Mon Sep 17 00:00:00 2001 From: Alessandro Maestri Date: Tue, 11 Nov 2025 14:36:36 +0100 Subject: [PATCH 2/2] docs(updated): updated README.md --- README.md | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4a0c935..7d49e88 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,20 @@ and import/export support. --- -### ✨ New in v0.4.5 - -**🔍 Full-text book search** - -- Introduced the new `search` command: - ```bash - librius search [--short] - ``` - #### Example usage: - ```bash - $ librius search "dune" - $ librius search "frank herbert" --short - ``` -- Performs full-text lookup across **title, author, editor, genre, and language** fields. -- Supports both **compact** (`--short`) and **full** table views. -- Uses the same localized message system (`tr()`) as the rest of the CLI. -- Unified output style with `print_info`, `print_ok`, and `print_warn` for consistent visual feedback. +### ✨ New in v0.4.6 + +**🔧 CLI help reorganization and localization** + +- Reorganized the **command index** in the main help output to provide a clearer, more intuitive structure. + - Commands are now grouped into: + - 📚 **Book commands** — `list`, `search`, `add`, `edit`, `del` + - ⚙️ **App commands** — `config`, `backup`, `export`, `import` + - ❓ **Other commands** — `help` +- Added **full localization** to all help section titles (`help_heading`), ensuring the help text is completely + translated and consistent between English and Italian. +- Improved the **readability and logical flow** of command listings and global options. +- Updated `display_order` values to match the new command grouping. +- Refactored `cli.rs` to simplify future maintenance and localization. --- @@ -501,7 +498,7 @@ The latest migration adds a unique index on `isbn` to guarantee that duplicate imports are ignored safely. ```sqlite -CREATE UNIQUE INDEX IF NOT EXISTS idx_books_isbn ON books(isbn); +CREATE UNIQUE INDEX IF NOT EXISTS idx_books_isbn ON books (isbn); ``` - On first launch → creates books table.