Skip to content

Commit ce803c0

Browse files
authored
Merge pull request #75 from umpire274/v0.5.5
Add multi-file fortune support + smarter cache repetition (v0.5.5)
2 parents 44edc18 + 72e0971 commit ce803c0

File tree

10 files changed

+427
-80
lines changed

10 files changed

+427
-80
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog
22

3+
## [0.5.5] - 2025-02-07
4+
5+
### Added
6+
7+
- Support for **multiple fortune files** via `--file <PATH>` (repeatable).
8+
- New configuration key `fortune_files` (list).
9+
When populated, it takes priority over `default_file`.
10+
- Automatic **config migration**: if `fortune_files` is missing or empty,
11+
it is initialized with the value of `default_file`.
12+
- Intelligent **no-repeat** mechanism:
13+
rfortune avoids showing the **same quote twice in a row** from the **same file**.
14+
- Unified JSON-based quote cache shared across multiple fortune files.
15+
16+
### Changed
17+
18+
- `files_fortune` is now deprecated and replaced by `fortune_files`.
19+
Existing configurations remain compatible via `serde(alias)`.
20+
21+
### Removed
22+
23+
- Deprecated single-file `print_random()` function.
24+
25+
---
26+
327
## [0.5.3] - 2025-11-05
428

529
### Fixed

Cargo.lock

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rfortune"
3-
version = "0.5.3"
3+
version = "0.5.5"
44
edition = "2024"
55
authors = ["Umpire274 <umpire274@gmail.com>"]
66
description = "A Rust-based clone of the classic UNIX 'fortune' command"
@@ -31,4 +31,5 @@ dirs = "6.0.0"
3131
serde = { version = "1.0.228", features = ["derive"] }
3232
serde_yaml = "0.9.33"
3333
atty = "0.2.14"
34+
serde_json = "1.0.145"
3435

README.md

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,83 @@ rfortune cache clear
192192

193193
---
194194

195-
## 📁 File Format
195+
### Configuration (`rfortune.conf`)
196+
197+
Example:
198+
199+
```yaml
200+
default_file: "/home/user/.local/share/rfortune/rfortune.dat"
201+
print_title: true
202+
use_cache: true
203+
204+
# Optional: load additional quote files
205+
fortune_files:
206+
- "/usr/local/share/rfortune/philosophy.fort"
207+
- "/usr/local/share/rfortune/tech.fort"
208+
```
209+
210+
Priority order:
211+
212+
1. `--file <PATH>` CLI argument(s)
213+
2. `fortune_files` list in config
214+
3. `default_file`
215+
216+
---
217+
218+
### Multiple Sources Configuration
219+
220+
You can load quotes from multiple files and rfortune will automatically
221+
choose one at random:
222+
223+
```bash
224+
rfortune --file my_quotes.fort --file jokes.fort --file tech.fort
225+
```
226+
227+
Or configure them permanently:
228+
229+
```yaml
230+
fortune_files:
231+
- "/path/to/my_quotes.fort"
232+
- "/path/to/jokes.fort"
233+
```
234+
235+
If both are present, **CLI always wins**.
236+
237+
### Smart Quote Repetition Avoidance
238+
239+
rfortune keeps a small cache and automatically avoids repeating
240+
the **same quote twice in a row**, but **only for quotes from the same file**.
241+
242+
This keeps the output natural across multiple sources.
243+
244+
---
245+
246+
### Migration from older versions
247+
248+
If your previous configuration did not contain `fortune_files`,
249+
rfortune will automatically migrate your config by adding it and setting:
250+
251+
```yaml
252+
fortune_files:
253+
- default_file
254+
```
255+
256+
No manual action is required.
257+
258+
---
259+
260+
## 📁 Fortune File Format
196261

197262
Each fortune must be on one or more lines separated by `%`, like so:
198263

