diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8d3cf49 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "steamlocate_doctest"] diff --git a/Cargo.toml b/Cargo.toml index 7b89134..6bdf54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ readme = "README.md" keywords = ["steam", "vdf", "appmanifest", "directory", "steamapps"] categories = ["os", "hardware-support", "filesystem", "accessibility"] +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--cfg", "steamlocate_doctest"] + # TODO: test more features in CI [features] @@ -22,11 +26,17 @@ keyvalues-parser = "0.2" keyvalues-serde = "0.2" serde = { version = "1.0", features = ["derive"] } +# TODO: is this really worth making optional? It should be a really small dep crc = { version = "3.0", optional = true } -[target.'cfg(target_os="windows")'.dependencies] +# Custom cfg used to enable a dependency only needed for doctests +[target."cfg(steamlocate_doctest)".dependencies] +tempfile = "3.8.1" + +# Platform-specific dependencies used for locating the steam dir +[target."cfg(target_os=\"windows\")".dependencies] locate_backend = { package = "winreg", version = "0.51", optional = true } -[target.'cfg(not(target_os="windows"))'.dependencies] +[target."cfg(not(target_os=\"windows\"))".dependencies] locate_backend = { package = "dirs", version = "5", optional = true } [dev-dependencies] diff --git a/examples/appmanifest.rs b/examples/appmanifest.rs index f3753f2..79dc53d 100644 --- a/examples/appmanifest.rs +++ b/examples/appmanifest.rs @@ -11,8 +11,8 @@ fn main() { let app_id: u32 = args[1].parse().expect(" should be a u32"); let steam_dir = SteamDir::locate().unwrap(); - match steam_dir.app(app_id) { - Ok(Some(app)) => println!("Found app - {:#?}", app), + match steam_dir.find_app(app_id) { + Ok(Some((app, _library))) => println!("Found app - {:#?}", app), Ok(None) => println!("No app found for {}", app_id), Err(err) => println!("Failed reading app: {err}"), } diff --git a/src/app.rs b/src/app.rs index d83c0bc..e44feaf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -40,47 +40,27 @@ impl<'library> Iterator for Iter<'library> { } } -/// An instance of an installed Steam app. -/// # Example -/// ```ignore -/// # use steamlocate::SteamDir; -/// let mut steamdir = SteamDir::locate().unwrap(); -/// let gmod = steamdir.app(&4000); -/// println!("{:#?}", gmod.unwrap()); -/// ``` -/// ```ignore -/// App ( -/// appid: u32: 4000, -/// path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod", -/// vdf: , -/// name: Some(String: "Garry's Mod"), -/// last_user: Some(u64: 76561198040894045) // This will be a steamid_ng::SteamID if the "steamid_ng" feature is enabled -/// ) -/// ``` +/// Metadata for an installed Steam app #[derive(Clone, Debug, Deserialize, PartialEq)] #[cfg_attr(test, derive(serde::Serialize))] #[non_exhaustive] #[serde(rename_all = "PascalCase")] pub struct App { - /// The app ID of this Steam app. + /// The app ID of this Steam app #[serde(rename = "appid")] pub app_id: u32, - - /// The name of the installation directory of this Steam app. - /// - /// Example: `"GarrysMod"` - /// - /// This can be resolved to the actual path off of the library + /// The name of the installation directory of this Steam app e.g. `"GarrysMod"` /// - /// ```rust,ignore - /// let app_dir = library.resolve_app_dir(&app); - /// ``` + /// If you're trying to get the app's installation directory then take a look at + /// [`Library::resolve_app_dir()`][crate::Library::resolve_app_dir] #[serde(rename = "installdir")] pub install_dir: String, - - /// The store name of the Steam app. + /// The store name of the Steam app #[serde(rename = "name")] pub name: Option, + /// The SteamID64 of the last Steam user that played this game on the filesystem + #[serde(rename = "LastOwner")] + pub last_user: Option, pub universe: Option, pub launcher_path: Option, @@ -117,14 +97,6 @@ pub struct App { pub install_scripts: BTreeMap, #[serde(default)] pub shared_depots: BTreeMap, - - /// The SteamID64 of the last Steam user that played this game on the filesystem. - /// - /// This crate supports [steamid-ng](https://docs.rs/steamid-ng) and can automatically convert this to a [SteamID](https://docs.rs/steamid-ng/*/steamid_ng/struct.SteamID.html) for you. - /// - /// To enable this support, [use the `steamid_ng` Cargo.toml feature](https://docs.rs/steamlocate/*/steamlocate#using-steamlocate). - #[serde(rename = "LastOwner")] - pub last_user: Option, } impl App { diff --git a/src/lib.rs b/src/lib.rs index 61b55e7..448f6d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,118 +1,90 @@ //! A crate which efficiently locates any Steam application on the filesystem, and/or the Steam installation itself. //! -//! **This crate supports Windows, macOS and Linux.** -//! //! # Using steamlocate -//! Simply add to your [Cargo.toml](https://doc.rust-lang.org/cargo/reference/manifest.html) file: -//! ```toml -//! [dependencies] -//! steamlocate = "0.*" -//! ``` //! -//! To use [steamid-ng](#steamid-ng-support) with steamlocate, add this to your [Cargo.toml](https://doc.rust-lang.org/cargo/reference/manifest.html) file: -//! ```toml -//! [dependencies] -//! steamid-ng = "1.*" +//! Simply add `steamlocate` using +//! [`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html). //! -//! [dependencies.steamlocate] -//! version = "0.*" -//! features = ["steamid_ng"] +//! ```console +//! $ cargo add steamlocate //! ``` //! -//! # Caching -//! All functions in this crate cache their results, meaning you can call them as many times as you like and they will always return the same reference. -//! -//! If you need to get uncached results, simply instantiate a new [SteamDir](https://docs.rs/steamlocate/*/steamlocate/struct.SteamDir.html). +//! ## Feature flags //! -//! # steamid-ng Support -//! This crate supports [steamid-ng](https://docs.rs/steamid-ng) and can automatically convert [App::last_user](struct.App.html#structfield.last_user) to a [SteamID](https://docs.rs/steamid-ng/*/steamid_ng/struct.SteamID.html) for you. +//! Default: `locate` //! -//! To enable this support, [use the `steamid_ng` Cargo.toml feature](#using-steamlocate). +//! | Feature flag | Description | +//! | :---: | :--- | +//! | `locate` | Enables automatically detecting the Steam installation on supported platforms (currently Windows, MacOS, and Linux). Unsupported platforms will return a runtime error. | //! //! # Examples //! -//! ### Locate the installed Steam directory -//! ```rust,ignore -//! extern crate steamlocate; -//! use steamlocate::SteamDir; -//! -//! match SteamDir::locate() { -//! Ok(steamdir) => println!("{:#?}", steamdir), -//! Err(_) => panic!("Couldn't locate Steam on this computer!") -//! } +//! ## Locate the Steam installation and a specific game +//! +//! The [`SteamDir`] is going to be your entrypoint into _most_ parts of the API. After you locate +//! it you can access related information. +//! +//! ```rust +//! # /* +//! let steam_dir = steamlocate::SteamDir::locate()?; +//! # */ +//! # let temp_steam_dir = steamlocate::tests::helpers::expect_test_env(); +//! # let steam_dir = temp_steam_dir.steam_dir(); +//! println!("Steam installation - {}", steam_dir.path().display()); +//! // ^^ prints something like `Steam installation - C:\Program Files (x86)\Steam` +//! +//! const GMOD_APP_ID: u32 = 4_000; +//! let (garrys_mod, _lib) = steam_dir.find_app(GMOD_APP_ID)?.expect("Of course we have G Mod"); +//! assert_eq!(garrys_mod.name.as_ref().unwrap(), "Garry's Mod"); +//! println!("{garrys_mod:#?}"); +//! // ^^ prints something like vv +//! # Ok::<_, steamlocate::tests::TestError>(()) //! ``` //! ```ignore -//! SteamDir ( -//! path: PathBuf: "C:\\Program Files (x86)\\Steam" -//! ) -//! ``` -//! -//! ### Locate an installed Steam app by its app ID -//! This will locate Garry's Mod anywhere on the filesystem. -//! ```ignore -//! extern crate steamlocate; -//! use steamlocate::SteamDir; -//! -//! let mut steamdir = SteamDir::locate().unwrap(); -//! match steamdir.app(&4000) { -//! Some(app) => println!("{:#?}", app), -//! None => panic!("Couldn't locate Garry's Mod on this computer!") +//! App { +//! app_id: 4_000, +//! install_dir: "GarrysMod", +//! name: Some("Garry's Mod"), +//! universe: Some(Public), +//! // much much more data //! } //! ``` -//! ```ignore -//! App ( -//! appid: u32: 4000, -//! path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod", -//! vdf: , -//! name: Some(String: "Garry's Mod"), -//! last_user: Some(u64: 76561198040894045) -//! ) -//! ``` //! -//! ### Locate all Steam apps on this filesystem -//! ```ignore -//! extern crate steamlocate; -//! use steamlocate::{SteamDir, App}; -//! use std::collections::HashMap; +//! ## Get an overview of all libraries and apps on the system //! -//! let mut steamdir = SteamDir::locate().unwrap(); -//! let apps: &HashMap> = steamdir.apps(); +//! You can iterate over all of Steam's libraries from the steam dir. Then from each library you +//! can iterate over all of its apps. //! -//! println!("{:#?}", apps); //! ``` -//! ```ignore -//! { -//! 4000: App ( -//! appid: u32: 4000, -//! path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod", -//! vdf: , -//! name: Some(String: "Garry's Mod"), -//! last_user: Some(u64: 76561198040894045) -//! ) -//! ... +//! # /* +//! let steam_dir = steamlocate::SteamDir::locate()?; +//! # */ +//! # let temp_steam_dir = steamlocate::tests::helpers::expect_test_env(); +//! # let steam_dir = temp_steam_dir.steam_dir(); +//! +//! for library in steam_dir.libraries()? { +//! let library = library?; +//! println!("Library - {}", library.path().display()); +//! +//! for app in library.apps() { +//! let app = app?; +//! println!(" App {} - {:?}", app.app_id, app.name); +//! } //! } +//! # Ok::<_, steamlocate::tests::TestError>(()) //! ``` //! -//! ### Locate all Steam library folders -//! ```ignore -//! extern crate steamlocate; -//! use steamlocate::{SteamDir, LibraryFolders}; -//! use std::{vec, path::PathBuf}; -//! -//! let mut steamdir: SteamDir = SteamDir::locate().unwrap(); -//! let libraryfolders: &LibraryFolders = steamdir.libraryfolders(); -//! let paths: &Vec = &libraryfolders.paths; -//! -//! println!("{:#?}", paths); -//! ``` -//! ```ignore -//! { -//! "C:\\Program Files (x86)\\Steam\\steamapps", -//! "D:\\Steam\\steamapps", -//! "E:\\Steam\\steamapps", -//! "F:\\Steam\\steamapps", -//! ... -//! } +//! On my laptop this prints +//! +//! ```text +//! Library - /home/wintermute/.local/share/Steam +//! App 1628350 - Steam Linux Runtime 3.0 (sniper) +//! App 1493710 - Proton Experimental +//! App 4000 - Garry's Mod +//! Library - /home/wintermute/temp steam lib +//! App 391540 - Undertale +//! App 1714040 - Super Auto Pets +//! App 2348590 - Proton 8.0 //! ``` #![warn( @@ -129,8 +101,10 @@ pub mod library; #[cfg(feature = "locate")] mod locate; pub mod shortcut; -#[cfg(any(test, doctest))] -pub mod tests; +// NOTE: exposed publicly, so that we can use them in doctests +/// Not part of the public API >:V +#[doc(hidden)] +pub mod tests; // TODO: rename this since it may leak out in compiler error messages use std::collections::HashMap; use std::fs; @@ -146,22 +120,31 @@ pub use crate::error::{Error, Result}; pub use crate::library::Library; pub use crate::shortcut::Shortcut; -/// An instance of a Steam installation. +/// The entrypoint into most of the rest of the API /// -/// All functions of this struct will cache their results. +/// Use either [`SteamDir::locate()`] or [`SteamDir::from_steam_dir()`] to create a new instance. +/// From there you have access to: /// -/// If you'd like to dispose of the cache or get uncached results, just instantiate a new `SteamDir`. +/// - The Steam installation directory +/// - [`steam_dir.path()`][SteamDir::path] +/// - Library info +/// - [`steam_dir.library_paths()`][SteamDir::library_paths] +/// - [`steam_dir.libraries()`][SteamDir::libraries] +/// - Convenient access to find a specific app by id +/// - [`steam_dir.find_app(app_id)`][SteamDir::find_app] +/// - Compatibility tool mapping (aka Proton to game mapping) +/// - [`steam_dir.compat_tool_mapping()`][SteamDir::compat_tool_mapping] +/// - Shortcuts info (aka the listing of non-Steam games) +/// - [`steam_dir.shortcuts()`][SteamDir::shortcuts] /// /// # Example -/// ```rust,ignore -/// # use steamlocate::SteamDir; -/// let steamdir = SteamDir::locate(); -/// println!("{:#?}", steamdir.unwrap()); /// ``` -/// ```ignore -/// SteamDir ( -/// path: "C:\\Program Files (x86)\\Steam" -/// ) +/// # /* +/// let steam_dir = SteamDir::locate()?; +/// # */ +/// # let temp_steam_dir = steamlocate::tests::helpers::expect_test_env(); +/// # let steam_dir = temp_steam_dir.steam_dir(); +/// assert!(steam_dir.path().ends_with("Steam")); /// ``` #[derive(Clone, Debug)] pub struct SteamDir { @@ -186,35 +169,31 @@ impl SteamDir { Ok(library::Iter::new(paths)) } - /// Returns a `Some` reference to a `App` via its app ID. - /// - /// If the Steam app is not installed on the system, this will return `None`. - /// - /// This function will cache its (either `Some` and `None`) result and will always return a reference to the same `App`. + /// Convenient helper to look through all the libraries for a specific app /// /// # Example - /// ```ignore - /// # use steamlocate::SteamDir; - /// let mut steamdir = SteamDir::locate().unwrap(); - /// let gmod = steamdir.app(&4000); - /// println!("{:#?}", gmod.unwrap()); /// ``` - /// ```ignore - /// App ( - /// appid: u32: 4000, - /// path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod", - /// vdf: , - /// name: Some(String: "Garry's Mod"), - /// last_user: Some(u64: 76561198040894045) // This will be a steamid_ng::SteamID if the "steamid_ng" feature is enabled - /// ) + /// # let temp_steam_dir = steamlocate::tests::helpers::expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// # /* + /// let steam_dir = SteamDir::locate()?; + /// # */ + /// const WARFRAME: u32 = 230_410; + /// let (warframe, library) = steam_dir.find_app(WARFRAME)?.unwrap(); + /// assert_eq!(warframe.app_id, WARFRAME); + /// assert!(library.app_ids().contains(&warframe.app_id)); + /// # Ok::<_, steamlocate::tests::TestError>(()) /// ``` - pub fn app(&self, app_id: u32) -> Result> { + pub fn find_app(&self, app_id: u32) -> Result> { // Search for the `app_id` in each library match self.libraries() { Err(e) => Err(e), Ok(libraries) => libraries .filter_map(|library| library.ok()) - .find_map(|lib| lib.app(app_id)) + .find_map(|lib| { + lib.app(app_id) + .map(|maybe_app| maybe_app.map(|app| (app, lib))) + }) .transpose(), } } @@ -234,11 +213,12 @@ impl SteamDir { Ok(store.software.valve.steam.mapping) } - /// Returns a listing of all added non-Steam games + /// Returns an iterator of all non-Steam games that were added to steam pub fn shortcuts(&mut self) -> Result { shortcut::Iter::new(&self.path) } + // TODO: rename to `from_dir()` and make consitent with similar constructors on other structs pub fn from_steam_dir(path: &Path) -> Result { if !path.is_dir() { return Err(Error::validation(ValidationError::missing_dir())); @@ -251,9 +231,7 @@ impl SteamDir { }) } - /// Locates the Steam installation directory on the filesystem and initializes a `SteamDir` (Windows) - /// - /// Returns `None` if no Steam installation can be located. + /// Attempts to locate the Steam installation directory on the system #[cfg(feature = "locate")] pub fn locate() -> Result { let path = locate::locate_steam_dir()?; diff --git a/src/library.rs b/src/library.rs index 9c7a3aa..8db1f1a 100644 --- a/src/library.rs +++ b/src/library.rs @@ -154,6 +154,24 @@ impl Library { app::Iter::new(self) } + /// Resolves the theoretical installation directory for the given `app` + /// + /// This is an unvalidated path, so it's up to you to call this with an `app` that's in this + /// library + /// + /// # Example + /// + /// ``` + /// # use std::path::Path; + /// # let temp_steam_dir = steamlocate::tests::helpers::expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// const GRAVEYARD_KEEPER: u32 = 599_140; + /// let (graveyard_keeper, library) = steam_dir.find_app(GRAVEYARD_KEEPER)?.unwrap(); + /// let app_dir = library.resolve_app_dir(&graveyard_keeper); + /// let expected_rel_path = Path::new("steamapps").join("common").join("Graveyard Keeper"); + /// assert!(app_dir.ends_with(expected_rel_path)); + /// # Ok::<_, steamlocate::tests::TestError>(()) + /// ``` pub fn resolve_app_dir(&self, app: &App) -> PathBuf { self.path .join("steamapps") diff --git a/src/shortcut.rs b/src/shortcut.rs index 69c8340..202178e 100644 --- a/src/shortcut.rs +++ b/src/shortcut.rs @@ -1,4 +1,4 @@ -//! **WARN:** This is all hacky and should be replaced with proper binary VDF parsing +// HACK: This is all hacky and should be replaced with proper binary VDF parsing use std::{ fs, io, diff --git a/src/snapshots/steamlocate__app__tests__more_sanity.snap b/src/snapshots/steamlocate__app__tests__more_sanity.snap index 187a72f..bfc0a2b 100644 --- a/src/snapshots/steamlocate__app__tests__more_sanity.snap +++ b/src/snapshots/steamlocate__app__tests__more_sanity.snap @@ -6,6 +6,7 @@ App( appid: 599140, installdir: "Graveyard Keeper", name: Some("Graveyard Keeper"), + LastOwner: Some(12312312312312312), Universe: Some(Public), LauncherPath: None, StateFlags: Some(StateFlags(6)), @@ -46,5 +47,4 @@ App( }, InstallScripts: {}, SharedDepots: {}, - LastOwner: Some(12312312312312312), ) diff --git a/src/snapshots/steamlocate__app__tests__sanity.snap b/src/snapshots/steamlocate__app__tests__sanity.snap index 9beddf9..5b925fd 100644 --- a/src/snapshots/steamlocate__app__tests__sanity.snap +++ b/src/snapshots/steamlocate__app__tests__sanity.snap @@ -6,6 +6,7 @@ App( appid: 230410, installdir: "Warframe", name: Some("Warframe"), + LastOwner: Some(12312312312312312), Universe: Some(Public), LauncherPath: Some("C:\\Program Files (x86)\\Steam\\steam.exe"), StateFlags: Some(StateFlags(4)), @@ -43,5 +44,4 @@ App( 230411: "installscript.vdf", }, SharedDepots: {}, - LastOwner: Some(12312312312312312), ) diff --git a/src/tests/helpers.rs b/src/tests/helpers.rs index b57a7d6..a2eb940 100644 --- a/src/tests/helpers.rs +++ b/src/tests/helpers.rs @@ -9,21 +9,22 @@ use std::{ path::{Path, PathBuf}, }; -use crate::SteamDir; +use crate::{ + tests::{temp::TempDir, TestError}, + SteamDir, +}; use serde::Serialize; -use tempfile::TempDir; -fn test_temp_dir() -> Result { - let temp_dir = tempfile::Builder::new() - .prefix("steamlocate-test-") - .tempdir()?; - Ok(temp_dir) +pub fn expect_test_env() -> TempSteamDir { + TempSteamDir::builder() + .app(SampleApp::GarrysMod.into()) + .app(SampleApp::Warframe.into()) + .library(SampleApp::GraveyardKeeper.try_into().unwrap()) + .finish() + .unwrap() } -pub type TestError = Box; -pub type TestResult = Result<(), TestError>; - // TODO(cosmic): Add in functionality for providing shortcuts too pub struct TempSteamDir { steam_dir: crate::SteamDir, @@ -76,7 +77,7 @@ impl TempSteamDirBuilder { // Steam dir is also a library, but is laid out slightly differently than a regular library pub fn finish(self) -> Result { - let tmp = test_temp_dir()?; + let tmp = TempDir::new()?; let root_dir = tmp.path().join("test-steam-dir"); let steam_dir = root_dir.join("Steam"); let apps_dir = steam_dir.join("steamapps"); @@ -209,7 +210,7 @@ impl TempLibraryBuilder { } fn finish(self) -> Result { - let tmp = test_temp_dir()?; + let tmp = TempDir::new()?; let root_dir = tmp.path().join("test-library"); let apps_dir = root_dir.join("steamapps"); fs::create_dir_all(&apps_dir)?; @@ -257,6 +258,7 @@ impl AppFile { pub enum SampleApp { GarrysMod, GraveyardKeeper, + Warframe, } impl SampleApp { @@ -284,15 +286,29 @@ impl SampleApp { "Graveyard Keeper", include_str!("../../tests/assets/appmanifest_599140.acf"), ), + Self::Warframe => ( + 230_410, + "Warframe", + include_str!("../../tests/assets/appmanifest_230410.acf"), + ), } } } -#[test] -fn sanity() -> TestResult { - let tmp_steam_dir = TempSteamDir::try_from(SampleApp::GarrysMod)?; - let steam_dir = tmp_steam_dir.steam_dir(); - assert!(steam_dir.app(SampleApp::GarrysMod.id()).unwrap().is_some()); - - Ok(()) +#[cfg(test)] +mod test { + use super::*; + use crate::tests::TestResult; + + #[test] + fn sanity() -> TestResult { + let tmp_steam_dir = TempSteamDir::try_from(SampleApp::GarrysMod)?; + let steam_dir = tmp_steam_dir.steam_dir(); + assert!(steam_dir + .find_app(SampleApp::GarrysMod.id()) + .unwrap() + .is_some()); + + Ok(()) + } } diff --git a/src/tests/legacy.rs b/src/tests/legacy.rs index 1db670e..ab344c2 100644 --- a/src/tests/legacy.rs +++ b/src/tests/legacy.rs @@ -1,24 +1,18 @@ -use std::convert::TryInto; +// TODO: steamlocate_tempfile cfg for docs. Otherwise rely on a env var to get passed in -use super::helpers::{SampleApp, TempSteamDir, TestError, TestResult}; -use crate::Error; +use crate::{ + tests::{ + helpers::{expect_test_env, SampleApp}, + TestResult, + }, + Error, +}; static GMOD_ID: u32 = SampleApp::GarrysMod.id(); -// The legacy test env assumed the following prerequisites: -// - Steam must be installed -// - At least two library folders must be setup (the steam dir acts as one) -// - Garry's Mod along with at least one other steam app must be installed -pub fn legacy_test_env() -> std::result::Result { - TempSteamDir::builder() - .app(SampleApp::GarrysMod.into()) - .library(SampleApp::GraveyardKeeper.try_into()?) - .finish() -} - #[test] fn find_library_folders() -> TestResult { - let tmp_steam_dir = legacy_test_env()?; + let tmp_steam_dir = expect_test_env(); let steam_dir = tmp_steam_dir.steam_dir(); assert!(steam_dir.libraries().unwrap().len() > 1); Ok(()) @@ -26,25 +20,25 @@ fn find_library_folders() -> TestResult { #[test] fn find_app() -> TestResult { - let tmp_steam_dir = legacy_test_env()?; + let tmp_steam_dir = expect_test_env(); let steam_dir = tmp_steam_dir.steam_dir(); - let steam_app = steam_dir.app(GMOD_ID).unwrap(); - assert_eq!(steam_app.unwrap().app_id, GMOD_ID); + let steam_app = steam_dir.find_app(GMOD_ID).unwrap(); + assert_eq!(steam_app.unwrap().0.app_id, GMOD_ID); Ok(()) } #[test] fn app_details() -> TestResult { - let tmp_steam_dir = legacy_test_env()?; + let tmp_steam_dir = expect_test_env(); let steam_dir = tmp_steam_dir.steam_dir(); - let steam_app = steam_dir.app(GMOD_ID)?.unwrap(); - assert_eq!(steam_app.name.unwrap(), "Garry's Mod"); + let steam_app = steam_dir.find_app(GMOD_ID)?.unwrap(); + assert_eq!(steam_app.0.name.unwrap(), "Garry's Mod"); Ok(()) } #[test] fn all_apps() -> TestResult { - let tmp_steam_dir = legacy_test_env()?; + let tmp_steam_dir = expect_test_env(); let steam_dir = tmp_steam_dir.steam_dir(); let mut libraries = steam_dir.libraries().unwrap(); let all_apps: Vec<_> = libraries @@ -63,7 +57,7 @@ fn all_apps() -> TestResult { #[test] fn all_apps_get_one() -> TestResult { - let tmp_steam_dir = legacy_test_env()?; + let tmp_steam_dir = expect_test_env(); let steam_dir = tmp_steam_dir.steam_dir(); let mut libraries = steam_dir.libraries().unwrap(); @@ -80,13 +74,13 @@ fn all_apps_get_one() -> TestResult { assert!(!all_apps.is_empty()); assert!(all_apps.len() > 1); - let steam_app = steam_dir.app(GMOD_ID).unwrap().unwrap(); + let steam_app = steam_dir.find_app(GMOD_ID).unwrap().unwrap(); assert_eq!( all_apps .into_iter() .find(|app| app.app_id == GMOD_ID) .unwrap(), - steam_app + steam_app.0, ); Ok(()) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 3fe53e6..d24aa8b 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,7 @@ pub mod helpers; #[cfg(test)] mod legacy; +mod temp; + +pub type TestError = Box; +pub type TestResult = Result<(), TestError>; diff --git a/src/tests/temp.rs b/src/tests/temp.rs new file mode 100644 index 0000000..3973a85 --- /dev/null +++ b/src/tests/temp.rs @@ -0,0 +1,38 @@ +#[cfg(not(any(steamlocate_doctest, test)))] +pub use fake::TempDir; +#[cfg(any(steamlocate_doctest, test))] +pub use real::TempDir; + +#[cfg(not(any(steamlocate_doctest, test)))] +mod fake { + pub struct TempDir; + + impl TempDir { + // TODO: I think that this can be added to a `.cargo` config file for this project? + pub fn new() -> Result { + unimplemented!("Pass RUSTFLAGS='--cfg steamlocate_doctest' to run doctests"); + } + + pub fn path(&self) -> &std::path::Path { + panic!(); + } + } +} + +#[cfg(any(steamlocate_doctest, test))] +mod real { + pub struct TempDir(tempfile::TempDir); + + impl TempDir { + pub fn new() -> Result { + let temp_dir = tempfile::Builder::new() + .prefix("steamlocate-test-") + .tempdir()?; + Ok(Self(temp_dir)) + } + + pub fn path(&self) -> &std::path::Path { + self.0.path() + } + } +}