aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/install.rs142
-rw-r--r--src/main.rs192
-rw-r--r--src/uiutil.rs55
3 files changed, 205 insertions, 184 deletions
diff --git a/src/install.rs b/src/install.rs
new file mode 100644
index 0000000..56754d3
--- /dev/null
+++ b/src/install.rs
@@ -0,0 +1,142 @@
+//! Implementation of the `modderbaas install` subcommand.
+
+use std::{io::Write, path::Path, str::FromStr};
+
+use anyhow::{bail, Context, Result};
+use clap::ArgMatches;
+use itertools::Itertools;
+use termcolor::StandardStream;
+
+use modderbaas::{
+ baas::{Baas, Installer},
+ minemod::{self, ModContainer},
+ util, ContentDb, Downloader, MineMod, Source, World,
+};
+
+use super::uiutil::{ask_continue, user_choice};
+
+/// Install the given mods, installing dependencies if needed.
+pub fn install_mods(
+ output: &mut StandardStream,
+ baas: &Baas,
+ world: &World,
+ mods: &[&str],
+ matches: &ArgMatches,
+) -> Result<()> {
+ let target_dir = Path::new(matches.value_of("target").unwrap());
+ let dry_run = matches.is_present("dry-run");
+ let chown = matches.is_present("chown");
+
+ let content_db = ContentDb::new();
+ let downloader = Downloader::new()?;
+ let sources = mods
+ .iter()
+ .map(|&s| Source::from_str(s))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let installer = InteractiveInstaller {
+ output,
+ content_db,
+ target_dir,
+ dry_run,
+ chown,
+ };
+ baas.install(installer, &downloader, world, sources)?;
+
+ writeln!(output, "Done!")?;
+
+ Ok(())
+}
+
+/// The installer that interactively asks the user about choices.
+struct InteractiveInstaller<'o, 'p> {
+ output: &'o mut StandardStream,
+ content_db: ContentDb,
+ target_dir: &'p Path,
+ dry_run: bool,
+ chown: bool,
+}
+
+impl<'o, 'p> Installer for InteractiveInstaller<'o, 'p> {
+ type Error = anyhow::Error;
+
+ fn resolve(&mut self, mod_id: &str) -> Result<Source> {
+ writeln!(&mut self.output, "Searching for candidates: {}", mod_id)?;
+
+ let candidates = self.content_db.resolve(mod_id)?;
+ if candidates.is_empty() {
+ bail!("Could not find a suitable mod for '{}'", mod_id);
+ } else if candidates.len() == 1 {
+ Ok(Source::Http(candidates.into_iter().next().unwrap().url))
+ } else {
+ let items = candidates
+ .into_iter()
+ .map(|c| {
+ (
+ format!("{} by {} - {}", c.title, c.author, c.short_description),
+ c,
+ )
+ })
+ .collect::<Vec<_>>();
+ writeln!(
+ &mut self.output,
+ "{} candidates found, please select one:",
+ items.len()
+ )?;
+ let candidate = user_choice(&items, self.output)?;
+ Ok(Source::Http(candidate.url.clone()))
+ }
+ }
+
+ fn install_mod(&mut self, mod_or_pack: &dyn ModContainer) -> Result<Box<dyn ModContainer>> {
+ let mod_id = mod_or_pack.name()?;
+ writeln!(&mut self.output, "Installing {}", mod_id)?;
+
+ if self.dry_run {
+ // Re-open so we get a fresh Box<>
+ return Ok(minemod::open_mod_or_pack(mod_or_pack.path())?);
+ }
+
+ let installed = mod_or_pack
+ .install_to(self.target_dir)
+ .context(format!("Error installing '{}'", mod_id))?;
+
+ #[cfg(unix)]
+ {
+ use nix::{
+ sys::stat,
+ unistd::{Gid, Uid},
+ };
+ if self.chown {
+ let perms = stat::stat(self.target_dir)?;
+ let (uid, gid) = (Uid::from_raw(perms.st_uid), Gid::from_raw(perms.st_gid));
+ util::chown_recursive(installed.path(), Some(uid), Some(gid))?;
+ }
+ }
+
+ Ok(installed)
+ }
+
+ fn display_changes(&mut self, to_install: &[Box<dyn ModContainer>]) -> Result<()> {
+ writeln!(
+ &mut self.output,
+ "Installing {} new mods:",
+ to_install.len()
+ )?;
+ writeln!(&mut self.output, "{}", to_install.iter().join(", "))?;
+
+ ask_continue(self.output)
+ }
+
+ fn enable_mod(&mut self, world: &World, minemod: &MineMod) -> Result<()> {
+ let mod_id = minemod.mod_id()?;
+ writeln!(&mut self.output, "Enabling {}", mod_id)?;
+
+ if !self.dry_run {
+ world
+ .enable_mod(&mod_id)
+ .context(format!("Error enabling '{}'", mod_id))?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 817d39b..5390479 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,4 @@
-use std::{
- fmt::Display,
- io::{self, Write},
- path::Path,
- str::FromStr,
-};
+use std::io::Write;
use anyhow::{anyhow, bail, Context, Result};
use clap::{crate_version, App, AppSettings, Arg, ArgMatches, SubCommand};
@@ -11,11 +6,13 @@ use itertools::Itertools;
use log::debug;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
-use modderbaas::{
- baas::{Baas, Installer},
- minemod::{self, ModContainer},
- util, ContentDb, Downloader, MineMod, Snapshot, Source, World,
-};
+use modderbaas::{baas::Baas, MineMod, Snapshot, World};
+
+mod uiutil;
+use uiutil::{ask_continue, user_choice};
+
+mod install;
+use install::install_mods;
fn main() -> Result<()> {
stderrlog::new()
@@ -206,176 +203,3 @@ fn enable_mods(
writeln!(output, "Done!")?;
Ok(())
}
-
-/// Install the given mods, installing dependencies if needed.
-fn install_mods(
- output: &mut StandardStream,
- baas: &Baas,
- world: &World,
- mods: &[&str],
- matches: &ArgMatches,
-) -> Result<()> {
- let target_dir = Path::new(matches.value_of("target").unwrap());
- let dry_run = matches.is_present("dry-run");
- let chown = matches.is_present("chown");
-
- let content_db = ContentDb::new();
- let downloader = Downloader::new()?;
- let sources = mods
- .iter()
- .map(|&s| Source::from_str(s))
- .collect::<Result<Vec<_>, _>>()?;
-
- let installer = InteractiveInstaller {
- output,
- content_db,
- target_dir,
- dry_run,
- chown,
- };
- baas.install(installer, &downloader, world, sources)?;
-
- writeln!(output, "Done!")?;
-
- Ok(())
-}
-
-/// The installer that interactively asks the user about choices.
-struct InteractiveInstaller<'o, 'p> {
- output: &'o mut StandardStream,
- content_db: ContentDb,
- target_dir: &'p Path,
- dry_run: bool,
- chown: bool,
-}
-
-impl<'o, 'p> Installer for InteractiveInstaller<'o, 'p> {
- type Error = anyhow::Error;
-
- fn resolve(&mut self, mod_id: &str) -> Result<Source> {
- writeln!(&mut self.output, "Searching for candidates: {}", mod_id)?;
-
- let candidates = self.content_db.resolve(mod_id)?;
- if candidates.is_empty() {
- bail!("Could not find a suitable mod for '{}'", mod_id);
- } else if candidates.len() == 1 {
- Ok(Source::Http(candidates.into_iter().next().unwrap().url))
- } else {
- let items = candidates
- .into_iter()
- .map(|c| {
- (
- format!("{} by {} - {}", c.title, c.author, c.short_description),
- c,
- )
- })
- .collect::<Vec<_>>();
- writeln!(
- &mut self.output,
- "{} candidates found, please select one:",
- items.len()
- )?;
- let candidate = user_choice(&items, self.output)?;
- Ok(Source::Http(candidate.url.clone()))
- }
- }
-
- fn install_mod(&mut self, mod_or_pack: &dyn ModContainer) -> Result<Box<dyn ModContainer>> {
- let mod_id = mod_or_pack.name()?;
- writeln!(&mut self.output, "Installing {}", mod_id)?;
-
- if self.dry_run {
- // Re-open so we get a fresh Box<>
- return Ok(minemod::open_mod_or_pack(mod_or_pack.path())?);
- }
-
- let installed = mod_or_pack
- .install_to(self.target_dir)
- .context(format!("Error installing '{}'", mod_id))?;
-
- #[cfg(unix)]
- {
- use nix::{
- sys::stat,
- unistd::{Gid, Uid},
- };
- if self.chown {
- let perms = stat::stat(self.target_dir)?;
- let (uid, gid) = (Uid::from_raw(perms.st_uid), Gid::from_raw(perms.st_gid));
- util::chown_recursive(installed.path(), Some(uid), Some(gid))?;
- }
- }
-
- Ok(installed)
- }
-
- fn display_changes(&mut self, to_install: &[Box<dyn ModContainer>]) -> Result<()> {
- writeln!(
- &mut self.output,
- "Installing {} new mods:",
- to_install.len()
- )?;
- writeln!(&mut self.output, "{}", to_install.iter().join(", "))?;
-
- ask_continue(self.output)
- }
-
- fn enable_mod(&mut self, world: &World, minemod: &MineMod) -> Result<()> {
- let mod_id = minemod.mod_id()?;
- writeln!(&mut self.output, "Enabling {}", mod_id)?;
-
- if !self.dry_run {
- world
- .enable_mod(&mod_id)
- .context(format!("Error enabling '{}'", mod_id))?;
- }
- Ok(())
- }
-}
-
-/// Presents the user with a choice of items and awaits a selection.
-fn user_choice<'i, L: Display, I>(
- items: &'i [(L, I)],
- output: &mut StandardStream,
-) -> Result<&'i I> {
- for (i, (label, _)) in items.iter().enumerate() {
- output.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
- write!(output, "[{}]", i)?;
- output.reset()?;
- writeln!(output, " {}", label)?;
- }
-
- let stdin = io::stdin();
- loop {
- write!(output, "Enter a number: ")?;
- output.flush()?;
- let mut buffer = String::new();
- stdin.read_line(&mut buffer)?;
- if let Ok(number) = buffer.trim().parse::<usize>() {
- if number < items.len() {
- return Ok(&items[number].1);
- }
- }
- }
-}
-
-/// Ask the user whether they want to continue.
-///
-/// Returns `Ok(())` if the program should continue, and an error otherwise.
-fn ask_continue(output: &mut StandardStream) -> Result<()> {
- let stdin = io::stdin();
- loop {
- output.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
- write!(output, "Continue? [Y/n] ")?;
- output.reset()?;
- output.flush()?;
-
- let mut buffer = String::new();
- stdin.read_line(&mut buffer)?;
- if buffer == "\n" || buffer == "Y\n" || buffer == "y\n" {
- return Ok(());
- } else if buffer == "N\n" || buffer == "n\n" {
- bail!("Cancelled by user");
- }
- }
-}
diff --git a/src/uiutil.rs b/src/uiutil.rs
new file mode 100644
index 0000000..188e20c
--- /dev/null
+++ b/src/uiutil.rs
@@ -0,0 +1,55 @@
+//! Utility functions for user interaction.
+use std::{
+ fmt::Display,
+ io::{self, Write},
+};
+
+use anyhow::{bail, Result};
+use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
+
+/// Presents the user with a choice of items and awaits a selection.
+pub fn user_choice<'i, L: Display, I>(
+ items: &'i [(L, I)],
+ output: &mut StandardStream,
+) -> Result<&'i I> {
+ for (i, (label, _)) in items.iter().enumerate() {
+ output.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
+ write!(output, "[{}]", i)?;
+ output.reset()?;
+ writeln!(output, " {}", label)?;
+ }
+
+ let stdin = io::stdin();
+ loop {
+ write!(output, "Enter a number: ")?;
+ output.flush()?;
+ let mut buffer = String::new();
+ stdin.read_line(&mut buffer)?;
+ if let Ok(number) = buffer.trim().parse::<usize>() {
+ if number < items.len() {
+ return Ok(&items[number].1);
+ }
+ }
+ }
+}
+
+/// Ask the user whether they want to continue.
+///
+/// Returns `Ok(())` if the program should continue, and an error otherwise.
+pub fn ask_continue(output: &mut StandardStream) -> Result<()> {
+ let stdin = io::stdin();
+ loop {
+ output.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
+ write!(output, "Continue? [Y/n] ")?;
+ output.reset()?;
+ output.flush()?;
+
+ let mut buffer = String::new();
+ stdin.read_line(&mut buffer)?;
+ if buffer == "\n" || buffer == "Y\n" || buffer == "y\n" {
+ return Ok(());
+ } else if buffer == "N\n" || buffer == "n\n" {
+ bail!("Cancelled by user");
+ }
+ }
+}