mirror of
https://github.com/numtide/treefmt.git
synced 2024-09-11 17:07:44 +03:00
Merge pull request #65 from numtide/config-simplifications
more config simplification
This commit is contained in:
commit
7a95af7f95
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -464,6 +464,24 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6ab2aaa5faefed84db46e4398eab15fa51325606462b5da8b0e230af3ac59a"
|
||||
dependencies = [
|
||||
"path-dedot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-dedot"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658c6e985fce9c25289fe7c86c08a3cbe82c19a3cd5b3bc5945c8c632552e460"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.0"
|
||||
@ -815,6 +833,7 @@ dependencies = [
|
||||
"hex",
|
||||
"ignore",
|
||||
"log",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.",
|
||||
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();
|
||||
|
||||
let treefmt_toml = cfg_dir.join("treefmt.toml");
|
||||
CLOG.debug(&format!(
|
||||
"Found {} at {}",
|
||||
treefmt_toml.display(),
|
||||
treefmt_root.display()
|
||||
));
|
||||
|
||||
// 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) => {
|
||||
@ -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(())
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
@ -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(())
|
||||
|
@ -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";
|
||||
@ -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>,
|
||||
@ -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(),
|
||||
@ -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(),
|
||||
})
|
||||
}
|
||||
|
@ -1,28 +1,32 @@
|
||||
//! The main formatting engine logic should be in this module.
|
||||
|
||||
use crate::eval_cache::{check_treefmt, create_manifest, read_manifest, RootManifest};
|
||||
use crate::{config, customlog, CmdContext, CmdMeta, FileMeta, CLOG};
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use filetime::FileTime;
|
||||
use crate::eval_cache::{check_treefmt, create_manifest, get_mtime, read_manifest, RootManifest};
|
||||
use crate::{config, CmdContext, CmdMeta, FileMeta, CLOG};
|
||||
use anyhow::{Error, Result};
|
||||
use rayon::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::metadata;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
/// Run the treefmt
|
||||
pub fn run_treefmt(cwd: PathBuf, cache_dir: PathBuf) -> anyhow::Result<()> {
|
||||
let treefmt_toml = cwd.join(config::FILENAME);
|
||||
|
||||
pub fn run_treefmt(cwd: PathBuf, cache_dir: PathBuf, treefmt_toml: PathBuf) -> anyhow::Result<()> {
|
||||
let project_config = config::from_path(&treefmt_toml)?;
|
||||
|
||||
// Load the manifest if it exists, otherwise print the error and use an empty manifest
|
||||
let mfst: RootManifest = match read_manifest(&treefmt_toml, &cache_dir) {
|
||||
Ok(mfst) => mfst,
|
||||
Err(err) => {
|
||||
CLOG.warn(&format!("Using empty manifest due to error: {}", err));
|
||||
RootManifest::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Once the treefmt found the $XDG_CACHE_DIR/treefmt/eval-cache/ folder,
|
||||
// it will try to scan the manifest and passed it into check_treefmt function
|
||||
let old_ctx = create_command_context(&cwd, &project_config)?;
|
||||
// TODO: Resolve all of the formatters paths. If missing, print an error, remove the formatters from the list and continue.
|
||||
// Load the manifest if it exists, otherwise start with empty manifest
|
||||
let mfst: RootManifest = read_manifest(&treefmt_toml, &cache_dir)?;
|
||||
|
||||
// Compare the list of files with the manifest, keep the ones that are not in the manifest
|
||||
let ctxs = check_treefmt(&treefmt_toml, &old_ctx, &mfst)?;
|
||||
let context = if mfst.manifest.is_empty() && ctxs.is_empty() {
|
||||
@ -31,21 +35,11 @@ pub fn run_treefmt(cwd: PathBuf, cache_dir: PathBuf) -> anyhow::Result<()> {
|
||||
&ctxs
|
||||
};
|
||||
|
||||
if !treefmt_toml.exists() {
|
||||
return Err(anyhow!(
|
||||
"{}treefmt.toml not found, please run --init command",
|
||||
customlog::ERROR
|
||||
));
|
||||
}
|
||||
|
||||
println!("===========================");
|
||||
for c in context {
|
||||
if !c.metadata.is_empty() {
|
||||
println!("{}", c.command);
|
||||
println!(
|
||||
"Working Directory: {}",
|
||||
c.work_dir.clone().unwrap_or("".to_string())
|
||||
);
|
||||
println!("{}", c.name);
|
||||
println!("Working Directory: {}", c.work_dir.display());
|
||||
println!("Files:");
|
||||
for m in &c.metadata {
|
||||
let path = &m.path;
|
||||
@ -60,18 +54,15 @@ pub fn run_treefmt(cwd: PathBuf, cache_dir: PathBuf) -> anyhow::Result<()> {
|
||||
.map(|c| {
|
||||
let arg = &c.options;
|
||||
let mut cmd_arg = Command::new(&c.path);
|
||||
let work_dir = match c.work_dir.clone() {
|
||||
Some(x) => x,
|
||||
None => String::new(),
|
||||
};
|
||||
// Set the command to run under its working directory.
|
||||
cmd_arg.current_dir(&c.work_dir);
|
||||
// Append the default options to the command.
|
||||
cmd_arg.args(arg);
|
||||
// Append all of the file paths to format.
|
||||
let paths = c.metadata.iter().map(|f| &f.path);
|
||||
if !work_dir.is_empty() {
|
||||
let _x = std::env::set_current_dir(work_dir);
|
||||
cmd_arg.args(arg).args(paths).output()
|
||||
} else {
|
||||
let _x = std::env::set_current_dir(cwd.clone());
|
||||
cmd_arg.args(arg).args(paths).output()
|
||||
}
|
||||
cmd_arg.args(paths);
|
||||
// And run
|
||||
cmd_arg.output()
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -135,10 +126,9 @@ pub fn glob_to_path(
|
||||
pub fn path_to_filemeta(paths: Vec<PathBuf>) -> Result<BTreeSet<FileMeta>> {
|
||||
let mut filemeta = BTreeSet::new();
|
||||
for p in paths {
|
||||
let metadata = metadata(&p)?;
|
||||
let mtime = FileTime::from_last_modification_time(&metadata).unix_seconds();
|
||||
let mtime = get_mtime(&p)?;
|
||||
if !filemeta.insert(FileMeta {
|
||||
mtimes: mtime,
|
||||
mtime,
|
||||
path: p.clone(),
|
||||
}) {
|
||||
CLOG.warn("Duplicated file detected:");
|
||||
@ -156,12 +146,12 @@ pub fn create_command_context(
|
||||
) -> Result<Vec<CmdContext>> {
|
||||
let cmd_context: Vec<CmdContext> = toml_content
|
||||
.formatter
|
||||
.values()
|
||||
.map(|config| {
|
||||
.iter()
|
||||
.map(|(name, config)| {
|
||||
let list_files = glob_to_path(&cwd.to_path_buf(), &config.includes, &config.excludes)?;
|
||||
let cmd_meta = CmdMeta::new(config.command.clone())?;
|
||||
let cmd_meta = CmdMeta::new(&config.command)?;
|
||||
Ok(CmdContext {
|
||||
command: cmd_meta.name,
|
||||
name: name.clone(),
|
||||
mtime: cmd_meta.mtime,
|
||||
path: cmd_meta.path,
|
||||
options: config.options.clone(),
|
||||
@ -195,12 +185,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_path_to_filemeta() -> Result<()> {
|
||||
let file_path = PathBuf::from(r"examples/rust/src/main.rs");
|
||||
let metadata = metadata(&file_path)?;
|
||||
let mtime = FileTime::from_last_modification_time(&metadata).unix_seconds();
|
||||
let mtime = get_mtime(&file_path)?;
|
||||
let mut vec_path = Vec::new();
|
||||
vec_path.push(file_path);
|
||||
let file_meta = FileMeta {
|
||||
mtimes: mtime,
|
||||
mtime,
|
||||
path: PathBuf::from(r"examples/rust/src/main.rs"),
|
||||
};
|
||||
let mut set_filemeta = BTreeSet::new();
|
||||
|
@ -2,19 +2,22 @@
|
||||
use crate::{customlog, CmdContext, CLOG};
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use filetime::FileTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
/// RootManifest
|
||||
pub struct RootManifest {
|
||||
/// Map of manifests config based on its formatter
|
||||
pub manifest: BTreeMap<String, CmdContext>,
|
||||
}
|
||||
|
||||
/// Create <hex(hash(path-to-treefmt))>.toml and put it in $XDG_CACHE_DIR/treefmt/eval-cache/
|
||||
pub fn create_manifest(
|
||||
treefmt_toml: PathBuf,
|
||||
@ -27,20 +30,20 @@ pub fn create_manifest(
|
||||
let map_manifest: BTreeMap<String, CmdContext> = cmd_ctx
|
||||
.into_iter()
|
||||
.map(|cmd| {
|
||||
let treefmt = cmd.command.clone();
|
||||
let name = cmd.name.clone();
|
||||
let manifest = CmdContext {
|
||||
command: cmd.command,
|
||||
mtime: cmd.mtime,
|
||||
name: cmd.name,
|
||||
path: cmd.path,
|
||||
mtime: cmd.mtime,
|
||||
work_dir: cmd.work_dir,
|
||||
options: cmd.options,
|
||||
metadata: cmd.metadata,
|
||||
};
|
||||
(treefmt, manifest)
|
||||
(name, manifest)
|
||||
})
|
||||
.collect();
|
||||
let manifest_toml = RootManifest {
|
||||
manifest: map_manifest.clone(),
|
||||
manifest: map_manifest,
|
||||
};
|
||||
f.write_all(
|
||||
format!(
|
||||
@ -106,18 +109,18 @@ pub fn check_treefmt(
|
||||
) -> Result<Vec<CmdContext>> {
|
||||
let cache_context = cache.manifest.values();
|
||||
let map_ctx: BTreeMap<String, CmdContext> = cmd_context
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|cmd| {
|
||||
let treefmt = cmd.command.clone();
|
||||
let name = cmd.name.clone();
|
||||
let ctx = CmdContext {
|
||||
command: cmd.command.clone(),
|
||||
mtime: cmd.mtime.clone(),
|
||||
name: cmd.name.clone(),
|
||||
mtime: cmd.mtime,
|
||||
path: cmd.path.clone(),
|
||||
work_dir: cmd.work_dir.clone(),
|
||||
options: cmd.options.clone(),
|
||||
metadata: cmd.metadata.clone(),
|
||||
};
|
||||
(treefmt, ctx)
|
||||
(name, ctx)
|
||||
})
|
||||
.collect();
|
||||
let new_cmd_ctx = map_ctx.values();
|
||||
@ -126,12 +129,12 @@ pub fn check_treefmt(
|
||||
.clone()
|
||||
.map(|(new, old)| {
|
||||
Ok(CmdContext {
|
||||
command: new.command.clone(),
|
||||
mtime: new.mtime.clone(),
|
||||
name: new.name.clone(),
|
||||
path: new.path.clone(),
|
||||
mtime: new.mtime,
|
||||
work_dir: new.work_dir.clone(),
|
||||
options: new.options.clone(),
|
||||
metadata: if new.command != old.command
|
||||
metadata: if new.path != old.path
|
||||
|| new.options != old.options
|
||||
|| new.work_dir != old.work_dir
|
||||
{
|
||||
@ -160,7 +163,7 @@ pub fn check_treefmt(
|
||||
CLOG.info(&format!(
|
||||
" - {} last modification time: {}",
|
||||
p.path.display(),
|
||||
p.mtimes
|
||||
p.mtime
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -199,3 +202,21 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Mtime represents a unix epoch file modification time
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Copy, Clone)]
|
||||
pub struct Mtime(i64);
|
||||
|
||||
impl fmt::Display for Mtime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Small utility that stat() and retrieve the mtime of a file
|
||||
pub fn get_mtime(path: &PathBuf) -> Result<Mtime> {
|
||||
let metadata = std::fs::metadata(path)?;
|
||||
Ok(Mtime(
|
||||
FileTime::from_last_modification_time(&metadata).unix_seconds(),
|
||||
))
|
||||
}
|
||||
|
86
src/lib.rs
86
src/lib.rs
@ -7,16 +7,14 @@ pub mod customlog;
|
||||
pub mod engine;
|
||||
pub mod eval_cache;
|
||||
|
||||
use crate::eval_cache::{get_mtime, Mtime};
|
||||
use anyhow::Result;
|
||||
use customlog::CustomLogOutput;
|
||||
use filetime::FileTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::fs::metadata;
|
||||
use std::path::PathBuf;
|
||||
use which::which;
|
||||
|
||||
/// The global custom log and user-facing message output.
|
||||
pub static CLOG: CustomLogOutput = CustomLogOutput::new();
|
||||
@ -24,14 +22,14 @@ pub static CLOG: CustomLogOutput = CustomLogOutput::new();
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
/// Each context of the formatter config
|
||||
pub struct CmdContext {
|
||||
/// formatter command to run
|
||||
pub command: String,
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtime: i64,
|
||||
/// Name of the command
|
||||
pub name: String,
|
||||
/// Path to the formatted file
|
||||
pub path: PathBuf,
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtime: Mtime,
|
||||
/// formatter work_dir
|
||||
pub work_dir: Option<String>,
|
||||
pub work_dir: PathBuf,
|
||||
/// formatter arguments or flags
|
||||
pub options: Vec<String>,
|
||||
/// formatter target path
|
||||
@ -44,12 +42,12 @@ impl CmdContext {
|
||||
let new_meta: BTreeSet<FileMeta> = self
|
||||
.metadata
|
||||
.into_iter()
|
||||
.map(|e| e.update_mtimes())
|
||||
.map(|e| e.update_mtime())
|
||||
.collect::<Result<BTreeSet<FileMeta>>>()?;
|
||||
Ok(CmdContext {
|
||||
command: self.command,
|
||||
mtime: self.mtime,
|
||||
name: self.name,
|
||||
path: self.path,
|
||||
mtime: self.mtime,
|
||||
work_dir: self.work_dir,
|
||||
options: self.options,
|
||||
metadata: new_meta,
|
||||
@ -59,7 +57,8 @@ impl CmdContext {
|
||||
|
||||
impl PartialEq for CmdContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.command == other.command
|
||||
self.name == other.name
|
||||
&& self.path == other.path
|
||||
&& self.work_dir == other.work_dir
|
||||
&& self.options == other.options
|
||||
&& self.metadata == other.metadata
|
||||
@ -71,58 +70,34 @@ impl Eq for CmdContext {}
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
/// Command metadata created after the first treefmt run
|
||||
pub struct CmdMeta {
|
||||
/// Name provided by user's config
|
||||
pub name: String,
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtime: i64,
|
||||
/// Path to the formatted file
|
||||
/// Absolute path to the formatter
|
||||
pub path: PathBuf,
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtime: Mtime,
|
||||
}
|
||||
|
||||
impl CmdMeta {
|
||||
/// Create new CmdMeta based on the given config name
|
||||
pub fn new(cmd: String) -> Result<Self> {
|
||||
let cmd_path = Self::check_bin(&cmd)?;
|
||||
let metadata = metadata(&cmd_path)?;
|
||||
let cmd_mtime = FileTime::from_last_modification_time(&metadata).unix_seconds();
|
||||
|
||||
///
|
||||
/// We assume that cmd_path is absolute.
|
||||
pub fn new(cmd_path: &PathBuf) -> Result<Self> {
|
||||
assert!(cmd_path.is_absolute());
|
||||
Ok(CmdMeta {
|
||||
name: cmd,
|
||||
mtime: cmd_mtime,
|
||||
path: cmd_path,
|
||||
path: cmd_path.clone(),
|
||||
mtime: get_mtime(&cmd_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Make sure that formatter binary exists. This also for other formatter
|
||||
fn check_bin<'a>(command: &'a str) -> Result<PathBuf> {
|
||||
let cmd_bin = command.split_ascii_whitespace().next().unwrap_or("");
|
||||
if let Ok(path) = which(cmd_bin) {
|
||||
CLOG.debug(&format!("Found {} at {}", cmd_bin, path.display()));
|
||||
return Ok(path);
|
||||
}
|
||||
anyhow::bail!(
|
||||
"Failed to locate formatter named {}. \
|
||||
Please make sure it is available in your PATH",
|
||||
command
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CmdMeta {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: ({}, {})",
|
||||
self.name,
|
||||
self.path.display(),
|
||||
self.mtime
|
||||
)
|
||||
write!(f, "({}, {})", self.path.display(), self.mtime)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CmdMeta {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.mtime == other.mtime && self.path == other.path
|
||||
self.path == other.path && self.mtime == other.mtime
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,19 +106,18 @@ impl Eq for CmdMeta {}
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
/// File metadata created after the first treefmt run
|
||||
pub struct FileMeta {
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtimes: i64,
|
||||
/// Path to the formatted file
|
||||
pub path: PathBuf,
|
||||
/// Last modification time listed in the file's metadata
|
||||
pub mtime: Mtime,
|
||||
}
|
||||
|
||||
impl FileMeta {
|
||||
fn update_mtimes(self) -> Result<Self> {
|
||||
let metadata = metadata(&self.path)?;
|
||||
let mtime = FileTime::from_last_modification_time(&metadata).unix_seconds();
|
||||
fn update_mtime(self) -> Result<Self> {
|
||||
let mtime = get_mtime(&self.path)?;
|
||||
Ok(FileMeta {
|
||||
mtimes: mtime,
|
||||
path: self.path,
|
||||
mtime,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -153,10 +127,10 @@ impl Ord for FileMeta {
|
||||
if self.eq(other) {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
if self.mtimes.eq(&other.mtimes) {
|
||||
if self.mtime.eq(&other.mtime) {
|
||||
return self.path.cmp(&other.path);
|
||||
}
|
||||
self.mtimes.cmp(&other.mtimes)
|
||||
self.mtime.cmp(&other.mtime)
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,7 +142,7 @@ impl PartialOrd for FileMeta {
|
||||
|
||||
impl PartialEq for FileMeta {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.mtimes == other.mtimes && self.path == other.path
|
||||
self.mtime == other.mtime && self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#![allow(clippy::redundant_closure, clippy::redundant_pattern_matching)]
|
||||
|
||||
use structopt::StructOpt;
|
||||
use treefmt::command::{run_cli, Cli};
|
||||
use treefmt::command::{cli_from_args, run_cli};
|
||||
use treefmt::CLOG;
|
||||
|
||||
fn main() {
|
||||
@ -12,7 +11,7 @@ fn main() {
|
||||
}
|
||||
|
||||
fn run() -> anyhow::Result<()> {
|
||||
let options = Cli::from_args();
|
||||
let options = cli_from_args()?;
|
||||
|
||||
CLOG.set_log_level(options.log_level);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user