first working example

This commit is contained in:
Andika Demas Riyandi 2021-01-11 15:12:16 +07:00
parent 431a174b9d
commit d40f78417a
12 changed files with 187 additions and 248 deletions

View File

@ -31,5 +31,9 @@ mkDevShell {
haskellPackages.cabal-install
haskellPackages.ghc
nixpkgs-fmt
go
# gopls
# gopkgs
# gocode
];
}

View File

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

View File

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

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,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;

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
/// Go formatters
pub mod go;
/// Haskell formatters
pub mod haskell;
/// Rust formatters
pub mod rust;

View File

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

View File

@ -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())?;