Merge pull request #4 from numtide/list-and-format

first working example: List directory and format files
This commit is contained in:
Andika Demas Riyandi 2021-01-11 22:06:02 +07:00 committed by GitHub
commit 044fdbb03e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 252 additions and 143 deletions

4
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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
];
}

View File

@ -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", "--" ]

View File

@ -0,0 +1,5 @@
# Revision history for haskell
## 0.1.0.0 -- YYYY-mm-dd
* First version. Released on an unsuspecting world.

View File

@ -0,0 +1,4 @@
module Main where
main :: IO ()
main = putStrLn "Hello, Haskell!"

View File

@ -0,0 +1,3 @@
import Distribution.Simple
main = defaultMain

View 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

View File

@ -1,4 +1,5 @@
module Main where
main :: IO ()
main = putStrLn "Hello, Haskell!"
main =
putStrLn "Hello, Haskell!"

View File

@ -1,2 +1,3 @@
import Distribution.Simple
main = defaultMain

View File

@ -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
View 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
View 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>),
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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>>
}