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}