Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more config simplification #65

Merged
merged 12 commits into from
Feb 22, 2021
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ structopt = "0.3"
toml = "0.5"
which = "4.0.2"

[dependencies.path-absolutize]
version = "3.0.6"
# Do not use `std::env::set_current_dir`.
# See https://github.com/magiclen/path-absolutize#once_cell_cache
features = ["once_cell_cache"]

[dev-dependencies]
criterion = "0.3"

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ FLAGS:
-v, --verbose Log verbosity is based off the number of v used

OPTIONS:
-C, --config <config> Specify where to look for the treefmt.toml file
--log-level <log-level> The maximum level of messages that should be logged by treefmt. [possible values:
info, warn, error] [default: debug]
-C <work-dir> Run as if treefmt was started in <work-dir> instead of the current working directory
[default: .]

SUBCOMMANDS:
--init Init a new project with a default config
Expand Down
49 changes: 23 additions & 26 deletions src/command/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@ use std::fs;
use std::path::Path;
use std::{env, path::PathBuf};

pub fn format_cmd(path: Option<PathBuf>) -> anyhow::Result<()> {
let cfg_dir = match path {
pub fn format_cmd(work_dir: PathBuf) -> anyhow::Result<()> {
// Search for the treefmt.toml from there.
let treefmt_toml = match config::lookup(&work_dir) {
Some(p) => p,
None => {
let cwd = env::current_dir()?;
match config::lookup_dir(&cwd) {
Some(p) => p,
None => return Err(anyhow!("treefmt.toml could not be found in {} and up. Use the --init option to create one.", cwd.display()))
}
return Err(anyhow!(
"{} could not be found in {} and up. Use the --init option to create one.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if --init shouldn't be init as it's presented as a subcommand in the help menu.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init is our first design. But iirc, we want to avoid GitHub's CLI styling and that's why we ended up with --init

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option is to not make it a sub-command but just a normal flag. And then deal with the command dispatch internally.

99% of the cases will be to run the formatter, which is the command we want to optimize for.

config::FILENAME,
work_dir.display()
))
}
};
// We assume that the parent always exists since the file is contained in a folder.
let treefmt_root = treefmt_toml.parent().unwrap();

CLOG.debug(&format!(
"Found {} at {}",
treefmt_toml.display(),
treefmt_root.display()
));

let treefmt_toml = cfg_dir.join("treefmt.toml");
// Find the location of the cache directory to store the eval-cache manifests.
let xdg_cache_dir = match env::var("XDG_CACHE_DIR") {
Ok(path) => path,
Err(err) => {
Expand All @@ -36,24 +45,12 @@ pub fn format_cmd(path: Option<PathBuf>) -> anyhow::Result<()> {
}
}
};
let cache_dir = Path::new(&xdg_cache_dir).join("treefmt/eval-cache");
// Make sure the cache directory exists.
fs::create_dir_all(&cache_dir)?;

// Finally run the main formatter logic from the engine.
run_treefmt(work_dir, cache_dir, treefmt_toml)?;

if treefmt_toml.exists() {
CLOG.debug(&format!(
"Found {} at {}",
treefmt_toml.display(),
cfg_dir.display()
));
CLOG.debug(&format!(
"Change current directory into: {}",
cfg_dir.display()
));
let cache_dir = Path::new(&xdg_cache_dir).join("treefmt/eval-cache");
fs::create_dir_all(&cache_dir)?;
run_treefmt(cfg_dir, cache_dir)?;
} else {
CLOG.error(
"file treefmt.toml couldn't be found. Run `--init` to generate the default setting",
);
}
Ok(())
}
9 changes: 3 additions & 6 deletions src/command/init.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use crate::config;
use crate::CLOG;
use anyhow::Context;
use console::style;
use std::fs;
use std::path::PathBuf;

pub fn init_cmd(path: Option<PathBuf>) -> anyhow::Result<()> {
let file = match path {
Some(loc) => loc,
None => PathBuf::from("."),
};
let file_path = file.join("treefmt.toml");
pub fn init_cmd(work_dir: PathBuf) -> anyhow::Result<()> {
let file_path = work_dir.join(config::FILENAME);
// TODO: detect if file exists
fs::write(
&file_path,
Expand Down
21 changes: 16 additions & 5 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod init;
use self::format::format_cmd;
use self::init::init_cmd;
use super::customlog::LogLevel;
use path_absolutize::*;
use std::path::PathBuf;
use structopt::StructOpt;

Expand Down Expand Up @@ -37,16 +38,26 @@ pub struct Cli {
/// The maximum level of messages that should be logged by treefmt. [possible values: info, warn, error]
pub log_level: LogLevel,

#[structopt(long = "config", short = "C")]
/// Specify where to look for the treefmt.toml file
pub config: Option<PathBuf>,
#[structopt(short = "C", default_value = ".")]
/// Run as if treefmt was started in <work-dir> instead of the current working directory.
pub work_dir: PathBuf,
}

/// Use this instead of Cli::from_args(). We do a little bit of post-processing here.
pub fn cli_from_args() -> anyhow::Result<Cli> {
let mut cli = Cli::from_args();
// Make sure the work_dir is an absolute path. Don't use the stdlib canonicalize() function
// because symlinks should not be resolved.
let abs_work_dir = cli.work_dir.absolutize()?;
cli.work_dir = abs_work_dir.to_path_buf();
Ok(cli)
}

/// Run a command with the given logger
pub fn run_cli(cli: Cli) -> anyhow::Result<()> {
match cli.cmd {
Some(Command::Init {}) => init_cmd(cli.config)?,
None => format_cmd(cli.config)?,
Some(Command::Init {}) => init_cmd(cli.work_dir)?,
None => format_cmd(cli.work_dir)?,
}

Ok(())
Expand Down
71 changes: 61 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Contains the project configuration schema and parsing
use crate::CLOG;
use anyhow::Result;
use path_absolutize::*;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::fs::read_to_string;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use which::which;

/// Name of the config file
pub const FILENAME: &str = "treefmt.toml";
Expand All @@ -19,9 +22,10 @@ pub struct Root {
#[derive(Debug, Deserialize)]
pub struct FmtConfig {
/// Command formatter to run
pub command: String,
pub command: PathBuf,
/// Working directory for formatter
pub work_dir: Option<String>,
#[serde(default = "cwd")]
pub work_dir: PathBuf,
/// Argument for formatter
#[serde(default)]
pub options: Vec<String>,
Expand All @@ -33,12 +37,18 @@ pub struct FmtConfig {
pub excludes: Vec<String>,
}

/// Find the directory that contains the treefmt.toml file. From the current folder, and up.
pub fn lookup_dir(dir: &PathBuf) -> Option<PathBuf> {
// The default work_dir value. It's a bit clunky. See https://github.com/serde-rs/serde/issues/1814
fn cwd() -> PathBuf {
".".into()
}

/// Returns an absolute path to the treefmt.toml file. From the current folder, and up.
pub fn lookup(dir: &PathBuf) -> Option<PathBuf> {
let mut cwd = dir.clone();
loop {
if cwd.join(FILENAME).exists() {
return Some(cwd);
let config_file = cwd.join(FILENAME);
if config_file.exists() {
return Some(config_file);
}
cwd = match cwd.parent() {
Some(x) => x.to_path_buf(),
Expand All @@ -50,8 +60,49 @@ pub fn lookup_dir(dir: &PathBuf) -> Option<PathBuf> {
}

/// Loads the treefmt.toml config from the given file path.
pub fn from_path(path: &PathBuf) -> Result<Root> {
let content = read_to_string(path)?;
let ret: Root = toml::from_str(&content)?;
pub fn from_path(file_path: &PathBuf) -> Result<Root> {
// unwrap: assume the file is in a folder
let file_dir = file_path.parent().unwrap();
// Load the file
let content = read_to_string(file_path)?;
// Parse the config
let mut ret: Root = toml::from_str(&content)?;
// Expand a bunch of formatter configs. If any of these fail, don't make it a fatal issue. Display the error and continue.
let new_formatter = ret
.formatter
.iter()
.fold(BTreeMap::new(), |mut sum, (name, fmt)| {
match load_formatter(fmt, file_dir) {
// Re-add the resolved formatter if it was successful
Ok(fmt2) => {
sum.insert(name.clone(), fmt2);
}
Err(err) => CLOG.warn(&format!("Ignoring {} because of error: {}", name, err)),
};
sum
});
// Replace the formatters with the loaded ones
ret.formatter = new_formatter;

Ok(ret)
}

fn load_formatter(fmt: &FmtConfig, config_dir: &Path) -> Result<FmtConfig> {
// Expand the work_dir to an absolute path, using the config directory as a reference.
let abs_work_dir = fmt.work_dir.absolutize_virtually(config_dir)?;
// Resolve the path to the binary
let abs_command = which(&fmt.command)?;
assert!(abs_command.is_absolute());
CLOG.debug(&format!(
"Found {} at {}",
fmt.command.display(),
abs_command.display()
));
Ok(FmtConfig {
command: abs_command,
work_dir: abs_work_dir.to_path_buf(),
options: fmt.options.clone(),
includes: fmt.includes.clone(),
excludes: fmt.excludes.clone(),
})
}
Loading