199-
```txt
200-
%
201-
The best way to get a good idea is to get a lot of ideas.
202-
%
203-
Do or do not. There is no try.
204-
%
205-
To iterate is human, to recurse divine.
206-
%
264+
```txt
265+
%
266+
The best way to get a good idea is to get a lot of ideas.
267+
%
268+
Do or do not. There is no try.
269+
%
270+
To iterate is human, to recurse divine.
271+
%
207272
```
208273

209274
You may optionally add a title at the top of the file by starting the first line with #. The title will be printed

src/cli.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use clap::ArgAction;
12
use clap::{Parser, Subcommand};
23

34
#[derive(Parser, Debug)]
@@ -27,8 +28,8 @@ while preserving the spirit of the original UNIX command.",
2728
)]
2829
pub struct Cli {
2930
/// Fortune file to use instead of the default (rfortune.dat)
30-
#[arg(short, long)]
31-
pub file: Option<String>,
31+
#[arg(long = "file", value_name = "FILE", num_args = 1.., action = ArgAction::Append)]
32+
pub files: Option<Vec<String>>,
3233

3334
#[command(subcommand)]
3435
pub command: Option<Commands>,

src/config.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ pub struct Config {
1010
pub default_file: Option<String>,
1111
pub print_title: Option<bool>,
1212
pub use_cache: Option<bool>,
13+
#[serde(default)]
14+
pub fortune_files: Vec<String>,
1315
}
1416

1517
pub(crate) fn app_dir() -> PathBuf {
1618
let mut base = data_dir().unwrap_or_else(|| {
1719
// fallback molto conservativo
18-
let mut p = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
20+
let mut p = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
1921
p.push(".rfortune");
2022
p
2123
});
@@ -60,6 +62,7 @@ pub fn init_config_file() -> std::io::Result<()> {
6062
default_file: Some(get_default_path().to_string_lossy().to_string()),
6163
print_title: Some(true),
6264
use_cache: Some(true),
65+
fortune_files: vec![],
6366
};
6467
let yaml = serde_yaml::to_string(&cfg).expect("Failed to serialize config");
6568
fs::write(path, yaml)?;
@@ -92,8 +95,19 @@ In Rust we trust.
9295
/// Carica la configurazione se presente
9396
pub fn load_config() -> Option<Config> {
9497
let path = get_config_path();
95-
let content = fs::read_to_string(path).ok()?;
96-
serde_yaml::from_str(&content).ok()
98+
let content = fs::read_to_string(&path).ok()?;
99+
let mut cfg: Config = serde_yaml::from_str(&content).ok()?;
100+
101+
// ✅ MIGRATION AUTOMATICA
102+
if cfg.fortune_files.is_empty()
103+
&& let Some(df) = &cfg.default_file
104+
{
105+
cfg.fortune_files = vec![df.clone()];
106+
}
107+
108+
let _ = cfg.save(); // ignoriamo eventuali errori non critici
109+
110+
Some(cfg)
97111
}
98112

99113
/// Tenta di migrare una vecchia configurazione `config.yaml` a `rfortune.conf`
@@ -190,3 +204,21 @@ pub fn run_config_edit(editor_arg: Option<String>) -> std::io::Result<()> {
190204

191205
Ok(())
192206
}
207+
208+
impl Config {
209+
/// Salva la configurazione corrente su disco (YAML).
210+
pub fn save(&self) -> Result<(), String> {
211+
let path = get_config_path();
212+
let parent = path.parent().unwrap();
213+
214+
if !parent.exists() {
215+
fs::create_dir_all(parent)
216+
.map_err(|e| format!("Could not create config directory: {e}"))?;
217+
}
218+
219+
let yaml =
220+
serde_yaml::to_string(&self).map_err(|e| format!("Could not serialize config: {e}"))?;
221+
222+
fs::write(&path, yaml).map_err(|e| format!("Could not write config file: {e}"))
223+
}
224+
}

src/main.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use clap::Parser;
2+
use rfortune::config::Config;
23
use rfortune::log::ConsoleLog;
34
use rfortune::utils::ensure_app_initialized;
4-
use rfortune::{config, loader, utils};
5+
use rfortune::{config, utils};
6+
use std::path::Path;
57

68
mod cli;
79
mod commands;
@@ -18,6 +20,17 @@ fn main() {
1820
return;
1921
}
2022

23+
// ✅ CARICHIAMO LA CONFIG UNA VOLTA QUI
24+
let config = config::load_config().unwrap_or_else(|| {
25+
ConsoleLog::warn("No configuration file found. Using defaults.");
26+
Config {
27+
default_file: Some(config::get_default_path().to_string_lossy().to_string()),
28+
print_title: Some(true),
29+
use_cache: Some(true),
30+
fortune_files: vec![],
31+
}
32+
});
33+
2134
match cli.command {
2235
// ---------------- CONFIG ----------------
2336
Some(Commands::Config { action }) => match action {
@@ -45,19 +58,20 @@ fn main() {
4558

4659
// ---------------- DEFAULT: print random fortune ----------------
4760
None => {
48-
let file_path = if let Some(path) = cli.file {
49-
std::path::PathBuf::from(path)
50-
} else {
51-
config::get_default_path()
52-
};
61+
// 1. Risolve la PRIORITÀ delle sorgenti
62+
let sources = utils::resolve_fortune_sources(cli.files.clone(), &config);
63+
64+
if sources.is_empty() {
65+
ConsoleLog::ko("No fortune sources configured or provided.");
66+
return;
67+
}
68+
69+
// 2. Convertiamo in Path
70+
let paths: Vec<&Path> = sources.iter().map(Path::new).collect();
5371

54-
match loader::FortuneFile::from_file(&file_path) {
55-
Ok(fortune_file) => {
56-
if let Err(e) = utils::print_random(&fortune_file, &file_path) {
57-
ConsoleLog::ko(format!("Failed to print fortune: {e}"));
58-
}
59-
}
60-
Err(e) => ConsoleLog::ko(format!("Error loading fortune file: {e}")),
72+
// 3. Stampa citazione casuale da più file
73+
if let Err(e) = utils::print_random_from_files(&paths) {
74+
ConsoleLog::ko(format!("Failed to print fortune: {e}"));
6175
}
6276
}
6377
}

0 commit comments

Comments
 (0)