From 0e594080088f352e8746c6075a32b5649ff0606a Mon Sep 17 00:00:00 2001 From: Alessandro Maestri Date: Wed, 12 Nov 2025 22:40:22 +0100 Subject: [PATCH 1/3] feat(db): add database management command (init, reset, copy) - Added `librius db` command for database lifecycle management. - Supports `--init`, `--reset`, and `--copy -f ` flags. - Integrated with configuration database path (`config.database`). - Updated CLI parser, i18n messages, and docs for v0.5.1. --- CHANGELOG.md | 45 ++++++-- Cargo.lock | 230 ++++++++++++++++++--------------------- Cargo.toml | 2 +- README.md | 17 +++ src/cli/args.rs | 47 +++++++- src/cli/dispatch.rs | 11 +- src/commands/db.rs | 111 +++++++++++++++++++ src/commands/mod.rs | 2 + src/db/load_db.rs | 61 ++++++++--- src/db/mod.rs | 2 +- src/i18n/locales/en.json | 15 ++- src/i18n/locales/it.json | 15 ++- src/main.rs | 2 +- 13 files changed, 402 insertions(+), 158 deletions(-) create mode 100644 src/commands/db.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 238f013..b12e972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,42 @@ All notable changes to this project will be documented in this file. +## [0.5.1] - 2025-11-12 + +### Added + +- **New `db` command** for database management: + - `librius db --init` β†’ initializes a new database (or resets the existing one). + - `librius db --reset` β†’ alias of `--init`, provided for clarity. + - `librius db --copy -f ` β†’ copies the current database (as defined in `config.database`) to a new file. +- Automatic use of the database path from the configuration file (`database:` key in `librius.yaml`). +- Added localized messages and colored output for database operations. + +### Fixed + +- CLI parsing for `--copy` now correctly behaves as a flag (no value required). +- Improved integration between configuration and database initialization routines. + +### Notes + +This version introduces a simple and safe way to initialize, reset, or back up your Librius database directly from the +command line. + +--- + ## [0.5.0] - 2025-11-11 ### πŸ§ͺ Added - Introduced a **complete automated test suite** covering both database and CLI layers. - Implemented `setup_temp_db()` utility for creating **temporary SQLite databases** in the system temp directory: - - Windows β†’ `%TEMP%\librius_test_*.db` - - macOS / Linux β†’ `/tmp/librius_test_*.db` + - Windows β†’ `%TEMP%\librius_test_*.db` + - macOS / Linux β†’ `/tmp/librius_test_*.db` - Added **unit tests** for database insert and search operations. - Added **integration tests** for: - - CLI commands (`--help`, `search`, etc.) using `assert_cmd` and `predicates`. - - Database schema and consistency validation. - - ISBN normalization and formatting. + - CLI commands (`--help`, `search`, etc.) using `assert_cmd` and `predicates`. + - Database schema and consistency validation. + - ISBN normalization and formatting. - All tests now use the **real production schema** for reliable, cross-platform testing. --- @@ -22,12 +45,12 @@ All notable changes to this project will be documented in this file. ### πŸ”§ Changed - Performed a **modular refactor of the CLI** (`cli.rs` β†’ `cli/` directory): - - Split the monolithic `cli.rs` into three logical units: - - `args.rs` β€” defines the full command tree and global flags. - - `dispatch.rs` β€” routes parsed commands to their handlers. - - `mod.rs` β€” re-exports and integrates the CLI components. - - Improved code readability, testability, and long-term maintainability. - - Prepared CLI for future integration with the `librius_core` crate and the GUI frontend. + - Split the monolithic `cli.rs` into three logical units: + - `args.rs` β€” defines the full command tree and global flags. + - `dispatch.rs` β€” routes parsed commands to their handlers. + - `mod.rs` β€” re-exports and integrates the CLI components. + - Improved code readability, testability, and long-term maintainability. + - Prepared CLI for future integration with the `librius_core` crate and the GUI frontend. - Simplified the internal command dispatch logic and aligned display order for consistent help output. --- diff --git a/Cargo.lock b/Cargo.lock index 93a9f0c..93574c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,9 +34,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -165,9 +165,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -224,9 +224,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ "libbz2-rs-sys", ] @@ -242,9 +242,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" dependencies = [ "find-msvc-tools", "jobserver", @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codegen" @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -457,9 +457,9 @@ checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] name = "encoding_rs" @@ -753,7 +753,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -862,9 +862,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" dependencies = [ "atomic-waker", "bytes", @@ -966,9 +966,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -979,9 +979,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -992,11 +992,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1007,42 +1006,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1089,9 +1084,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1115,9 +1110,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -1125,9 +1120,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "isbn2" @@ -1158,9 +1153,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1197,7 +1192,7 @@ dependencies = [ [[package]] name = "librius" -version = "0.5.0" +version = "0.5.1" dependencies = [ "assert_cmd", "chrono", @@ -1249,9 +1244,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" @@ -1358,15 +1353,15 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags", "cfg-if", @@ -1396,9 +1391,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -1502,9 +1497,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1517,9 +1512,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppmd-rust" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" +checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" [[package]] name = "predicates" @@ -1575,9 +1570,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1594,9 +1589,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1751,9 +1746,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.33" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", "rustls-pki-types", @@ -1764,18 +1759,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -1896,7 +1891,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "itoa", "ryu", "serde", @@ -1979,9 +1974,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2165,9 +2160,9 @@ checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -2209,9 +2204,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -2226,7 +2221,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.0", "serde_core", "serde_spanned", "toml_datetime", @@ -2372,9 +2367,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -2475,9 +2470,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -2486,25 +2481,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -2515,9 +2496,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2525,31 +2506,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -2838,9 +2819,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "xattr" @@ -2860,11 +2841,10 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2872,9 +2852,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -2945,9 +2925,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -2956,9 +2936,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -2967,9 +2947,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", @@ -2987,7 +2967,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.11.4", + "indexmap 2.12.0", "memchr", "thiserror 2.0.17", "zopfli", @@ -3008,7 +2988,7 @@ dependencies = [ "flate2", "getrandom 0.3.4", "hmac", - "indexmap 2.11.4", + "indexmap 2.12.0", "lzma-rust2", "memchr", "pbkdf2", @@ -3028,9 +3008,9 @@ checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" [[package]] name = "zopfli" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index e262810..713e38b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "librius" -version = "0.5.0" +version = "0.5.1" edition = "2024" authors = ["Alessandro Maestri "] description = "A personal library manager CLI written in Rust." diff --git a/README.md b/README.md index cb24be5..f04608d 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ cargo install rtimelogger | **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` | +| **Database management** | `librius db` | DB Management via `--init`, `--reset`, `--copy -f\|--file ` | | **Backup** | `librius backup` | Create plain or compressed database backups (`.sqlite`, `.zip`, `.tar.gz`) | | **Export** | `librius export` | Export data in CSV, JSON, or XLSX format | | **Import** | `librius import` | Import data from CSV or JSON files (duplicate-safe via ISBN) | @@ -207,6 +208,22 @@ $ librius config [--print] [--init] [--edit] [--editor ] - `--init` Create default config file - `--edit` Open config file in editor - `--editor ` Specify editor (default: `$EDITOR` or `nano` +- `--help` Show command help + +### πŸ—„οΈ Database management + +Manage the Librius database lifecycle and backups. + +```bash +librius db [--init] [--reset] [--copy -f|--file ] +``` + +**Options**: + +- `--init` Initialize a new database +- `--reset` Reset the database (deletes all data) +- `--copy -f|--file ` Copy the database to a new file +- `--help` Show command help ### πŸ’Ύ backup diff --git a/src/cli/args.rs b/src/cli/args.rs index f4a8037..ea70057 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -210,18 +210,61 @@ pub fn build_cli() -> Command { .display_order(64), ), ) + .subcommand( + Command::new("db") + .about(tr_s("db_about")) + .display_order(65) + .arg( + Arg::new("init") + .long("init") + .help(tr_s("db_init_help")) + .action(ArgAction::SetTrue) + .conflicts_with_all(["reset", "copy"]) + .help_heading(tr_s("help.db_specific_options")) + .display_order(66), + ) + .arg( + Arg::new("reset") + .long("reset") + .help(tr_s("db_reset_help")) + .action(ArgAction::SetTrue) + .conflicts_with_all(["init", "copy"]) + .help_heading(tr_s("help.db_specific_options")) + .display_order(67), + ) + .arg( + Arg::new("copy") + .long("copy") + .help(tr_s("db_copy_help")) + .action(ArgAction::SetTrue) + .conflicts_with_all(["init", "reset"]) + .help_heading(tr_s("help.db_specific_options")) + .display_order(68), + ) + .arg( + Arg::new("file") + .short('f') + .long("file") + .help(tr_s("db_file_help")) + .requires("copy") + .num_args(1) + .value_name("PATH") + .help_heading(tr_s("help.db_specific_options")) + .display_order(69), + ), + ) // πŸ’Ύ backup command .subcommand( Command::new("backup") .about(tr_s("backup_about")) - .display_order(70) + .display_order(75) .arg( Arg::new("compress") .long("compress") .help(tr_s("backup_compress_help")) .action(ArgAction::SetTrue) .help_heading(tr_s("help.backup_specific_options")) - .display_order(71), + .display_order(76), ), ) // πŸ“€ export command diff --git a/src/cli/dispatch.rs b/src/cli/dispatch.rs index 06cab85..659a503 100644 --- a/src/cli/dispatch.rs +++ b/src/cli/dispatch.rs @@ -1,11 +1,12 @@ use crate::cli::{Commands, build_cli}; use crate::i18n::tr; use crate::utils::print_err; -use crate::{handle_config, handle_edit_book, handle_list, handle_search, tr_with}; +use crate::{AppConfig, handle_config, handle_edit_book, handle_list, handle_search, tr_with}; use rusqlite::Connection; /// Dispatch principale dei comandi pub fn run_cli( + config: &AppConfig, matches: &clap::ArgMatches, conn: &mut Connection, ) -> Result<(), Box> { @@ -36,6 +37,14 @@ pub fn run_cli( editor, }; Ok(handle_config(&cmd)?) + } else if let Some(("db", sub_m)) = matches.subcommand() { + let init = sub_m.get_flag("init"); + let reset = sub_m.get_flag("reset"); + let copy = sub_m.get_flag("copy"); + let file = sub_m.get_one::("file").map(|s| s.as_str()); + + crate::commands::db::handle_db(config, init, reset, copy, file)?; + Ok(()) } else if let Some(("edit", sub_m)) = matches.subcommand() { if let Some(("book", book_m)) = sub_m.subcommand() { handle_edit_book(conn, book_m)?; // βœ… integrazione comando edit book diff --git a/src/commands/db.rs b/src/commands/db.rs new file mode 100644 index 0000000..84cd10b --- /dev/null +++ b/src/commands/db.rs @@ -0,0 +1,111 @@ +use crate::{AppConfig, print_err, print_ok, print_warn, tr, tr_with}; +use rusqlite::Connection; +use std::error::Error; +use std::fs; +use std::path::Path; + +pub fn handle_db( + config: &AppConfig, + init: bool, + reset: bool, + copy: bool, + file: Option<&str>, +) -> Result<(), Box> { + if init || reset { + return init_db(config); + } + + if copy { + if let Some(dest) = file { + return copy_db(config, dest); + } + print_err(&tr("db_copy_missing_file")); + return Ok(()); + } + + print_warn(&tr("db_no_action")); + Ok(()) +} + +fn init_db(config: &AppConfig) -> Result<(), Box> { + let path = Path::new(&config.database); + if path.exists() { + fs::remove_file(path)?; + print_ok( + &tr_with("db_reset_done", &[("path", &path.to_string_lossy())]), + true, + ); + } else { + print_ok( + &tr_with("db_init_creating", &[("path", &path.to_string_lossy())]), + true, + ); + } + + let conn = Connection::open(path)?; + create_schema(&conn)?; + print_ok( + &tr_with("db_init_done", &[("path", &path.to_string_lossy())]), + true, + ); + Ok(()) +} + +/// Crea le tabelle di base nel database appena inizializzato. +/// +/// Al momento include: +/// - books β†’ archivio principale dei libri +/// - log β†’ tabella di log operazioni +fn create_schema(conn: &Connection) -> Result<(), Box> { + // Tabella principale "books" + conn.execute_batch( + " + CREATE TABLE IF NOT EXISTS books ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + author TEXT NOT NULL, + editor TEXT NOT NULL, + year INTEGER NOT NULL, + isbn TEXT NOT NULL, + language TEXT, + pages INTEGER, + genre TEXT, + summary TEXT, + room TEXT, + shelf TEXT, + row TEXT, + position TEXT, + added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + operation TEXT NOT NULL, + target TEXT DEFAULT '', + message TEXT NOT NULL + ); + ", + )?; + + print_ok(&tr("db.schema.created"), true); + Ok(()) +} + +fn copy_db(config: &AppConfig, dest: &str) -> Result<(), Box> { + let src = Path::new(&config.database); + if !src.exists() { + print_err(&tr("db_no_source")); + return Ok(()); + } + + fs::copy(src, dest)?; + print_ok( + &tr_with( + "db_copy_done", + &[("source", &src.to_string_lossy()), ("destination", dest)], + ), + true, + ); + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 48400dd..e5ca38e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,6 +8,7 @@ pub mod add; pub mod add_book; pub mod backup; pub mod config; +pub mod db; pub mod del_book; pub mod edit_book; pub mod export; @@ -19,6 +20,7 @@ pub use add::handle_add; pub use add_book::handle_add_book; pub use backup::handle_backup; pub use config::handle_config; +pub use db::handle_db; pub use del_book::handle_del_book; pub use edit_book::handle_edit_book; pub use export::handle_export_csv; diff --git a/src/db/load_db.rs b/src/db/load_db.rs index 09a5df4..3ac9fb8 100644 --- a/src/db/load_db.rs +++ b/src/db/load_db.rs @@ -5,16 +5,52 @@ use crate::utils::{is_verbose, print_err, print_info, print_ok, write_log}; use rusqlite::{Connection, Result}; use std::path::Path; +use std::fs; +use std::path::PathBuf; + +/// Restituisce il percorso completo del file di database. +/// +/// La logica Γ¨: +/// - Se esiste `LIBRIUS_DB_PATH` nelle variabili d’ambiente β†’ usa quello. +/// - Altrimenti crea (se necessario) la directory predefinita dell’app: +/// - Linux/macOS: `~/.local/share/librius/librius.db` +/// - Windows: `%APPDATA%\\Librius\\librius.db` +pub fn get_db_path() -> PathBuf { + // 1️⃣ Se definita, rispetta la variabile d’ambiente + if let Ok(custom) = std::env::var("LIBRIUS_DB_PATH") { + return PathBuf::from(custom); + } + + // 2️⃣ Altrimenti scegli la directory dati predefinita + let base_dir = dirs::data_dir() + .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); + + let librius_dir = base_dir.join("librius"); + if !librius_dir.exists() { + let _ = fs::create_dir_all(&librius_dir); + } + + librius_dir.join("librius.db") +} + /// Opens or initializes the SQLite database. /// -/// - If the database file does not exist, it creates a new one and its tables. -/// - If it exists, it just opens it and applies pending migrations. +/// - If `config.database` exists, that path is used. +/// - Otherwise, falls back to `get_db_path()`. +/// - If the DB file does not exist, it is created and initialized. +/// - Pending migrations are applied. /// - Each operation is logged in the `log` table. -/// pub fn start_db(config: &AppConfig) -> Result { - let db_path = Path::new(&config.database); + // 1️⃣ Determina il percorso del database + let db_path = if config.database.trim().is_empty() { + get_db_path() + } else { + Path::new(&config.database).to_path_buf() + }; + let db_exists = db_path.exists(); + // 2️⃣ Log di apertura o creazione if db_exists { print_info( &tr_with( @@ -33,10 +69,10 @@ pub fn start_db(config: &AppConfig) -> Result { ); } - // Try to open connection - let conn = Connection::open(db_path)?; + // 3️⃣ Apertura connessione + let conn = Connection::open(&db_path)?; - // Create log table immediately (for logging) + // 4️⃣ Tabella log (sempre disponibile) conn.execute( "CREATE TABLE IF NOT EXISTS log ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -48,7 +84,7 @@ pub fn start_db(config: &AppConfig) -> Result { [], )?; - // Initialize structure if missing + // 5️⃣ Inizializza schema se nuovo DB if !db_exists { print_info(&tr("db.schema.initializing"), is_verbose()); if let Err(e) = ensure_schema(&conn) { @@ -63,7 +99,7 @@ pub fn start_db(config: &AppConfig) -> Result { let _ = write_log(&conn, "DB_INIT_OK", "DB", &tr("log.db.schema.init")); } - // Apply migrations + // 6️⃣ Esegui eventuali migrazioni match migrate_db::run_migrations(&conn) { Err(e) => { print_err(&tr_with("db.migrate.failed", &[("error", &e.to_string())])); @@ -72,10 +108,7 @@ pub fn start_db(config: &AppConfig) -> Result { Ok(result) => match result { migrate_db::MigrationResult::Applied(patches) => { print_ok(&tr("db.migrate.applied"), is_verbose()); - let msg = &tr_with( - "log.db.patch_applied", - &[("patchCreata nuova configurazione in", &patches.join(", "))], - ); + let msg = &tr_with("log.db.patch_applied", &[("patches", &patches.join(", "))]); let _ = write_log(&conn, "DB_MIGRATION_OK", "DB", msg); } migrate_db::MigrationResult::None => { @@ -111,7 +144,7 @@ pub fn ensure_schema(conn: &Connection) -> Result<()> { shelf TEXT, row TEXT, position TEXT, - added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );", )?; Ok(()) diff --git a/src/db/mod.rs b/src/db/mod.rs index a63d314..42d0df2 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -14,6 +14,6 @@ pub mod migrate_db; pub mod search; pub use books::{get_book_fields, update_book_by_id, update_book_by_isbn}; -pub use load_db::{ensure_schema, init_db, start_db}; +pub use load_db::{ensure_schema, get_db_path, init_db, start_db}; pub use migrate_db::{MigrationResult, run_migrations}; pub use search::search_books; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 09921aa..50ed2b4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -182,5 +182,18 @@ "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" + "help.backup_specific_options": "Backup-specific options", + "help.db_specific_options": "DB-management specific options", + "db_about": "Database management", + "db_init_help": "Initialize a new database (overwrite existing one if present).", + "db_reset_help": "Reset the current database (alias of --init).", + "db_copy_help": "Copy the current database to a new file.", + "db_file_help": "Destination file path for the copy.", + "db_no_action": "No operation performed. Use --init, --reset or --copy.", + "db_no_source": "No existing database to copy.", + "db_copy_missing_file": "You must specify --file when using --copy.", + "db_init_creating": "Creating database at {path} ...", + "db_init_done": "Database initialized at {path}.", + "db_reset_done": "Database reset at {path}.", + "db_copy_done": "Database copied from {source} to {destination}." } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index cbcfb63..9f3db05 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -182,5 +182,18 @@ "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'" + "help.backup_specific_options": "Opzioni specifiche comando 'backup'", + "help.db_specific_options": "Opzioni specifiche comando 'db'", + "db_about": "Gestione del database locale.", + "db_init_help": "Inizializza un nuovo database (sovrascrive quello esistente).", + "db_reset_help": "Resetta il database attuale (equivalente a --init).", + "db_copy_help": "Copia il database attuale in un nuovo file.", + "db_file_help": "Percorso di destinazione del file di copia.", + "db_no_action": "Nessuna operazione eseguita. Usa --init, --reset o --copy.", + "db_no_source": "Nessun database trovato da copiare.", + "db_copy_missing_file": "Devi specificare --file con --copy.", + "db_init_creating": "Creo il database in {path} ...", + "db_init_done": "Database inizializzato in {path}.", + "db_reset_done": "Database resettato in {path}.", + "db_copy_done": "Database copiato da {source} a {destination}." } diff --git a/src/main.rs b/src/main.rs index ae44638..f86c1a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,7 +83,7 @@ fn main() { // 6️⃣ CLI localizzata ed esecuzione comandi // ------------------------------------------------------------ let matches = build_cli().get_matches(); - if let Err(e) = run_cli(&matches, &mut conn) { + if let Err(e) = run_cli(&config, &matches, &mut conn) { print_err(&format!("{} {}", ERR, e)); } } From e8df9460a1a54f564213ec81441e4b23762abc36 Mon Sep 17 00:00:00 2001 From: Alessandro Maestri Date: Wed, 12 Nov 2025 23:17:33 +0100 Subject: [PATCH 2/3] feat(list): add compact details view and enforce --details dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `--compact` flag for `list --id --details` to hide empty fields in vertical view. - Fixed table headers and ensured correct field order (id β†’ added_at). - The `--compact` flag now requires `--details`, preventing invalid combinations. - Updated i18n messages and CLI help for better usability. --- CHANGELOG.md | 13 ++++++ README.md | 6 ++- src/cli/args.rs | 10 +++++ src/cli/dispatch.rs | 3 +- src/commands/list.rs | 3 +- src/i18n/locales/en.json | 9 +++- src/i18n/locales/it.json | 9 +++- src/utils/table.rs | 82 +++++++++++++++++++++++++++---------- tests/librius_core_tests.rs | 4 +- 9 files changed, 110 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12e972..f26592c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,24 @@ All notable changes to this project will be documented in this file. - `librius db --copy -f ` β†’ copies the current database (as defined in `config.database`) to a new file. - Automatic use of the database path from the configuration file (`database:` key in `librius.yaml`). - Added localized messages and colored output for database operations. +- New `--compact` flag for `list --id --details`: + - Hides empty or null fields in the vertical table view. + - Useful for cleaner, shorter output when many fields are unused. + +### Changed + +- The `--compact` flag in `list` is now dependent on `--details`. + - Using `--compact` without `--details` will result in an error message. + - This ensures consistent CLI behavior and prevents meaningless flag combinations. ### Fixed - CLI parsing for `--copy` now correctly behaves as a flag (no value required). - Improved integration between configuration and database initialization routines. +- Corrected vertical table rendering (`list --id --details`): + - Proper localized headers (β€œField” / β€œValue”) are now displayed. + - Columns now follow the database schema order instead of alphabetical order. + - Improved compatibility with existing `tabled` crate version. ### Notes diff --git a/README.md b/README.md index f04608d..1477ded 100644 --- a/README.md +++ b/README.md @@ -124,14 +124,16 @@ cargo install rtimelogger List all books or a specific book by ID. ```bash -$ librius list [--short] [--id ] [--details] +$ librius list [--short] [--id ] [--details] [--compact] ``` **Options**: - `--short` Compact view - `--id` Show book by ID -- `--details` Show extended metadata +- `--details` Show extended metadata (requires `--id`) +- `--compact` Compact list view (requires `--details`) +- `--help` Show command help ### πŸ” search diff --git a/src/cli/args.rs b/src/cli/args.rs index ea70057..da02091 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -66,9 +66,19 @@ pub fn build_cli() -> Command { Arg::new("details") .long("details") .help(tr_s("help.list.details")) + .requires("id") .action(ArgAction::SetTrue) .help_heading(tr_s("help.list_specific_options")) .display_order(13), + ) + .arg( + Arg::new("compact") + .long("compact") + .help(tr_s("help.list.compact")) + .requires("details") + .action(ArgAction::SetTrue) + .help_heading(tr_s("help.list_specific_options")) + .display_order(14), ), ) // πŸ” search command diff --git a/src/cli/dispatch.rs b/src/cli/dispatch.rs index 659a503..e1eb625 100644 --- a/src/cli/dispatch.rs +++ b/src/cli/dispatch.rs @@ -14,7 +14,8 @@ pub fn run_cli( let short = matches.get_flag("short"); let id = matches.get_one::("id").copied(); let details = matches.get_flag("details"); - handle_list(conn, short, id, details)?; + let compact = matches.get_flag("compact"); + handle_list(conn, short, id, details, compact)?; Ok(()) } else if let Some(("search", sub_m)) = matches.subcommand() { if let Some(query) = sub_m.get_one::("query") { diff --git a/src/commands/list.rs b/src/commands/list.rs index d61de6c..438e880 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -65,6 +65,7 @@ pub fn handle_list( _short: bool, id: Option, _details: bool, + compact: bool, ) -> Result<(), Box> { // If user asked for details without specifying an id, show a localized // error message and do not display the list. @@ -112,7 +113,7 @@ pub fn handle_list( if id.is_some() { let book = &books[0]; println!("\nπŸ“– {} {:?}\n", tr("list.book_details_for_id"), book.id); - build_vertical_table(book); + build_vertical_table(book, compact); } else { // Otherwise show the list (short or full) println!("\n{}\n", tr("app.library.info")); diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 50ed2b4..e4c22f1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -195,5 +195,12 @@ "db_init_creating": "Creating database at {path} ...", "db_init_done": "Database initialized at {path}.", "db_reset_done": "Database reset at {path}.", - "db_copy_done": "Database copied from {source} to {destination}." + "db_copy_done": "Database copied from {source} to {destination}.", + "error.unable_display_record": "Unable to display record: not an object", + "list.header.pages": "Pages", + "list.header.genre": "Genre", + "list.header.summary": "Summary", + "list.header.row": "Row", + "list.header.added_at": "Added At", + "help.list.compact": "Show only fields with values in detailed view." } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 9f3db05..7f56793 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -195,5 +195,12 @@ "db_init_creating": "Creo il database in {path} ...", "db_init_done": "Database inizializzato in {path}.", "db_reset_done": "Database resettato in {path}.", - "db_copy_done": "Database copiato da {source} a {destination}." + "db_copy_done": "Database copiato da {source} a {destination}.", + "error.unable_display_record": "Impossibile visualizzare il record: non Γ¨ un oggetto", + "list.header.pages": "Pagine", + "list.header.genre": "Genere", + "list.header.summary": "Riepilogo", + "list.header.row": "Riga", + "list.header.added_at": "Aggiunto alle", + "help.list.compact": "Mostra solo i campi valorizzati nella vista dettagliata." } diff --git a/src/utils/table.rs b/src/utils/table.rs index 5c77a03..52229ff 100644 --- a/src/utils/table.rs +++ b/src/utils/table.rs @@ -3,6 +3,7 @@ //! Provides a unified interface for rendering tabular data using the `tabled` crate, //! ensuring consistent visual style and alignment across commands. +use crate::{print_warn, tr}; use serde::Serialize; use serde_json::Value; use tabled::settings::{Alignment, Modify, Style, object::Rows}; @@ -38,36 +39,75 @@ where .to_string() } -pub fn build_vertical_table(record: &T) { - // Converte la struct in una mappa JSON dinamica +#[derive(Tabled)] +struct VerticalRow { + #[tabled(rename = "Field")] + field: String, + #[tabled(rename = "Value")] + value: String, +} + +pub fn build_vertical_table(record: &T, compact: bool) { + // Serializza in mappa dinamica let value = serde_json::to_value(record).expect("Failed to serialize record"); if let Value::Object(map) = value { - // Costruiamo un vettore di tuple (campo, valore) - let mut rows: Vec<(String, String)> = map - .into_iter() - .map(|(key, val)| { - let val_str = match val { - Value::Null => String::from("β€”"), - Value::String(s) => s, + // βœ… Ordine corretto dei campi + let field_order = [ + "id", "title", "author", "editor", "year", "isbn", "language", "pages", "genre", + "summary", "room", "shelf", "row", "position", "added_at", + ]; + + // βœ… Etichette localizzate + let field_labels = [ + tr("list.header.id"), + tr("list.header.title"), + tr("list.header.author"), + tr("list.header.editor"), + tr("list.header.year"), + tr("list.header.ISBN"), + tr("list.header.language"), + tr("list.header.pages"), + tr("list.header.genre"), + tr("list.header.summary"), + tr("list.header.room"), + tr("list.header.shelf"), + tr("list.header.row"), + tr("list.header.position"), + tr("list.header.added_at"), + ]; + + // βœ… Costruisci righe ordinate + let mut rows: Vec = Vec::new(); + + for (i, key) in field_order.iter().enumerate() { + let value_str = map + .get(*key) + .map(|v| match v { + Value::Null => "β€”".to_string(), + Value::String(s) => s.clone(), Value::Number(n) => n.to_string(), Value::Bool(b) => b.to_string(), other => other.to_string(), - }; - (key, val_str) - }) - .collect(); + }) + .unwrap_or_else(|| "β€”".to_string()); - // Ordina alfabeticamente per nome del campo - rows.sort_by(|a, b| a.0.cmp(&b.0)); + // βœ… In modalitΓ  compatta, salta i campi vuoti + if compact && value_str == "β€”" { + continue; + } - // Crea tabella verticale - let style = Style::rounded(); - // Build an owned string representation to avoid borrowing temporaries - let table_str = Table::new(rows).with(style).to_string(); + rows.push(VerticalRow { + field: field_labels[i].clone(), + value: value_str, + }); + } - println!("{}", table_str); + // βœ… Crea e stampa la tabella + let mut table = Table::new(rows); + table.with(Style::rounded()); + println!("{}", table); } else { - println!("⚠️ Unable to display record: not an object"); + print_warn(&tr("error.unable_display_record")); } } diff --git a/tests/librius_core_tests.rs b/tests/librius_core_tests.rs index b1f459d..e128e2e 100644 --- a/tests/librius_core_tests.rs +++ b/tests/librius_core_tests.rs @@ -39,7 +39,7 @@ fn exercise_list_handler() -> Result<(), Box> { ], )?; - handle_list(&conn, false, None, false)?; + handle_list(&conn, false, None, false, false)?; Ok(()) } @@ -80,6 +80,6 @@ fn exercise_list_handler_short() -> Result<(), Box> { ], )?; - handle_list(&conn, true, None, false)?; + handle_list(&conn, true, None, false, false)?; Ok(()) } From 8648ecbc12a80320b6d59c16378fd3775d462055 Mon Sep 17 00:00:00 2001 From: Alessandro Maestri Date: Wed, 12 Nov 2025 23:26:46 +0100 Subject: [PATCH 3/3] Amended README.md --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1477ded..01fbe02 100644 --- a/README.md +++ b/README.md @@ -23,45 +23,44 @@ and import/export support. --- -### ✨ New in v0.5.0 +### ✨ New in v0.5.1 -**πŸ§ͺ Complete test suite** +**πŸ—„οΈ Database management command** -- Introduced a robust **automated testing framework** for both CLI and database layers. -- Tests now use the **real production schema**, ensuring consistent validation of all database operations. -- Added a new helper `setup_temp_db()` that automatically creates temporary SQLite databases: - - Windows β†’ `%TEMP%\librius_test_*.db` - - macOS / Linux β†’ `/tmp/librius_test_*.db` -- Unified test structure under `/tests/` for clarity and scalability. +A brand new `db` command has been introduced for complete database lifecycle control: -Example structure: +```bash +librius db --init +``` + +Initializes or resets the current database. + +```bash +librius db --reset +``` + +Alias of `--init`. -``` text -tests/ -β”œβ”€β”€ common.rs # Shared helpers (DB setup, fixtures) -β”œβ”€β”€ db_tests.rs # Database-level tests -β”œβ”€β”€ cli_tests.rs # CLI behavior tests -β”œβ”€β”€ isbn_tests.rs # ISBN module tests -└── librius_core_tests.rs # Core command handler tests +```bash +librius db --copy -f|--file ``` -**πŸ”§ Modular CLI refactor** +Copies the database defined in your librius.yaml configuration to a new file. -- Reorganized the CLI into a **modular structure** for better readability and future reuse: - - `cli/args.rs` β†’ Command definitions and global options. - - `cli/dispatch.rs` β†’ Command routing and subcommand handling. - - `cli/mod.rs` β†’ Unified CLI interface for main.rs. -- Simplified the main dispatcher logic and improved localization consistency. -- Prepared the CLI subsystem for integration with the upcoming `librius_core` library and GUI frontend. +> The database path is automatically read from the database: key in the configuration file. ---- +**πŸ“š Improved list details view** + +- Added the -`-compact` flag for list `--id --details` to hide empty fields in the vertical table. +- The `--compact` flag now requires `--details`, ensuring consistent CLI behavior. +- Fixed table headers that previously displayed `String`; now they correctly show localized Field / Value columns. +- Field order in detailed view now matches the database schema (`id β†’ added_at`). -**🧱 Internal improvements** +**🐞 Fixes & improvements** -- Removed legacy `#[cfg(test)]` blocks from source code. -- Cleaned up all build and Clippy warnings. -- Verified stability across all major platforms (Windows, macOS, Linux). -- Established the technical foundation for continuous integration (coming in `v0.5.1`). +- Fixed `--copy` flag incorrectly requiring a value. +- Improved integration between configuration and database operations. +- Enhanced localized messages and help text for better clarity. ---