hypersave/lib.rs
1//! # Adding a safe feature to hyperrogue
2//!
3//! Hyperrogue is an awesome educational game for teaching hyperbolic
4//! geometry, but sadly due to it being roguelike it (intentionally!)
5//! has only a crippled save feature. This crate fixes this. After
6//! installing it you can call `save` after having clicked "Save" in
7//! hyperrogue and `restore` after quitting after an game over.
8//!
9//! # Installing
10//! As all rust programs on crates.io this program to is installed
11//! with
12//! ```shell
13//! cargo install hypersave
14//! ```
15//! This creates the two binaries `save` and `restore`, which are
16//! pretty general name. If you want to change these, you can use
17//! your file manager or use commands like this:
18//! ```shell
19//! mv ~/.cargo/bin/save ~/.cargo/bin/insert-your-name-for-the-save-binary-here
20//! mv ~/.cargo/bin/restore ~/.cargo/bin/insert-your-name-for-the-restore-binary-here
21//! ```
22//!
23//! # Profiles
24//! You can start a new play through with the concept of profiles.
25//! With `save name-of-your-old-play-through` you save your current
26//! play through to the given name which you can restore later again
27//! with `restore name-of-your-old-play-through`.
28
29#![warn(
30 clippy::all,
31 clippy::pedantic,
32 clippy::nursery,
33 clippy::cargo_common_metadata
34)]
35// Anachronism
36#![allow(clippy::non_ascii_literal)]
37// More or less manual checked and documentation agrees with me that
38// it's usually not needed.
39#![allow(
40 clippy::cast_possible_truncation,
41 clippy::cast_sign_loss,
42 clippy::cast_precision_loss,
43 clippy::cast_lossless
44)]
45// Explicitly decided against; I think `let _ = …` is better than
46// `mem::drop(…)`. TODO: align my opinion and community's one with
47// each other.
48#![allow(let_underscore_drop)]
49
50use std::{
51 fs::read_dir,
52 io::stdin,
53 os::unix::prelude::OsStrExt,
54 path::{Path, PathBuf},
55};
56
57use anyhow::{anyhow, Context, Error};
58
59#[doc(hidden)]
60#[must_use]
61pub fn get_dirs() -> (PathBuf, PathBuf)
62{
63 let home_dir = home::home_dir().unwrap_or_else(|| PathBuf::from("./"));
64
65 (
66 home_dir.clone(),
67 home_dir.join(PathBuf::from("./.zvavybir/hypersave")),
68 )
69}
70
71#[doc(hidden)]
72pub fn check_profile_name(profile: &Path) -> Result<(), Error>
73{
74 if profile.as_os_str().as_bytes().iter().any(|&x| x == b'/')
75 || (!profile.as_os_str().is_empty() && profile.as_os_str().as_bytes()[0] == b'.')
76 {
77 Err(anyhow!(
78 "Profile name is malformatted, please try again with an other: {profile:?}"
79 ))
80 }
81 else
82 {
83 Ok(())
84 }
85}
86
87#[doc(hidden)]
88pub fn ask_question(question: &str, cli: bool) -> Result<bool, Error>
89{
90 if cli
91 {
92 return Ok(true);
93 }
94
95 println!("{question}");
96
97 let mut buf = String::new();
98 stdin()
99 .read_line(&mut buf)
100 .context("Couldn't read anything")?;
101
102 buf.parse().context("I didn't understand you")
103}
104
105#[doc(hidden)]
106pub fn get_last_file_id(save_dir: &Path) -> Result<isize, Error>
107{
108 Ok(read_dir(save_dir.join("current"))
109 .context("Couldn't read current profile")?
110 .map(|x| {
111 x.map(|x| {
112 x.file_name()
113 .to_str()
114 .map_or(-1, |x| x.parse().unwrap_or(-1))
115 })
116 .unwrap_or(-1)
117 })
118 .fold(-1, Ord::max))
119}