feat: bring over the formatter from gleam
This commit is contained in:
@@ -10,6 +10,7 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
aiken-lang = { path = "../lang", version = "0.0.24" }
|
||||
ignore = "0.4.18"
|
||||
miette = { version = "5.3.0", features = ["fancy"] }
|
||||
petgraph = "0.6.2"
|
||||
regex = "1.6.0"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
io,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use aiken_lang::{error::ParseError, tipo};
|
||||
use aiken_lang::{parser::error::ParseError, tipo};
|
||||
use miette::{Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode};
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -20,6 +20,12 @@ pub enum Error {
|
||||
#[error("file operation failed")]
|
||||
FileIo { error: io::Error, path: PathBuf },
|
||||
|
||||
#[error("source code incorrectly formatted")]
|
||||
Format { problem_files: Vec<Unformatted> },
|
||||
|
||||
#[error(transparent)]
|
||||
StandardIo(#[from] io::Error),
|
||||
|
||||
#[error("cyclical module imports")]
|
||||
ImportCycle { modules: Vec<String> },
|
||||
|
||||
@@ -64,6 +70,20 @@ impl Error {
|
||||
rest => eprintln!("Error: {:?}", rest),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_parse_errors(errs: Vec<ParseError>, path: &Path, src: &str) -> Self {
|
||||
let mut errors = Vec::with_capacity(errs.len());
|
||||
|
||||
for error in errs {
|
||||
errors.push(Error::Parse {
|
||||
path: path.into(),
|
||||
src: src.to_string(),
|
||||
error: error.into(),
|
||||
});
|
||||
}
|
||||
|
||||
Error::List(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Error {
|
||||
@@ -94,6 +114,8 @@ impl Diagnostic for Error {
|
||||
Error::List(_) => None,
|
||||
Error::Parse { .. } => Some(Box::new("aiken::parser")),
|
||||
Error::Type { .. } => Some(Box::new("aiken::typecheck")),
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Format { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +134,8 @@ impl Diagnostic for Error {
|
||||
Error::List(_) => None,
|
||||
Error::Parse { error, .. } => error.kind.help(),
|
||||
Error::Type { error, .. } => error.help(),
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Format { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +147,8 @@ impl Diagnostic for Error {
|
||||
Error::List(_) => None,
|
||||
Error::Parse { error, .. } => error.labels(),
|
||||
Error::Type { error, .. } => error.labels(),
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Format { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +160,8 @@ impl Diagnostic for Error {
|
||||
Error::List(_) => None,
|
||||
Error::Parse { src, .. } => Some(src),
|
||||
Error::Type { src, .. } => Some(src),
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Format { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,3 +224,11 @@ impl Debug for Warning {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Unformatted {
|
||||
pub source: PathBuf,
|
||||
pub destination: PathBuf,
|
||||
pub input: String,
|
||||
pub output: String,
|
||||
}
|
||||
|
||||
147
crates/project/src/format.rs
Normal file
147
crates/project/src/format.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use aiken_lang::{ast::ModuleKind, parser};
|
||||
|
||||
use crate::{
|
||||
error::{Error, Unformatted},
|
||||
is_aiken_path,
|
||||
};
|
||||
|
||||
pub fn run(stdin: bool, check: bool, files: Vec<String>) -> Result<(), Error> {
|
||||
if stdin {
|
||||
process_stdin(check)
|
||||
} else {
|
||||
process_files(check, files)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_stdin(check: bool) -> Result<(), Error> {
|
||||
let src = read_stdin()?;
|
||||
|
||||
let mut out = String::new();
|
||||
|
||||
let (module, extra) = parser::module(&src, ModuleKind::Lib)
|
||||
.map_err(|errs| Error::from_parse_errors(errs, Path::new("<stdin>"), &src))?;
|
||||
|
||||
aiken_lang::format::pretty(&mut out, module, extra, &src);
|
||||
|
||||
if !check {
|
||||
print!("{}", out);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if src != out {
|
||||
return Err(Error::Format {
|
||||
problem_files: vec![Unformatted {
|
||||
source: PathBuf::from("<standard input>"),
|
||||
destination: PathBuf::from("<standard output>"),
|
||||
input: src,
|
||||
output: out,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_files(check: bool, files: Vec<String>) -> Result<(), Error> {
|
||||
if check {
|
||||
check_files(files)
|
||||
} else {
|
||||
format_files(files)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_files(files: Vec<String>) -> Result<(), Error> {
|
||||
let problem_files = unformatted_files(files)?;
|
||||
|
||||
if problem_files.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Format { problem_files })
|
||||
}
|
||||
}
|
||||
|
||||
fn format_files(files: Vec<String>) -> Result<(), Error> {
|
||||
for file in unformatted_files(files)? {
|
||||
fs::write(file.destination, file.output)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unformatted_files(files: Vec<String>) -> Result<Vec<Unformatted>, Error> {
|
||||
let mut problem_files = Vec::with_capacity(files.len());
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for file_path in files {
|
||||
let path = PathBuf::from_str(&file_path).unwrap();
|
||||
|
||||
if path.is_dir() {
|
||||
for path in aiken_files_excluding_gitignore(&path) {
|
||||
if let Err(err) = format_file(&mut problem_files, path) {
|
||||
errors.push(err);
|
||||
};
|
||||
}
|
||||
} else if let Err(err) = format_file(&mut problem_files, path) {
|
||||
println!("{:?}", err);
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(problem_files)
|
||||
} else {
|
||||
Err(Error::List(errors))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_file(problem_files: &mut Vec<Unformatted>, path: PathBuf) -> Result<(), Error> {
|
||||
let src = fs::read_to_string(&path).map_err(|error| Error::FileIo {
|
||||
error,
|
||||
path: path.clone(),
|
||||
})?;
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
let (module, extra) = parser::module(&src, ModuleKind::Lib)
|
||||
.map_err(|errs| Error::from_parse_errors(errs, &path, &src))?;
|
||||
|
||||
aiken_lang::format::pretty(&mut output, module, extra, &src);
|
||||
|
||||
if src != output {
|
||||
problem_files.push(Unformatted {
|
||||
source: path.clone(),
|
||||
destination: path,
|
||||
input: src,
|
||||
output,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_stdin() -> Result<String, Error> {
|
||||
let mut src = String::new();
|
||||
|
||||
std::io::stdin().read_to_string(&mut src)?;
|
||||
|
||||
Ok(src)
|
||||
}
|
||||
|
||||
pub fn aiken_files_excluding_gitignore(dir: &Path) -> impl Iterator<Item = PathBuf> + '_ {
|
||||
ignore::WalkBuilder::new(dir)
|
||||
.follow_links(true)
|
||||
.require_git(false)
|
||||
.build()
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
|
||||
.map(ignore::DirEntry::into_path)
|
||||
.filter(move |d| is_aiken_path(d, dir))
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod format;
|
||||
pub mod module;
|
||||
|
||||
use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator};
|
||||
@@ -106,8 +107,8 @@ impl Project {
|
||||
kind,
|
||||
} in self.sources.drain(0..)
|
||||
{
|
||||
match aiken_lang::parser::script(&code) {
|
||||
Ok(mut ast) => {
|
||||
match aiken_lang::parser::module(&code, kind) {
|
||||
Ok((mut ast, _)) => {
|
||||
// Store the name
|
||||
ast.name = name.clone();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user