Skip to content

Commit 9ce63b7

Browse files
committed
Use fs abstraction with path information, tidy up main
1 parent 987375e commit 9ce63b7

File tree

8 files changed

+242
-16
lines changed

8 files changed

+242
-16
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
[package]
22
name = "foreman"
3+
description = "Toolchain manager for simple binary tools"
34
version = "0.1.0"
45
authors = ["Lucien Greathouse <[email protected]>"]
6+
license = "MIT"
7+
homepage = "https://github.com/rojo-rbx/foreman#readme"
8+
repository = "https://github.com/rojo-rbx/foreman"
59
edition = "2018"
610

711
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

src/aliaser.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use std::{
2-
env::{self, consts::EXE_SUFFIX},
3-
fs,
4-
};
1+
use std::env::{self, consts::EXE_SUFFIX};
52

6-
use crate::paths;
3+
use crate::{fs, paths};
74

85
pub fn add_self_alias(name: &str) {
96
let foreman_path = env::args().next().unwrap();

src/auth_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::{fs, io};
1+
use std::io;
22

33
use serde::{Deserialize, Serialize};
44

5-
use crate::paths;
5+
use crate::{fs, paths};
66

77
/// Contains stored user tokens that Foreman can use to download tools.
88
#[derive(Debug, Default, Serialize, Deserialize)]

src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::{collections::HashMap, env, fs, io};
1+
use std::{collections::HashMap, env, io};
22

33
use semver::VersionReq;
44
use serde::{Deserialize, Serialize};
55

6-
use crate::paths;
6+
use crate::{fs, paths};
77

88
#[derive(Debug, Serialize, Deserialize)]
99
pub struct ConfigFile {

src/fs.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//! Wrapper around std::fs and std::io that attaches file paths to errors.
2+
//!
3+
//! We won't use all these wrappers all the time, so it's okay if some of them
4+
//! are unused.
5+
6+
#![allow(unused)]
7+
8+
use std::{
9+
error::Error as StdError,
10+
fmt, fs,
11+
io::{self, Read, Write},
12+
path::{Path, PathBuf},
13+
};
14+
15+
pub type Result<T> = io::Result<T>;
16+
17+
/// A wrapper around std::fs::read.
18+
pub fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
19+
let path = path.as_ref();
20+
21+
fs::read(path).map_err(|source| Error::new(source, path))
22+
}
23+
24+
/// A wrapper around std::fs::write.
25+
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
26+
let path = path.as_ref();
27+
28+
fs::write(path, contents).map_err(|source| Error::new(source, path))
29+
}
30+
31+
/// A wrapper around std::fs::read.
32+
pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(source_path: P, dest_path: Q) -> Result<u64> {
33+
let source_path = source_path.as_ref();
34+
let dest_path = dest_path.as_ref();
35+
36+
fs::copy(source_path, dest_path)
37+
.map_err(|source| CopyError::new(source, source_path, dest_path))
38+
}
39+
40+
/// A wrapper around std::fs::create_dir_all.
41+
///
42+
/// Currently reports all errors as happening from the given path.
43+
pub fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
44+
let path = path.as_ref();
45+
46+
fs::create_dir_all(path).map_err(|source| Error::new(source, path))
47+
}
48+
49+
/// A wrapper around std::fs::metadata.
50+
pub fn metadata<P: AsRef<Path>>(path: P) -> Result<fs::Metadata> {
51+
let path = path.as_ref();
52+
53+
fs::metadata(path).map_err(|source| Error::new(source, path))
54+
}
55+
56+
/// A wrapper around std::fs::File that contains file path information in error
57+
/// cases.
58+
#[derive(Debug)]
59+
pub struct File {
60+
source: fs::File,
61+
path: PathBuf,
62+
}
63+
64+
impl File {
65+
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
66+
let path = path.as_ref();
67+
let source = fs::File::create(path).map_err(|source| Error::new(source, path))?;
68+
69+
Ok(Self {
70+
source,
71+
path: path.to_owned(),
72+
})
73+
}
74+
75+
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
76+
let path = path.as_ref();
77+
let source = fs::File::open(path).map_err(|source| Error::new(source, path))?;
78+
79+
Ok(Self {
80+
source,
81+
path: path.to_owned(),
82+
})
83+
}
84+
}
85+
86+
impl Read for File {
87+
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
88+
self.source
89+
.read(buf)
90+
.map_err(|source| Error::new(source, &self.path))
91+
}
92+
}
93+
94+
impl Write for File {
95+
fn write(&mut self, buf: &[u8]) -> Result<usize> {
96+
self.source
97+
.write(buf)
98+
.map_err(|source| Error::new(source, &self.path))
99+
}
100+
101+
fn flush(&mut self) -> Result<()> {
102+
self.source
103+
.flush()
104+
.map_err(|source| Error::new(source, &self.path))
105+
}
106+
}
107+
108+
/// Wrapper around std::fs::read_dir.
109+
pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir> {
110+
let path = path.as_ref();
111+
112+
fs::read_dir(path)
113+
.map(|source| ReadDir {
114+
source,
115+
path: path.to_owned(),
116+
})
117+
.map_err(|source| Error::new(source, path))
118+
}
119+
120+
/// Wrapper around std::fs::ReadDir.
121+
pub struct ReadDir {
122+
source: fs::ReadDir,
123+
path: PathBuf,
124+
}
125+
126+
impl Iterator for ReadDir {
127+
type Item = Result<fs::DirEntry>;
128+
129+
fn next(&mut self) -> Option<Self::Item> {
130+
Some(
131+
self.source
132+
.next()?
133+
.map_err(|source| Error::new(source, &self.path)),
134+
)
135+
}
136+
}
137+
138+
/// Contains an IO error that has a file path attached.
139+
///
140+
/// This type is never returned directly, but is instead wrapped inside yet
141+
/// another IO error.
142+
#[derive(Debug)]
143+
struct Error {
144+
source: io::Error,
145+
path: PathBuf,
146+
}
147+
148+
impl Error {
149+
fn new<P: Into<PathBuf>>(source: io::Error, path: P) -> io::Error {
150+
io::Error::new(
151+
source.kind(),
152+
Self {
153+
source,
154+
path: path.into(),
155+
},
156+
)
157+
}
158+
}
159+
160+
impl fmt::Display for Error {
161+
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
162+
write!(formatter, "{} in path {}", self.source, self.path.display())
163+
}
164+
}
165+
166+
impl StdError for Error {
167+
fn source(&self) -> Option<&(dyn StdError + 'static)> {
168+
Some(&self.source)
169+
}
170+
}
171+
172+
/// Contains an IO error from a copy operation, containing both paths.
173+
#[derive(Debug)]
174+
struct CopyError {
175+
source: io::Error,
176+
source_path: PathBuf,
177+
dest_path: PathBuf,
178+
}
179+
180+
impl CopyError {
181+
fn new<P: Into<PathBuf>, P2: Into<PathBuf>>(
182+
source: io::Error,
183+
source_path: P,
184+
dest_path: P2,
185+
) -> io::Error {
186+
io::Error::new(
187+
source.kind(),
188+
Self {
189+
source,
190+
source_path: source_path.into(),
191+
dest_path: dest_path.into(),
192+
},
193+
)
194+
}
195+
}
196+
197+
impl fmt::Display for CopyError {
198+
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
199+
write!(
200+
formatter,
201+
"{} copying path {} to {}",
202+
self.source,
203+
self.source_path.display(),
204+
self.dest_path.display()
205+
)
206+
}
207+
}
208+
209+
impl StdError for CopyError {
210+
fn source(&self) -> Option<&(dyn StdError + 'static)> {
211+
Some(&self.source)
212+
}
213+
}

