mirror of
https://github.com/numtide/treefmt.git
synced 2024-09-11 17:07:44 +03:00
first working example
This commit is contained in:
parent
431a174b9d
commit
d40f78417a
@ -31,5 +31,9 @@ mkDevShell {
|
||||
haskellPackages.cabal-install
|
||||
haskellPackages.ghc
|
||||
nixpkgs-fmt
|
||||
go
|
||||
# gopls
|
||||
# gopkgs
|
||||
# gocode
|
||||
];
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ args = [
|
||||
"--check-idempotence"
|
||||
]
|
||||
|
||||
[formatters.rustfmt]
|
||||
files = [ "*.hs" ]
|
||||
[formatters.cargo]
|
||||
files = [ "*.rs" ]
|
||||
includes = [ "rust/" ]
|
||||
excludes = []
|
||||
command = "rustfmt"
|
||||
args = []
|
||||
command = "cargo"
|
||||
args = [ "fmt", "--" ]
|
@ -1,2 +1,3 @@
|
||||
import Distribution.Simple
|
||||
|
||||
main = defaultMain
|
||||
|
@ -1,4 +1,5 @@
|
||||
module Main where
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn "Hello, Haskell!"
|
||||
main =
|
||||
putStrLn "Hello, Haskell!"
|
||||
|
@ -1,2 +1,3 @@
|
||||
import Distribution.Simple
|
||||
|
||||
main = defaultMain
|
||||
|
@ -1,71 +1,5 @@
|
||||
//! Functionality related to installing prebuilt binaries
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
/// Specific language formatting tools
|
||||
pub mod tools;
|
||||
mod tool;
|
||||
use self::tool::Tools;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use xshell::{cmd, pushd, pushenv};
|
||||
|
||||
// TODO: This module provides functions to install all formatter
|
||||
// that is not available in user's $PATH
|
||||
/// Make sure that rustfmt exists. This also for other formatter
|
||||
fn check_rustfmt() -> anyhow::Result<()> {
|
||||
let out = cmd!("rustfmt --version").read()?;
|
||||
if !out.contains("stable") && !out.contains("nightly") {
|
||||
anyhow::bail!(
|
||||
"Failed to run rustfmt from toolchain 'stable'. \
|
||||
Please make sure it is available in your PATH",
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Running rustfmt
|
||||
pub fn run_rustfmt(mode: Mode, path: PathBuf) -> anyhow::Result<()> {
|
||||
let _dir = pushd(path)?;
|
||||
let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
|
||||
check_rustfmt()?;
|
||||
let check = match mode {
|
||||
Mode::Overwrite => &[][..],
|
||||
Mode::Verify => &["--", "--check"],
|
||||
};
|
||||
cmd!("cargo fmt {check...}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Formatter mode
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Mode {
|
||||
/// Start the formatting process
|
||||
Overwrite,
|
||||
/// Trying to check the project using formatter. This maybe helpful to --dry-run
|
||||
Verify,
|
||||
}
|
||||
|
||||
/// fmt.toml structure
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Root {
|
||||
formatters: HashMap<String, FmtConfig>,
|
||||
}
|
||||
|
||||
/// Config for each formatters
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FmtConfig {
|
||||
files: FileExtensions,
|
||||
includes: Option<Vec<String>>,
|
||||
excludes: Option<Vec<String>>,
|
||||
command: Option<Tools>,
|
||||
args: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum FileExtensions {
|
||||
SingleFile(String),
|
||||
MultipleFile(Vec<String>),
|
||||
}
|
||||
/// Formatter utility
|
||||
pub mod tool;
|
||||
|
@ -1,29 +1,128 @@
|
||||
use crate::emoji;
|
||||
use console::style;
|
||||
|
||||
// use super::tools;
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use tools::{
|
||||
haskell::{Hfmt, deserialize_haskell},
|
||||
go::{Gfmt, deserialize_go},
|
||||
rust::{Rfmt, deserialize_rust}
|
||||
};
|
||||
use glob::{glob, Paths};
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
use std::vec::IntoIter;
|
||||
// use tools::{
|
||||
// go::{deserialize_go, Gfmt},
|
||||
// haskell::{deserialize_haskell, Hfmt},
|
||||
// rust::{deserialize_rust, Rfmt},
|
||||
// };
|
||||
use xshell::cmd;
|
||||
|
||||
use super::tools;
|
||||
|
||||
/// Represents the set of formatter tools `allfmt` uses
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged, rename_all = "lowercase")]
|
||||
pub enum Tools {
|
||||
/// Haskell formatter tool
|
||||
#[serde(deserialize_with = "deserialize_haskell")]
|
||||
HaskellFmts(Hfmt),
|
||||
/// gofmt tools
|
||||
#[serde(deserialize_with = "deserialize_go")]
|
||||
GoFmts(Gfmt),
|
||||
/// rustfmt tools
|
||||
#[serde(deserialize_with = "deserialize_rust")]
|
||||
RustFmts(Rfmt),
|
||||
// TODO: This module provides functions to install all formatter
|
||||
// that is not available in user's $PATH
|
||||
/// Make sure that rustfmt exists. This also for other formatter
|
||||
pub fn check_fmt(command: String) -> Result<()> {
|
||||
let cmd_bin = command.split_ascii_whitespace().next().unwrap_or("");
|
||||
if let Ok(str) = cmd!("which {cmd_bin}").read() {
|
||||
return Ok(());
|
||||
}
|
||||
anyhow::bail!(
|
||||
"Failed to locate formatter named {}. \
|
||||
Please make sure it is available in your PATH",
|
||||
command
|
||||
)
|
||||
}
|
||||
|
||||
/// Running the fmt
|
||||
pub fn run_fmt(cmd_arg: &str, args: &Vec<String>, path: PathBuf) -> anyhow::Result<()> {
|
||||
cmd!("{cmd_arg} {args...} {path}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert glob pattern into list of pathBuf
|
||||
pub fn glob_to_path(cwd: PathBuf, extensions: FileExtensions) -> Result<Paths> {
|
||||
match extensions {
|
||||
FileExtensions::SingleFile(sfile) => {
|
||||
let dir = cwd.as_path().to_str().unwrap_or("");
|
||||
let pat = format!("{}**/{}", dir, &sfile);
|
||||
match glob(&pat) {
|
||||
Ok(paths) => Ok(paths),
|
||||
Err(err) => {
|
||||
anyhow::bail!(
|
||||
"{} Error at position: {} due to {}",
|
||||
emoji::ERROR,
|
||||
err.pos,
|
||||
err.msg
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FileExtensions::MultipleFile(strs) => {
|
||||
let files = strs
|
||||
.into_iter()
|
||||
.map(|str| {
|
||||
let dir = cwd.as_path().to_str().unwrap_or("");
|
||||
let pat = format!("{}**/{}", dir, &str);
|
||||
match glob(&pat) {
|
||||
Ok(paths) => Ok(paths),
|
||||
Err(err) => {
|
||||
anyhow::bail!(
|
||||
"{} Error at position: {} due to {}",
|
||||
emoji::ERROR,
|
||||
err.pos,
|
||||
err.msg
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.nth(0);
|
||||
match files {
|
||||
Some(paths) => Ok(paths),
|
||||
None => {
|
||||
anyhow::bail!("{} Blob not found", emoji::ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert glob's Paths into list of PathBuf
|
||||
pub fn paths_to_pathbuf(
|
||||
inc: Vec<String>,
|
||||
excl: Vec<String>,
|
||||
paths_list: Vec<Paths>,
|
||||
) -> Vec<PathBuf> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// fmt.toml structure
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Root {
|
||||
/// Map of formatters into the config
|
||||
pub formatters: BTreeMap<String, FmtConfig>,
|
||||
}
|
||||
|
||||
/// Config for each formatters
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FmtConfig {
|
||||
/// File extensions that want to be formatted
|
||||
pub files: FileExtensions,
|
||||
/// File or Folder that is included to be formatted
|
||||
pub includes: Option<Vec<String>>,
|
||||
/// File or Folder that is excluded to be formatted
|
||||
pub excludes: Option<Vec<String>>,
|
||||
/// Command formatter to run
|
||||
pub command: Option<String>,
|
||||
/// Argument for formatter
|
||||
pub args: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// File extensions can be single string (e.g. "*.hs") or
|
||||
/// list of string (e.g. [ "*.hs", "*.rs" ])
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum FileExtensions {
|
||||
/// Single file type
|
||||
SingleFile(String),
|
||||
/// List of file type
|
||||
MultipleFile(Vec<String>),
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use crate::emoji;
|
||||
use console::style;
|
||||
|
||||
/// Gofmt const
|
||||
pub const GO_FMT_GOFMT: &str = "gofmt";
|
||||
|
||||
/// Name of Go formatter
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Gfmt {
|
||||
/// the default gofmt
|
||||
GoFmt
|
||||
}
|
||||
|
||||
/// Parse config's input into data type
|
||||
pub fn from_config<'de, D>(code: &str) -> std::result::Result<Gfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match code.as_ref() {
|
||||
GO_FMT_GOFMT=> Ok(Gfmt::GoFmt),
|
||||
_ => Err(D::Error::custom(format!(
|
||||
"{} {}",
|
||||
emoji::ERROR,
|
||||
style("Unknown Go formatter").bold().red(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of deserialization for Go
|
||||
pub fn deserialize_go<'de, D>(deserializer: D) -> std::result::Result<Gfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
from_config::<D>(&s)
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use crate::emoji;
|
||||
use console::style;
|
||||
|
||||
/// Brittany const
|
||||
pub const HASKELL_FMT_BRITTANY: &str = "brittany";
|
||||
/// Floskell const
|
||||
pub const HASKELL_FMT_FLOSKELL: &str = "floskell";
|
||||
/// Fourmolu const
|
||||
pub const HASKELL_FMT_FOURMOLU: &str = "fourmolu";
|
||||
/// Ormolu const
|
||||
pub const HASKELL_FMT_ORMOLU: &str = "ormolu";
|
||||
/// Stylish-Haskell const
|
||||
pub const HASKELL_FMT_STYLISHHASKELL: &str = "stylish-haskell";
|
||||
|
||||
/// List of Haskell formatters that are available
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Hfmt {
|
||||
/// Brittany
|
||||
Brittany,
|
||||
/// Floskell
|
||||
Floskell,
|
||||
/// Fourmolu
|
||||
Fourmolu,
|
||||
/// Ormolu (default)
|
||||
Ormolu,
|
||||
/// Stylish-Haskell
|
||||
StylishHaskell,
|
||||
}
|
||||
|
||||
/// Parse config's input into data type
|
||||
pub fn from_config<'de, D>(code: &str) -> std::result::Result<Hfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match code.as_ref() {
|
||||
HASKELL_FMT_BRITTANY => Ok(Hfmt::Brittany),
|
||||
HASKELL_FMT_FLOSKELL => Ok(Hfmt::Floskell),
|
||||
HASKELL_FMT_FOURMOLU => Ok(Hfmt::Fourmolu),
|
||||
HASKELL_FMT_ORMOLU => Ok(Hfmt::Ormolu),
|
||||
HASKELL_FMT_STYLISHHASKELL => Ok(Hfmt::StylishHaskell),
|
||||
_ => Err(D::Error::custom(format!(
|
||||
"{} {}",
|
||||
emoji::ERROR,
|
||||
style("Unknown Haskell formatter").bold().red(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of deserialization for Haskell
|
||||
pub fn deserialize_haskell<'de, D>(deserializer: D) -> std::result::Result<Hfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
from_config::<D>(&s)
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/// Go formatters
|
||||
pub mod go;
|
||||
/// Haskell formatters
|
||||
pub mod haskell;
|
||||
/// Rust formatters
|
||||
pub mod rust;
|
@ -1,44 +0,0 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use crate::emoji;
|
||||
use console::style;
|
||||
|
||||
/// Cargo const
|
||||
pub const RUST_FMT_CARGO: &str = "cargo";
|
||||
/// Rustfmt const
|
||||
pub const RUST_FMT_RUSTFMT: &str = "rustfmt";
|
||||
|
||||
/// Name of Rust formatter
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Rfmt {
|
||||
/// cargo fmt command
|
||||
Cargo,
|
||||
///rustfmt command
|
||||
RustFmt
|
||||
}
|
||||
|
||||
/// Parse config's input into data type
|
||||
pub fn from_config<'de, D>(code: &str) -> std::result::Result<Rfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match code.as_ref() {
|
||||
RUST_FMT_CARGO => Ok(Rfmt::Cargo),
|
||||
RUST_FMT_RUSTFMT => Ok(Rfmt::RustFmt),
|
||||
_ => Err(D::Error::custom(format!(
|
||||
"{} {}",
|
||||
emoji::ERROR,
|
||||
style("Unknown Haskell formatter").bold().red(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of deserialization for Rust
|
||||
pub fn deserialize_rust<'de, D>(deserializer: D) -> std::result::Result<Rfmt, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
from_config::<D>(&s)
|
||||
}
|
59
src/lib.rs
59
src/lib.rs
@ -21,13 +21,13 @@ pub mod command;
|
||||
pub mod customlog;
|
||||
pub mod emoji;
|
||||
pub mod formatters;
|
||||
// pub mod license;
|
||||
// pub mod lockfile;
|
||||
|
||||
use command::run_all_fmt;
|
||||
use formatters::Root;
|
||||
use formatters::tool::{check_fmt, glob_to_path, run_fmt, Root};
|
||||
use glob::Paths;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
use std::vec;
|
||||
|
||||
use customlog::{CustomLogOutput, LogLevel};
|
||||
|
||||
@ -69,20 +69,63 @@ pub fn run_cli(cli: Cli) -> anyhow::Result<()> {
|
||||
None => PathBuf::from("."),
|
||||
};
|
||||
|
||||
let fmt_toml = cwd.clone().as_path().join("fmt.toml");
|
||||
let fmt_toml = cwd.as_path().join("fmt.toml");
|
||||
|
||||
if let true = fmt_toml.as_path().exists() {
|
||||
println!("found fmt.toml");
|
||||
println!("set path to {}/", cwd.to_str().unwrap_or(""));
|
||||
println!("set path to {}", cwd.to_str().unwrap_or(""));
|
||||
let open_file = match read_to_string(fmt_toml.as_path()) {
|
||||
Ok(file) => file,
|
||||
Err(err) => return Err(anyhow::Error::new(err)),
|
||||
};
|
||||
let toml_content: Root = toml::from_str(&open_file)?;
|
||||
println!("{:#?}", toml_content);
|
||||
// let rustdir = toml_content.rustfmt
|
||||
// .and_then(|config| config.includes).unwrap_or(Vec::new());
|
||||
|
||||
let commands = toml_content
|
||||
.formatters
|
||||
.values()
|
||||
.map(|c| c.command.clone().unwrap_or("".into()));
|
||||
|
||||
let args = toml_content.formatters.values().map(|c| {
|
||||
let arg = match c.args.clone() {
|
||||
Some(vstr) => vstr,
|
||||
None => Vec::new(),
|
||||
};
|
||||
arg
|
||||
});
|
||||
|
||||
let all_files = toml_content
|
||||
.formatters
|
||||
.values()
|
||||
.map(|f| f.files.clone())
|
||||
.filter_map(|glob| glob_to_path(cwd.clone(), glob).ok());
|
||||
|
||||
for cmd in commands.clone() {
|
||||
check_fmt(cmd)?;
|
||||
}
|
||||
|
||||
let cmd_args = commands.zip(args).zip(all_files);
|
||||
|
||||
// TODO: implement `filtered_files: Vec<Paths>` for `[includes]` and `[exclude]`
|
||||
println!("===========================");
|
||||
for ((cmd, args), paths) in cmd_args.clone() {
|
||||
println!("Command: {}", cmd);
|
||||
println!("Files:");
|
||||
for f in paths {
|
||||
println!(" - {}", f?.display());
|
||||
}
|
||||
println!("===========================");
|
||||
}
|
||||
|
||||
for ((cmd, args), paths) in cmd_args.clone() {
|
||||
for f in paths {
|
||||
let path = f?;
|
||||
run_fmt(&cmd.clone(), &args, path)?
|
||||
}
|
||||
}
|
||||
|
||||
println!("Format successful");
|
||||
|
||||
// .and_then(|config| config.includes).unwrap_or(Vec::new());
|
||||
// for dir in rustdir {
|
||||
// cwd.push(Path::new(&dir));
|
||||
// run_rustfmt(Mode::Overwrite, cwd.clone())?;
|
||||
|
Loading…
Reference in New Issue
Block a user