mirror of
https://github.com/numtide/treefmt.git
synced 2024-09-11 17:07:44 +03:00
Merge pull request #4 from numtide/list-and-format
first working example: List directory and format files
This commit is contained in:
commit
044fdbb03e
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -39,7 +39,6 @@ dependencies = [
|
||||
"log",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"structopt",
|
||||
"toml",
|
||||
@ -596,6 +595,9 @@ name = "serde"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
|
@ -17,8 +17,7 @@ glob = "0.3"
|
||||
human-panic = "1.0"
|
||||
log = "0.4"
|
||||
parking_lot = "0.11"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
structopt = "0.3"
|
||||
toml = "0.5"
|
||||
|
14
devshell.nix
14
devshell.nix
@ -10,7 +10,7 @@ mkDevShell {
|
||||
motd = ''
|
||||
Welcome to the allfmt development environment.
|
||||
'';
|
||||
env = { };
|
||||
commands = [ ];
|
||||
|
||||
bash = {
|
||||
extra = ''
|
||||
@ -20,22 +20,20 @@ mkDevShell {
|
||||
'';
|
||||
};
|
||||
|
||||
commands = [ ];
|
||||
env = { };
|
||||
|
||||
packages = [
|
||||
# Build tools
|
||||
allfmt.rust
|
||||
binutils
|
||||
gcc
|
||||
openssl
|
||||
openssl.dev
|
||||
pkgconfig
|
||||
zlib
|
||||
|
||||
# Code formatters
|
||||
haskellPackages.ormolu
|
||||
haskellPackages.cabal-install
|
||||
haskellPackages.ghc
|
||||
nixpkgs-fmt
|
||||
go
|
||||
# gopls
|
||||
# gopkgs
|
||||
# gocode
|
||||
];
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
# fmt is the universal code formatter - https://github.com/numtide/fmt
|
||||
[ormolu]
|
||||
includes = [ "haskell/" ]
|
||||
excludes = []
|
||||
[formatters.ormolu]
|
||||
files = "*.hs" # / "*.hs" / "Makefile" mandatory
|
||||
#includes = [ "haskell/" ] # "haskell-frontend/*.hs" treat it like whitelist, and haskell-frontend will be ignored. only if `includes` exists.
|
||||
#excludes = [] # blacklisted folder/files.
|
||||
command = "ormolu"
|
||||
args = [
|
||||
"--ghc-opt", "-XBangPatterns",
|
||||
"--ghc-opt", "-XPatternSynonyms",
|
||||
@ -10,7 +12,9 @@ args = [
|
||||
"--check-idempotence"
|
||||
]
|
||||
|
||||
[rustfmt]
|
||||
[formatters.cargo]
|
||||
files = [ "*.rs" ]
|
||||
includes = [ "rust/" ]
|
||||
excludes = []
|
||||
args = []
|
||||
command = "cargo"
|
||||
args = [ "fmt", "--" ]
|
5
examples/monorepo/haskell-frontend/CHANGELOG.md
Normal file
5
examples/monorepo/haskell-frontend/CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Revision history for haskell
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
4
examples/monorepo/haskell-frontend/Main.hs
Normal file
4
examples/monorepo/haskell-frontend/Main.hs
Normal file
@ -0,0 +1,4 @@
|
||||
module Main where
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn "Hello, Haskell!"
|
3
examples/monorepo/haskell-frontend/Setup.hs
Normal file
3
examples/monorepo/haskell-frontend/Setup.hs
Normal file
@ -0,0 +1,3 @@
|
||||
import Distribution.Simple
|
||||
|
||||
main = defaultMain
|
25
examples/monorepo/haskell-frontend/haskell-frontend.cabal
Normal file
25
examples/monorepo/haskell-frontend/haskell-frontend.cabal
Normal file
@ -0,0 +1,25 @@
|
||||
cabal-version: >=1.10
|
||||
-- Initial package description 'haskell.cabal' generated by 'cabal init'.
|
||||
-- For further documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: haskell-frontend
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
-- bug-reports:
|
||||
-- license:
|
||||
license-file: LICENSE
|
||||
author: Andika Demas Riyandi
|
||||
maintainer: andika.riyan@gmail.com
|
||||
-- copyright:
|
||||
-- category:
|
||||
build-type: Simple
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
executable haskell-frontend
|
||||
main-is: Main.hs
|
||||
-- other-modules:
|
||||
-- other-extensions:
|
||||
build-depends: base >=4.14 && <4.15
|
||||
-- hs-source-dirs:
|
||||
default-language: Haskell2010
|
@ -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
|
||||
|
7
fmt.toml
7
fmt.toml
@ -1,7 +0,0 @@
|
||||
# fmt is the universal code formatter - https://github.com/numtide/fmt
|
||||
[formats.<Language>]
|
||||
includes = [ "*.<language-extension>" ]
|
||||
excludes = []
|
||||
command = ""
|
||||
args = []
|
||||
|
5
src/formatters/mod.rs
Normal file
5
src/formatters/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
//! Functionality related to installing prebuilt binaries
|
||||
|
||||
#![deny(missing_docs)]
|
||||
/// Formatter utility
|
||||
pub mod tool;
|
128
src/formatters/tool.rs
Normal file
128
src/formatters/tool.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use crate::emoji;
|
||||
use console::style;
|
||||
|
||||
// use super::tools;
|
||||
use anyhow::{anyhow, Result};
|
||||
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;
|
||||
|
||||
// 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,7 +0,0 @@
|
||||
//! Functionality related to installing prebuilt binaries
|
||||
|
||||
mod tools;
|
||||
pub use self::tools::Tools;
|
||||
|
||||
// TODO: This module provides functions to install all formatter
|
||||
// that is not available in user's $PATH
|
@ -1,44 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
/// List of Haskell formatters that are available
|
||||
pub enum Hfmt {
|
||||
Brittany,
|
||||
Floskell,
|
||||
Fourmolu,
|
||||
Ormolu, // This will be the default Haskell formatter
|
||||
StylishHaskell,
|
||||
}
|
||||
|
||||
impl fmt::Display for Hfmt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
Hfmt::Brittany => "brittany",
|
||||
Hfmt::Floskell => "floskell",
|
||||
Hfmt::Fourmolu => "fourmolu",
|
||||
Hfmt::Ormolu => "ormolu",
|
||||
Hfmt::StylishHaskell => "stylish-haskell",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the set of formatter tools `allfmt` uses
|
||||
pub enum Tools {
|
||||
/// Haskell formatter tool
|
||||
HaskellFmts(Hfmt),
|
||||
/// gofmt tools
|
||||
GoFmt,
|
||||
/// rustfmt tools
|
||||
RustFmt,
|
||||
}
|
||||
|
||||
impl fmt::Display for Tools {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
Tools::HaskellFmts(hfmt) => format!("{}", hfmt),
|
||||
Tools::GoFmt => "gofmt".to_string(),
|
||||
Tools::RustFmt => "rustfmt".to_string(),
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
128
src/lib.rs
128
src/lib.rs
@ -13,23 +13,21 @@ extern crate chrono;
|
||||
extern crate curl;
|
||||
extern crate dialoguer;
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate walkdir;
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod command;
|
||||
pub mod customlog;
|
||||
pub mod emoji;
|
||||
pub mod install;
|
||||
// pub mod license;
|
||||
// pub mod lockfile;
|
||||
pub mod formatters;
|
||||
|
||||
use command::run_all_fmt;
|
||||
use serde_derive::Deserialize;
|
||||
use std::env;
|
||||
use std::fs::{read_to_string};
|
||||
use std::path::{Path, PathBuf};
|
||||
use xshell::{cmd, pushd, pushenv};
|
||||
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};
|
||||
|
||||
@ -66,81 +64,75 @@ pub fn run_cli(cli: Cli) -> anyhow::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cwd = match cli.files {
|
||||
let cwd = match cli.files {
|
||||
Some(path) => path,
|
||||
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))
|
||||
Err(err) => return Err(anyhow::Error::new(err)),
|
||||
};
|
||||
let toml_content: Root = toml::from_str(&open_file)?;
|
||||
let rustdir = toml_content.rustfmt
|
||||
.and_then(|config| config.includes).unwrap_or(Vec::new());
|
||||
|
||||
for dir in rustdir {
|
||||
cwd.push(Path::new(&dir));
|
||||
run_rustfmt(Mode::Overwrite, cwd.clone())?;
|
||||
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())?;
|
||||
// }
|
||||
} else {
|
||||
println!("file fmt.toml couldn't be found. Run `--init` to generate the default setting");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
struct Root {
|
||||
ormolu: Option<FmtConfig>,
|
||||
rustfmt: Option<FmtConfig>
|
||||
}
|
||||
|
||||
/// Config for each formatters
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FmtConfig {
|
||||
includes: Option<Vec<String>>,
|
||||
excludes: Option<Vec<String>>,
|
||||
command: Option<String>,
|
||||
args: Option<Vec<String>>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user