src/main.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod aliaser;
22
mod artifact_choosing;
33
mod auth_store;
44
mod config;
5+
mod fs;
56
mod github;
67
mod paths;
78
mod tool_cache;
@@ -77,7 +78,13 @@ fn main() -> Result<(), Box<dyn Error>> {
7778
}
7879

7980
#[derive(Debug, StructOpt)]
80-
enum Options {
81+
struct Options {
82+
#[structopt(subcommand)]
83+
subcommand: Subcommand,
84+
}
85+
86+
#[derive(Debug, StructOpt)]
87+
enum Subcommand {
8188
/// Install tools defined by foreman.toml.
8289
Install,
8390

@@ -88,8 +95,8 @@ enum Options {
8895
fn actual_main() -> io::Result<()> {
8996
let options = Options::from_args();
9097

91-
match options {
92-
Options::Install => {
98+
match options.subcommand {
99+
Subcommand::Install => {
93100
let config = ConfigFile::aggregate()?;
94101

95102
log::trace!("Installing from gathered config: {:#?}", config);
@@ -99,7 +106,7 @@ fn actual_main() -> io::Result<()> {
99106
add_self_alias(tool_alias);
100107
}
101108
}
102-
Options::List => {
109+
Subcommand::List => {
103110
println!("Installed tools:");
104111

105112
let cache = ToolCache::load().unwrap();

src/paths.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Contains all of the paths that Foreman needs to deal with.
22
3-
use std::{fs, io, path::PathBuf};
3+
use std::{io, path::PathBuf};
4+
5+
use crate::fs;
46

57
static DEFAULT_USER_CONFIG: &str = include_str!("../resources/default-foreman.toml");
68
static DEFAULT_AUTH_STORE: &str = include_str!("../resources/default-auth.toml");

src/tool_cache.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{
22
collections::{BTreeSet, HashMap},
33
env::consts::EXE_SUFFIX,
4-
fs::{self, File},
54
io::{self, BufWriter, Cursor},
65
path::PathBuf,
76
process,
@@ -11,7 +10,11 @@ use semver::{Version, VersionReq};
1110
use serde::{Deserialize, Serialize};
1211
use zip::ZipArchive;
1312

14-
use crate::{artifact_choosing::platform_keywords, github, paths};
13+
use crate::{
14+
artifact_choosing::platform_keywords,
15+
fs::{self, File},
16+
github, paths,
17+
};
1518

1619
fn index_file() -> PathBuf {
1720
let mut path = paths::base_dir();

0 commit comments

Comments
 (0)