Only use colors & text decorations on ANSI-capable terminals.

Fixes #404.
This commit is contained in:
KtorZ 2023-02-26 13:19:03 +01:00
parent 2f2be39813
commit a46a9fca41
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
16 changed files with 593 additions and 261 deletions

3
Cargo.lock generated vendored
View File

@ -1488,6 +1488,9 @@ name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
dependencies = [
"supports-color",
]
[[package]]
name = "pallas"

View File

@ -18,7 +18,7 @@ indoc = "1.0.7"
itertools = "0.10.5"
miette = "5.2.0"
ordinal = "0.3.2"
owo-colors = "3.5.0"
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
strum = "0.24.1"
thiserror = "1.0.37"
uplc = { path = '../uplc', version = "0.0.29" }

View File

@ -7,7 +7,7 @@ use crate::{
tipo::{PatternConstructor, Type, TypeInfo},
};
use miette::Diagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
pub const ASSERT_VARIABLE: &str = "_try";
pub const CAPTURE_VARIABLE: &str = "_capture";
@ -1190,8 +1190,8 @@ impl chumsky::Span for Span {
pub enum Error {
#[error(
"I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
name.purple(),
keyword.purple()
name.if_supports_color(Stdout, |s| s.purple()),
keyword.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
#[diagnostic(code("illegal::module_name"))]
@ -1200,7 +1200,9 @@ pub enum Error {
as, expect, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
KeywordInModuleName { name: String, keyword: String },
#[error("I realized you used '{}' as a module name, which is reserved (and not available).\n", name.purple())]
#[error("I realized you used '{}' as a module name, which is reserved (and not available).\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("illegal::module_name"))]
#[diagnostic(help(r#"Some module names are reserved for internal use. This the case of:

View File

@ -1,7 +1,7 @@
use crate::{ast::Span, parser::token::Token};
use indoc::formatdoc;
use miette::Diagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
use std::collections::HashSet;
#[derive(Debug, Diagnostic, thiserror::Error)]
@ -123,8 +123,12 @@ pub enum ErrorKind {
{} my_policy_id {}
#{}
"#
, "pub const".bright_blue(), "=".yellow(), "\"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50\"".bright_purple()}))]
"#,
"pub const".if_supports_color(Stdout, |s| s.bright_blue()),
"=".if_supports_color(Stdout, |s| s.yellow()),
"\"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50\""
.if_supports_color(Stdout, |s| s.bright_purple())
}))]
MalformedBase16StringLiteral,
#[error("I failed to understand a when clause guard.")]
@ -143,10 +147,10 @@ pub enum ErrorKind {
{bad} (x, _) if x % 3 == 0 -> ...
{bad} (x, y) if x + y > 42 -> ...
"#
, operator_or = "||".yellow()
, operator_and = "&&".yellow()
, good = "✔️".green()
, bad = "✖️".red()
, operator_or = "||".if_supports_color(Stdout, |s| s.yellow())
, operator_and = "&&".if_supports_color(Stdout, |s| s.yellow())
, good = "✔️".if_supports_color(Stdout, |s| s.green())
, bad = "✖️".if_supports_color(Stdout, |s| s.red())
}))]
InvalidWhenClause,
}

View File

@ -9,7 +9,10 @@ use crate::{
use indoc::formatdoc;
use miette::{Diagnostic, LabeledSpan};
use ordinal::Ordinal;
use owo_colors::OwoColorize;
use owo_colors::{
OwoColorize,
Stream::{Stderr, Stdout},
};
use std::{collections::HashMap, fmt::Display, sync::Arc};
#[derive(Debug, thiserror::Error, Diagnostic)]
@ -37,7 +40,7 @@ impl Diagnostic for UnknownLabels {
{known_labels}"#
, known_labels = self.valid
.iter()
.map(|s| format!("─▶ {}", s.yellow()))
.map(|s| format!("─▶ {}", s.if_supports_color(Stdout, |s| s.yellow())))
.collect::<Vec<_>>()
.join("\n")
}))
@ -85,10 +88,14 @@ pub enum Error {
errors: Vec<Snippet>,
},
#[error("I found two function arguments both called '{}'.\n", label.purple())]
#[error(
"I found two function arguments both called '{}'.\n",
label.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("duplicate::argument"))]
#[diagnostic(help("Function arguments cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names."
, discard = "_".yellow()
#[diagnostic(help(
"Function arguments cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names.",
discard = "_".if_supports_color(Stdout, |s| s.yellow())
))]
DuplicateArgument {
#[label]
@ -100,8 +107,9 @@ pub enum Error {
#[error("I found two declarations for the constant '{}'.\n", name.purple())]
#[diagnostic(code("duplicate::constant"))]
#[diagnostic(help("Top-level constants of a same module cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names."
, discard = "_".yellow()
#[diagnostic(help(
"Top-level constants of a same module cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names.",
discard = "_".if_supports_color(Stdout, |s| s.yellow())
))]
DuplicateConstName {
#[label("declared again here")]
@ -111,7 +119,10 @@ pub enum Error {
name: String,
},
#[error("I stumbled upon the field '{}' twice in a data-type definition.\n", label.purple())]
#[error(
"I stumbled upon the field '{}' twice in a data-type definition.\n",
label.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("duplicate::field"))]
#[diagnostic(help(r#"Data-types must have fields with strictly different names. You can use '{discard}' and numbers to distinguish between similar names.
Note that it is also possible to declare data-types with positional (nameless) fields only.
@ -123,12 +134,12 @@ For example:
{variant_Point}({type_Int}, {type_Int}, {type_Int})
}}
"#
, discard = "_".yellow()
, keyword_pub = "pub".bright_blue()
, keyword_type = "type".yellow()
, type_Int = "Int".green()
, type_Point = "Point".green()
, variant_Point = "Point".green()
, discard = "_".if_supports_color(Stdout, |s| s.yellow())
, keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_type = "type".if_supports_color(Stdout, |s| s.yellow())
, type_Int = "Int".if_supports_color(Stdout, |s| s.green())
, type_Point = "Point".if_supports_color(Stdout, |s| s.green())
, variant_Point = "Point".if_supports_color(Stdout, |s| s.green())
))]
DuplicateField {
#[label]
@ -138,7 +149,10 @@ For example:
label: String,
},
#[error("I noticed you were importing '{}' twice.\n", name.purple())]
#[error(
"I noticed you were importing '{}' twice.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("duplicate::import"))]
#[diagnostic(help(r#"If you're trying to import two modules with identical names but from different packages, you'll need to use a named import.
For example:
@ -146,9 +160,13 @@ For example:
{keyword_use} {import} {keyword_as} {named}
Otherwise, just remove the redundant import."#
, keyword_use = "use".bright_blue()
, keyword_as = "as".bright_blue()
, import = module.iter().map(|x| x.purple().bold().to_string()).collect::<Vec<_>>().join("/".bold().to_string().as_ref())
, keyword_use = "use".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_as = "as".if_supports_color(Stdout, |s| s.bright_blue())
, import = module
.iter()
.map(|x| x.if_supports_color(Stdout, |s| s.purple()).to_string())
.collect::<Vec<_>>()
.join("/".if_supports_color(Stdout, |s| s.bold()).to_string().as_ref())
, named = module.join("_")
))]
DuplicateImport {
@ -160,13 +178,17 @@ Otherwise, just remove the redundant import."#
previous_location: Span,
},
#[error("I discovered two top-level objects referred to as '{}'.\n", name.purple())]
#[error(
"I discovered two top-level objects referred to as '{}'.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("duplicate::name"))]
#[diagnostic(help(r#"Top-level definitions cannot have the same name, even if they refer to objects with different natures (e.g. function and test).
#[diagnostic(help(
r#"Top-level definitions cannot have the same name, even if they refer to objects with different natures (e.g. function and test).
You can use '{discard}' and numbers to distinguish between similar names.
"#
, discard = "_".yellow()
"#,
discard = "_".if_supports_color(Stdout, |s| s.yellow())
))]
DuplicateName {
#[label]
@ -176,10 +198,14 @@ You can use '{discard}' and numbers to distinguish between similar names.
name: String,
},
#[error("I found two types declared with the same name: '{}'.\n", name.purple())]
#[error(
"I found two types declared with the same name: '{}'.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("duplicate::type"))]
#[diagnostic(help("Types cannot have the same top-level name. You {cannot} use '_' in types name, but you can use numbers to distinguish between similar names."
, cannot = "cannot".red()
#[diagnostic(help(
"Types cannot have the same top-level name. You {cannot} use '_' in types name, but you can use numbers to distinguish between similar names.",
cannot = "cannot".if_supports_color(Stdout, |s| s.red())
))]
DuplicateTypeName {
#[label]
@ -189,7 +215,10 @@ You can use '{discard}' and numbers to distinguish between similar names.
name: String,
},
#[error("I realized the variable '{}' was mentioned more than once in an alternative pattern.\n", name.purple())]
#[error(
"I realized the variable '{}' was mentioned more than once in an alternative pattern.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
@ -200,7 +229,10 @@ You can use '{discard}' and numbers to distinguish between similar names.
name: String,
},
#[error("I tripped over an extra variable in an alternative pattern: {}.\n", name.purple())]
#[error(
"I tripped over an extra variable in an alternative pattern: {}.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
@ -221,16 +253,21 @@ You can use '{discard}' and numbers to distinguish between similar names.
#[error("I found a discarded expression not bound to a variable.\n")]
#[diagnostic(code("implicit_discard"))]
#[diagnostic(help("A function can contain a sequence of expressions. However, any expression but the last one must be assign to a variable using the {keyword_let} keyword. If you really wish to discard an expression that is unused, you can assign it to '{discard}'."
, keyword_let = "let".yellow()
, discard = "_".yellow()
#[diagnostic(help(
"A function can contain a sequence of expressions. However, any expression but the last one must be assign to a variable using the {keyword_let} keyword. If you really wish to discard an expression that is unused, you can assign it to '{discard}'.",
keyword_let = "let".if_supports_color(Stdout, |s| s.yellow()),
discard = "_".if_supports_color(Stdout, |s| s.yellow())
))]
ImplicitlyDiscardedExpression {
#[label]
location: Span,
},
#[error("I saw a {} fields in a context where there should be {}.\n", given.purple(), expected.purple())]
#[error(
"I saw a {} fields in a context where there should be {}.\n",
given.if_supports_color(Stdout, |s| s.purple()),
expected.if_supports_color(Stdout, |s| s.purple()),
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types"))]
#[diagnostic(code("arity::constructor"))]
IncorrectFieldsArity {
@ -241,7 +278,11 @@ You can use '{discard}' and numbers to distinguish between similar names.
labels: Vec<String>,
},
#[error("I saw a function or constructor that expects {} arguments be called with {} arguments.\n", expected.purple(), given.purple())]
#[error(
"I saw a function or constructor that expects {} arguments be called with {} arguments.\n",
expected.if_supports_color(Stdout, |s| s.purple()),
given.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
#[diagnostic(code("arity::invoke"))]
#[diagnostic(help(r#"Functions (and constructors) must always be called with all their arguments (comma-separated, between brackets).
@ -260,11 +301,11 @@ From there, you can define 'increment', a function that takes a single argument
{keyword_let} increment = add(1, _)
"#
, discard = "_".yellow()
, expected = expected.purple()
, keyword_fn = "fn".yellow()
, keyword_let = "let".yellow()
, type_Int = "Int".green()
, discard = "_".if_supports_color(Stdout, |s| s.yellow())
, expected = expected.if_supports_color(Stdout, |s| s.purple())
, keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_let = "let".if_supports_color(Stdout, |s| s.yellow())
, type_Int = "Int".if_supports_color(Stdout, |s| s.green())
))]
IncorrectFunctionCallArity {
#[label]
@ -276,7 +317,11 @@ From there, you can define 'increment', a function that takes a single argument
// TODO: Since we do not actually support patterns on multiple items, we won't likely ever
// encounter that error. We could simplify a bit the type-checker and get rid of that error
// eventually.
#[error("I counted {} different clauses in a multi-pattern instead of {}.\n", given.purple(), expected.purple())]
#[error(
"I counted {} different clauses in a multi-pattern instead of {}.\n",
given.if_supports_color(Stdout, |s| s.purple()),
expected.if_supports_color(Stdout, |s| s.purple()),
)]
#[diagnostic(code("arity::clause"))]
IncorrectNumClausePatterns {
#[label]
@ -285,12 +330,17 @@ From there, you can define 'increment', a function that takes a single argument
given: usize,
},
#[error("I saw a pattern on a constructor that has {} field(s) be matched with {} argument(s).\n", expected.purple(), given.len().purple())]
#[error(
"I saw a pattern on a constructor that has {} field(s) be matched with {} argument(s).\n",
expected.if_supports_color(Stdout, |s| s.purple()),
given.len().if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
#[diagnostic(code("arity::pattern"))]
#[diagnostic(help("When pattern-matching on constructors, you must either match the exact number of fields, or use the spread operator '{spread}'. Note that unused fields must be discarded by prefixing their name with '{discard}'."
, discard = "_".yellow()
, spread = "..".yellow()
#[diagnostic(help(
"When pattern-matching on constructors, you must either match the exact number of fields, or use the spread operator '{spread}'. Note that unused fields must be discarded by prefixing their name with '{discard}'.",
discard = "_".if_supports_color(Stdout, |s| s.yellow()),
spread = "..".if_supports_color(Stdout, |s| s.yellow()),
))]
IncorrectPatternArity {
#[label("{}", suggest_pattern(*expected, name, given, module, *is_record).unwrap_or_default())]
@ -302,11 +352,16 @@ From there, you can define 'increment', a function that takes a single argument
is_record: bool,
},
#[error("I saw a pattern on a {}-tuple be matched into a {}-tuple.\n", expected.purple(), given.purple())]
#[error(
"I saw a pattern on a {}-tuple be matched into a {}-tuple.\n",
expected.if_supports_color(Stdout, |s| s.purple()),
given.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))]
#[diagnostic(code("arity::tuple"))]
#[diagnostic(help("When pattern matching on a tuple, you must match all of its elements. Note that unused fields must be discarded by prefixing their name with '{discard}'."
, discard = "_".yellow()
#[diagnostic(help(
"When pattern matching on a tuple, you must match all of its elements. Note that unused fields must be discarded by prefixing their name with '{discard}'.",
discard = "_".if_supports_color(Stdout, |s| s.yellow())
))]
IncorrectTupleArity {
#[label]
@ -315,7 +370,11 @@ From there, you can define 'increment', a function that takes a single argument
given: usize,
},
#[error("I noticed a generic data-type with {} type parameters instead of {}.\n", given.purple(), expected.purple())]
#[error(
"I noticed a generic data-type with {} type parameters instead of {}.\n",
given.if_supports_color(Stdout, |s| s.purple()),
expected.if_supports_color(Stdout, |s| s.purple()),
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#generics"))]
#[diagnostic(code("arity::generic"))]
#[diagnostic(help(r#"Data-types that are generic in one or more types must be written with all their generic types in type annotations. Generic types must be indicated between chevrons '{chevron_left}' and '{chevron_right}'.
@ -323,8 +382,8 @@ From there, you can define 'increment', a function that takes a single argument
Perhaps, try the following:
{suggestion}"#
, chevron_left = "<".yellow()
, chevron_right = ">".yellow()
, chevron_left = "<".if_supports_color(Stdout, |s| s.yellow())
, chevron_right = ">".if_supports_color(Stdout, |s| s.yellow())
, suggestion = suggest_generic(name, *expected)
))]
IncorrectTypeArity {
@ -337,8 +396,8 @@ Perhaps, try the following:
#[error(
"I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
name.purple(),
keyword.purple()
name.if_supports_color(Stdout, |s| s.purple()),
keyword.if_supports_color(Stdout, |s| s.purple()),
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
#[diagnostic(code("illegal::module_name"))]
@ -363,7 +422,10 @@ If you really meant to return that last expression, try to replace it with the f
expr: expr::UntypedExpr,
},
#[error("I found a missing variable in an alternative pattern: {}.\n", name.purple())]
#[error(
"I found a missing variable in an alternative pattern: {}.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
@ -374,7 +436,10 @@ If you really meant to return that last expression, try to replace it with the f
name: String,
},
#[error("I stumbled upon an invalid (non-local) clause guard '{}'.\n", name.purple())]
#[error(
"I stumbled upon an invalid (non-local) clause guard '{}'.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
#[diagnostic(code("illegal::clause_guard"))]
#[diagnostic(help("There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."))]
@ -389,10 +454,11 @@ If you really meant to return that last expression, try to replace it with the f
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("illegal::tuple_index"))]
#[diagnostic(help(r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found:
#[diagnostic(help(
r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found:
{type_info}"#
, type_info = tipo.to_pretty(4).red()
{type_info}"#,
type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red())
))]
NotATuple {
#[label]
@ -405,8 +471,8 @@ If you really meant to return that last expression, try to replace it with the f
} else {
format!(
"I realized that a given '{keyword_when}/{keyword_is}' expression is non-exhaustive.",
keyword_is = "is".purple(),
keyword_when = "when".purple()
keyword_is = "is".if_supports_color(Stdout, |s| s.purple()),
keyword_when = "when".if_supports_color(Stdout, |s| s.purple())
)
}
)]
@ -417,9 +483,9 @@ If you really meant to return that last expression, try to replace it with the f
In this particular instance, the following cases are unmatched:
{missing}"#
, discard = "_".yellow()
, keyword_is = "is".purple()
, keyword_when = "when".purple()
, discard = "_".if_supports_color(Stdout, |s| s.yellow())
, keyword_is = "is".if_supports_color(Stdout, |s| s.purple())
, keyword_when = "when".if_supports_color(Stdout, |s| s.purple())
, missing = unmatched
.iter()
.map(|s| format!("─▶ {s}"))
@ -435,10 +501,11 @@ In this particular instance, the following cases are unmatched:
#[error("I tripped over a call attempt on something that isn't a function.\n")]
#[diagnostic(code("illegal::invoke"))]
#[diagnostic(help(r#"It seems like you're trying to call something that isn't a function. I am inferring the following type:
#[diagnostic(help(
r#"It seems like you're trying to call something that isn't a function. I am inferring the following type:
{inference}"#
, inference = tipo.to_pretty(4)
{inference}"#,
inference = tipo.to_pretty(0)
))]
NotFn {
#[label]
@ -468,8 +535,8 @@ The culprit is:
{type_info}
Maybe you meant to turn it public using the '{keyword_pub}' keyword?"#
, type_info = leaked.to_pretty(4).red()
, keyword_pub = "pub".bright_blue()
, type_info = leaked.to_pretty(4).if_supports_color(Stdout, |s| s.red())
, keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
))]
PrivateTypeLeak {
#[label]
@ -495,9 +562,9 @@ You can help me by providing a type-annotation for 'x', as such:
{keyword_let} foo = {keyword_fn}(x: {type_ScriptContext}) {{ x.transaction }}
"#
, keyword_fn = "fn".yellow()
, keyword_let = "let".yellow()
, type_ScriptContext = "ScriptContext".green()
, keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_let = "let".if_supports_color(Stdout, |s| s.yellow())
, type_ScriptContext = "ScriptContext".if_supports_color(Stdout, |s| s.green())
))]
RecordAccessUnknownType {
#[label]
@ -523,8 +590,8 @@ You can help me by providing a type-annotation for 'x', as such:
#[error(
"I discovered an attempt to access the {} element of a {}-tuple.\n",
Ordinal(*index + 1).to_string().purple(),
size.purple()
Ordinal(*index + 1).to_string().if_supports_color(Stdout, |s| s.purple()),
size.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("invalid::tuple_index"))]
@ -535,7 +602,10 @@ You can help me by providing a type-annotation for 'x', as such:
size: usize,
},
#[error("I tripped over the following labeled argument: {}.\n", label.purple())]
#[error(
"I tripped over the following labeled argument: {}.\n",
label.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))]
#[diagnostic(code("unexpected::module_name"))]
UnexpectedLabeledArg {
@ -544,7 +614,10 @@ You can help me by providing a type-annotation for 'x', as such:
label: String,
},
#[error("I tripped over the following labeled argument: {}.\n", label.purple())]
#[error(
"I tripped over the following labeled argument: {}.\n",
label.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#named-accessors"))]
#[diagnostic(code("unexpected::labeled_argument"))]
#[diagnostic(help(r#"The constructor '{constructor}' does not have any labeled field. Its fields must therefore be matched only by position.
@ -553,7 +626,9 @@ Perhaps, try the following:
{suggestion}
"#
, constructor = name.green()
, constructor = name
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, suggestion = suggest_constructor_pattern(name, args, module, *with_spread)
))]
UnexpectedLabeledArgInPattern {
@ -579,7 +654,10 @@ Perhaps, try the following:
#[diagnostic(code("unknown::labels"))]
UnknownLabels(#[related] Vec<UnknownLabels>),
#[error("I stumbled upon a reference to an unknown module: '{}'\n", name.purple())]
#[error(
"I stumbled upon a reference to an unknown module: '{}'\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::module"))]
#[diagnostic(help(
"{}",
@ -594,8 +672,8 @@ Perhaps, try the following:
#[error(
"I found an unknown import '{}' from module '{}'.\n",
name.purple(),
module_name.purple()
name.if_supports_color(Stdout, |s| s.purple()),
module_name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::module_field"))]
#[diagnostic(help(
@ -615,7 +693,11 @@ Perhaps, try the following:
type_constructors: Vec<String>,
},
#[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())]
#[error(
"I looked for '{}' in '{}' but couldn't find it.\n",
name.if_supports_color(Stdout, |s| s.purple()),
module_name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::module_type"))]
#[diagnostic(help(
"{}",
@ -633,7 +715,10 @@ Perhaps, try the following:
type_constructors: Vec<String>,
},
#[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())]
#[error("I looked for '{}' in '{}' but couldn't find it.\n",
name.if_supports_color(Stdout, |s| s.purple()),
module_name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::module_value"))]
#[diagnostic(help(
"{}",
@ -652,14 +737,14 @@ Perhaps, try the following:
},
#[error(
"I looked for the field '{}' in a record of type '{}' couldn't find it.\n",
label.purple(),
typ.to_pretty(4).purple()
"I looked for the field '{}' in a record of type '{}' but couldn't find it.\n",
label.if_supports_color(Stdout, |s| s.purple()),
typ.to_pretty(0).if_supports_color(Stdout, |s| s.purple()),
)]
#[diagnostic(code("unknown::record_field"))]
#[diagnostic(help(
"{}",
suggest_neighbor(label, fields.iter(), "Did you forget to make the it public?")
suggest_neighbor(label, fields.iter(), "Did you forget to make it public?")
))]
UnknownRecordField {
#[label]
@ -683,7 +768,10 @@ Perhaps, try the following:
types: Vec<String>,
},
#[error("I found a reference to an unknown data-type constructor: '{}'.\n", name.purple())]
#[error(
"I found a reference to an unknown data-type constructor: '{}'.\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::type_constructor"))]
#[diagnostic(help(
"{}",
@ -737,24 +825,37 @@ The best thing to do from here is to remove it."#))]
location: Span,
},
#[error("I discovered an attempt to import a validator module: '{}'\n", name.purple())]
#[error(
"I discovered an attempt to import a validator module: '{}'\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("illegal::import"))]
#[diagnostic(help("If you are trying to share code defined in a validator then move it to a library module under {}", "lib/".purple()))]
#[diagnostic(help(
"If you are trying to share code defined in a validator then move it to a library module under {}",
"lib/".if_supports_color(Stdout, |s| s.purple()))
)]
ValidatorImported {
#[label]
location: Span,
name: String,
},
#[error("A validator must return {}", "Bool".bright_blue().bold())]
#[error(
"A validator must return {}",
"Bool"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
)]
#[diagnostic(code("illegal::validator_return_type"))]
#[diagnostic(help(r#"While analyzing the return type of your validator, I found it to be:
{signature}
...but I expected this to be a {type_Bool}. If I am inferring the wrong type, try annotating the validator's return type with Bool"#
, type_Bool = "Bool".bright_blue().bold()
, signature = return_type.to_pretty(0).red()
, type_Bool = "Bool"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, signature = return_type.to_pretty(0).if_supports_color(Stdout, |s| s.red())
))]
ValidatorMustReturnBool {
#[label("invalid return type")]
@ -775,7 +876,10 @@ The best thing to do from here is to remove it."#))]
arguments.push('s');
}
format!("please add the {} missing {arguments}", missing.to_string().yellow())
format!(
"please add the {} missing {arguments}",
missing.to_string().if_supports_color(Stdout, |s| s.yellow()),
)
} else {
let extra = count - 3;
@ -785,7 +889,10 @@ The best thing to do from here is to remove it."#))]
arguments.push('s');
}
format!("please remove the {} extra {arguments}", extra.to_string().yellow())
format!(
"please remove the {} extra {arguments}",
extra.to_string().if_supports_color(Stdout, |s| s.yellow()),
)
}
))]
IncorrectValidatorArity {
@ -881,7 +988,10 @@ fn suggest_neighbor<'a>(
.min_by(|(_, a), (_, b)| a.cmp(b))
.and_then(|(suggestion, distance)| {
if distance <= threshold {
Some(format!("Did you mean '{}'?", suggestion.yellow()))
Some(format!(
"Did you mean '{}'?",
suggestion.if_supports_color(Stdout, |s| s.yellow())
))
} else {
None
}
@ -962,10 +1072,10 @@ fn suggest_unify(
{given}
Note that I infer the type of the entire '{keyword_when}/{keyword_is}' expression based on the type of the first branch I encounter."#,
keyword_when = "when".yellow(),
keyword_is = "is".yellow(),
expected = expected.green(),
given = given.red()
keyword_when = "when".if_supports_color(Stdout, |s| s.yellow()),
keyword_is = "is".if_supports_color(Stdout, |s| s.yellow()),
expected = expected.if_supports_color(Stdout, |s| s.green()),
given = given.if_supports_color(Stdout, |s| s.red())
},
Some(UnifyErrorSituation::ReturnAnnotationMismatch) => formatdoc! {
r#"While comparing the return annotation of a function with its actual return type, I realized that both don't match.
@ -979,8 +1089,8 @@ fn suggest_unify(
{}
Either, fix the annotation or adjust the function body to return the expected type."#,
expected.green(),
given.red()
expected.if_supports_color(Stdout, |s| s.green()),
given.if_supports_color(Stdout, |s| s.red())
},
Some(UnifyErrorSituation::PipeTypeMismatch) => formatdoc! {
r#"As I was looking at a pipeline you have defined, I realized that one of the pipe isn't valid.
@ -994,8 +1104,8 @@ fn suggest_unify(
{}
Either, fix the input or change the target so that both match."#,
expected.green(),
given.red()
expected.if_supports_color(Stdout, |s| s.green()),
given.if_supports_color(Stdout, |s| s.red())
},
Some(UnifyErrorSituation::Operator(op)) => formatdoc! {
r#"While checking operands of a binary operator, I realized that at least one of them doesn't have the expected type.
@ -1008,9 +1118,9 @@ fn suggest_unify(
{}
"#,
op.to_doc().to_pretty_string(70).yellow(),
expected.green(),
given.red()
op.to_doc().to_pretty_string(70).if_supports_color(Stdout, |s| s.yellow()),
expected.if_supports_color(Stdout, |s| s.green()),
given.if_supports_color(Stdout, |s| s.red())
},
None => formatdoc! {
r#"I am inferring the following type:
@ -1022,8 +1132,8 @@ fn suggest_unify(
{}
Either, add type-annotation to improve my inference, or adjust the expression to have the expected type."#,
expected.green(),
given.red()
expected.if_supports_color(Stdout, |s| s.green()),
given.if_supports_color(Stdout, |s| s.red())
},
}
}
@ -1044,12 +1154,16 @@ fn suggest_make_public() -> String {
The function 'foo' is private and can't be accessed from outside of the 'aiken/foo' module. But the data-type '{type_Bar}' is public and available.
"#
, keyword_fn = "fn".yellow()
, keyword_pub = "pub".bright_blue()
, keyword_type = "type".yellow()
, literal_foo = "\"foo\"".bright_purple()
, type_Bar = "Bar".green()
, variant_Bar = "Bar".green()
, keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_type = "type".if_supports_color(Stdout, |s| s.bright_blue())
, literal_foo = "\"foo\"".if_supports_color(Stdout, |s| s.bright_purple())
, type_Bar = "Bar"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, variant_Bar = "Bar"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
}
}
@ -1077,15 +1191,21 @@ fn suggest_import_constructor() -> String {
}}
}}
"#
, keyword_fn = "fn".yellow()
, keyword_is = "is".yellow()
, keyword_pub = "pub".bright_blue()
, keyword_type = "type".yellow()
, keyword_use = "use".bright_blue()
, keyword_when = "when".yellow()
, type_Pet = "Pet".green()
, variant_Cat = "Cat".green()
, variant_Dog = "Dog".green()
, keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_is = "is".if_supports_color(Stdout, |s| s.yellow())
, keyword_pub = "pub".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_type = "type".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_use = "use".if_supports_color(Stdout, |s| s.bright_blue())
, keyword_when = "when".if_supports_color(Stdout, |s| s.yellow())
, type_Pet = "Pet"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, variant_Cat = "Cat"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, variant_Dog = "Dog"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
}
}
@ -1129,7 +1249,11 @@ pub enum Warning {
#[error("I found a when expression with a single clause.")]
#[diagnostic(
code("single_when_clause"),
help("Prefer using a {} binding like so...\n\n{}", "let".purple(), format_suggestion(sample))
help(
"Prefer using a {} binding like so...\n\n{}",
"let".if_supports_color(Stderr, |s| s.purple()),
format_suggestion(sample)
)
)]
SingleWhenClause {
#[label("use let")]
@ -1137,10 +1261,19 @@ pub enum Warning {
sample: UntypedExpr,
},
#[error("I found an {} trying to match a type with one constructor", "expect".purple())]
#[error(
"I found an {} trying to match a type with one constructor",
"expect".if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(
code("single_constructor_expect"),
help("If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}", "FROM".bold(), "Data".purple(), "let".purple(), format_suggestion(sample))
help(
"If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}",
"from".if_supports_color(Stderr, |s| s.bold()),
"Data".if_supports_color(Stderr, |s| s.purple()),
"let".if_supports_color(Stderr, |s| s.purple()),
format_suggestion(sample)
)
)]
SingleConstructorExpect {
#[label("use let")]
@ -1161,7 +1294,10 @@ pub enum Warning {
tipo: Arc<Type>,
},
#[error("I discovered an unused constructor: '{}'.\n", name.purple())]
#[error(
"I discovered an unused constructor: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning."
))]
@ -1173,7 +1309,10 @@ pub enum Warning {
name: String,
},
#[error("I discovered an unused imported module: '{}'.\n", name.purple())]
#[error(
"I discovered an unused imported module: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning."
))]
@ -1184,7 +1323,10 @@ pub enum Warning {
name: String,
},
#[error("I discovered an unused imported value: '{}'.\n", name.purple())]
#[error(
"I discovered an unused imported value: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple()),
)]
#[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning."
))]
@ -1202,11 +1344,14 @@ pub enum Warning {
location: Span,
},
#[error("I found an unused private function: '{}'.\n", name.purple())]
#[error(
"I found an unused private function: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple()),
)]
#[diagnostic(help(
"Perhaps your forgot to make it public using the '{keyword_pub}' keyword?\n\
Otherwise, you might want to get rid of it altogether."
, keyword_pub = "pub".bright_blue()
Otherwise, you might want to get rid of it altogether.",
keyword_pub = "pub".if_supports_color(Stderr, |s| s.bright_blue())
))]
#[diagnostic(code("unused::function"))]
UnusedPrivateFunction {
@ -1215,11 +1360,14 @@ pub enum Warning {
name: String,
},
#[error("I found an unused (private) module constant: '{}'.\n", name.purple())]
#[error(
"I found an unused (private) module constant: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(help(
"Perhaps your forgot to make it public using the '{keyword_pub}' keyword?\n\
Otherwise, you might want to get rid of it altogether."
, keyword_pub = "pub".bright_blue()
Otherwise, you might want to get rid of it altogether.",
keyword_pub = "pub".if_supports_color(Stderr, |s| s.bright_blue())
))]
#[diagnostic(code("unused::constant"))]
UnusedPrivateModuleConstant {
@ -1228,7 +1376,10 @@ pub enum Warning {
name: String,
},
#[error("I discovered an unused type: '{}'.\n", name.purple())]
#[error(
"I discovered an unused type: '{}'.\n",
name.if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(code("unused::type"))]
UnusedType {
#[label]
@ -1248,10 +1399,13 @@ pub enum Warning {
name: String,
},
#[error("I came across a validator in a {} module which means\nI'm going to ignore it.\n", "lib/".purple())]
#[error(
"I came across a validator in a {} module which means\nI'm going to ignore it.\n",
"lib/".if_supports_color(Stderr, |s| s.purple())
)]
#[diagnostic(help(
"No big deal, but you might want to move it to a {} module\nor remove it to get rid of that warning.",
"validators/".purple()
"validators/".if_supports_color(Stderr, |s| s.purple())
))]
#[diagnostic(code("unused::validator"))]
ValidatorInLibraryModule {
@ -1261,7 +1415,9 @@ pub enum Warning {
#[error(
"I noticed a suspicious {type_ByteArray} UTF-8 literal which resembles a hash digest.",
type_ByteArray = "ByteArray".bold().bright_blue()
type_ByteArray = "ByteArray"
.if_supports_color(Stderr, |s| s.bright_blue())
.if_supports_color(Stderr, |s| s.bold())
)]
#[diagnostic(help("{}", formatdoc! {
r#"When you specify a {type_ByteArray} literal using plain double-quotes, it's interpreted as an array of UTF-8 bytes. For example, the literal {literal_foo} is interpreted as the byte sequence {foo_bytes}.
@ -1270,11 +1426,13 @@ pub enum Warning {
{symbol_hash}{value}
"#,
type_ByteArray = "ByteArray".bold().bright_blue(),
literal_foo = "\"foo\"".purple(),
foo_bytes = "#[102, 111, 111]".purple(),
value = "\"{value}\"".purple(),
symbol_hash = "#".purple(),
type_ByteArray = "ByteArray"
.if_supports_color(Stderr, |s| s.bright_blue())
.if_supports_color(Stderr, |s| s.bold()),
literal_foo = "\"foo\"".if_supports_color(Stderr, |s| s.purple()),
foo_bytes = "#[102, 111, 111]".if_supports_color(Stderr, |s| s.purple()),
value = "\"{value}\"".if_supports_color(Stderr, |s| s.purple()),
symbol_hash = "#".if_supports_color(Stderr, |s| s.purple()),
}))]
#[diagnostic(code("syntax::bytearray_literal_is_hex_string"))]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#bytearray"))]
@ -1314,9 +1472,11 @@ fn format_suggestion(sample: &UntypedExpr) -> String {
.enumerate()
.map(|(ix, line)| {
if ix == 0 {
format!("╰─▶ {}", line.yellow())
format!("╰─▶ {}", line.if_supports_color(Stdout, |s| s.yellow()))
} else {
format!(" {line}").yellow().to_string()
format!(" {line}")
.if_supports_color(Stdout, |s| s.yellow())
.to_string()
}
})
.collect::<Vec<_>>()

View File

@ -20,7 +20,7 @@ ignore = "0.4.18"
indexmap = "1.9.1"
itertools = "0.10.1"
miette = { version = "5.3.0", features = ["fancy"] }
owo-colors = "3.5.0"
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
pallas = "0.16.0"
pallas-traverse = "0.16.0"
petgraph = "0.6.2"

View File

@ -1,7 +1,7 @@
use super::schema;
use aiken_lang::ast::Span;
use miette::{Diagnostic, NamedSource};
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
use std::fmt::Debug;
#[derive(Debug, thiserror::Error, Diagnostic)]
@ -19,15 +19,25 @@ pub enum Error {
#[error("Invalid or missing project's blueprint file.")]
#[diagnostic(code("aiken::blueprint::missing"))]
#[diagnostic(help("Did you forget to {build} the project?", build = "build".purple().bold()))]
#[diagnostic(help(
"Did you forget to {build} the project?",
build = "build"
.if_supports_color(Stdout, |s| s.purple())
.if_supports_color(Stdout, |s| s.bold())
))]
InvalidOrMissingFile,
#[error("I didn't find any parameters to apply in the given validator.")]
#[diagnostic(code("aiken::blueprint::apply::no_parameters"))]
NoParametersToApply,
#[error("I couldn't compute the address of the given validator because it's parameterized by {} parameter(s)!", format!("{n}").purple())]
#[error(
"I couldn't compute the address of the given validator because it's parameterized by {} parameter(s)!",
n.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("aiken::blueprint::address::parameterized"))]
#[diagnostic(help("I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly 3 arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly in the validator. Applying parameters change the validator's compiled code, and thus the address.\n\nThis is why I need you to apply parmeters first.", keyword_spend = "spend".purple()))]
#[diagnostic(help(
"I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly 3 arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly in the validator. Applying parameters change the validator's compiled code, and thus the address.\n\nThis is why I need you to apply parmeters first.",
keyword_spend = "spend".if_supports_color(Stdout, |s| s.purple())))]
ParameterizedValidator { n: usize },
}

View File

@ -3,7 +3,7 @@ use aiken_lang::{
ast::{DataType, Definition, TypedDefinition},
tipo::{pretty, Type, TypeVar},
};
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
use serde::{
self,
ser::{Serialize, SerializeStruct, Serializer},
@ -533,7 +533,9 @@ I got there when trying to generate a blueprint specification of the following t
r#"There cannot be any unbound type variable at the contract's boundary (i.e. in types used as datum and/or redeemer). Indeed, in order to generate an outward-facing specification of the contract's interface, I need to know what concrete representations will the datum and/or the redeemer have.
If your contract doesn't need datum or redeemer, you can always give them the type {type_Void} to indicate this. It is very concrete and will help me progress forward."#,
type_Void = "Void".bright_blue().bold()
type_Void = "Void"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
),
ErrorContext::ExpectedData => format!(
@ -542,7 +544,9 @@ If your contract doesn't need datum or redeemer, you can always give them the ty
There are few restrictions like this one. In this instance, here's the types I followed and that led me to this problem:
{breadcrumbs}"#,
type_String = "String".bright_blue().bold(),
type_String = "String"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold()),
breadcrumbs = Error::fmt_breadcrumbs(&self.breadcrumbs)
),
@ -564,8 +568,8 @@ Here's the types I followed and that led me to this problem:
pretty::Printer::new()
.print(type_info)
.to_pretty_string(70)
.bright_blue()
.bold()
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
.to_string()
})
.collect::<Vec<_>>()

View File

@ -10,7 +10,7 @@ use aiken_lang::{
use miette::{
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
};
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
use std::{
fmt::{Debug, Display},
io,
@ -121,7 +121,7 @@ pub enum Error {
impl Error {
pub fn report(&self) {
eprintln!("Error: {self:?}")
println!("{self:?}")
}
pub fn from_parse_errors(errs: Vec<ParseError>, path: &Path, src: &str) -> Vec<Self> {
@ -314,13 +314,27 @@ impl Diagnostic for Error {
Error::NoValidatorNotFound { known_validators } => {
Some(Box::new(format!(
"Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}",
known_validators.iter().map(|title| format!("{title}", title = title.purple().bold())).collect::<Vec<String>>().join("\n")
known_validators
.iter()
.map(|title| format!(
"→ {title}",
title = title.if_supports_color(Stdout, |s| s.purple())
))
.collect::<Vec<String>>()
.join("\n")
)))
},
Error::MoreThanOneValidatorFound { known_validators } => {
Some(Box::new(format!(
"Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}",
known_validators.iter().map(|title| format!("{title}", title = title.purple().bold())).collect::<Vec<String>>().join("\n")
known_validators
.iter()
.map(|title| format!(
"→ {title}",
title = title.if_supports_color(Stdout, |s| s.purple())
))
.collect::<Vec<String>>()
.join("\n")
)))
},
Error::Module(e) => e.help(),
@ -526,7 +540,7 @@ impl Warning {
}
pub fn report(&self) {
eprintln!("Warning: {self:?}")
eprintln!("{self:?}")
}
}

View File

@ -1,4 +1,4 @@
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stdout};
use serde::{de::Visitor, Deserialize, Serialize};
use std::{
fmt::{self, Display},
@ -109,12 +109,19 @@ impl<'de> Deserialize<'de> for PackageName {
#[derive(Debug, Error, miette::Diagnostic)]
pub enum Error {
#[error("{} is not a valid project name: {}", name.red(), reason.to_string())]
#[error(
"{} is not a valid project name: {}",
name.if_supports_color(Stdout, |s| s.red()),
reason.to_string()
)]
InvalidProjectName {
name: String,
reason: InvalidProjectNameReason,
},
#[error("A project named {} already exists.", name.red())]
#[error(
"A project named {} already exists.",
name.if_supports_color(Stdout, |s| s.red())
)]
ProjectExists { name: String },
}
@ -134,9 +141,9 @@ impl fmt::Display for InvalidProjectNameReason {
{}/{}\n\nEach part must start with a lowercase letter \
and may only contain lowercase letters, numbers, hyphens or underscores.\
\nFor example,\n\n\t{}",
"{owner}".bright_blue(),
"{project}".bright_blue(),
"aiken-lang/stdlib".bright_blue(),
"{owner}".if_supports_color(Stdout, |s| s.bright_blue()),
"{project}".if_supports_color(Stdout, |s| s.bright_blue()),
"aiken-lang/stdlib".if_supports_color(Stdout, |s| s.bright_blue()),
),
}
}

View File

@ -15,7 +15,7 @@ hex = "0.4.3"
ignore = "0.4.18"
indoc = "1.0"
miette = { version = "5.3.0", features = ["fancy"] }
owo-colors = "3.5.0"
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
pallas-addresses = "0.16.0"
pallas-codec = "0.16.0"
pallas-crypto = "0.16.0"

View File

@ -4,7 +4,7 @@ use aiken_project::{
};
use indoc::{formatdoc, indoc};
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stderr};
use std::{
fs,
path::{Path, PathBuf},
@ -55,7 +55,7 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()>
}
fn print_success_message(package_name: &PackageName) {
println!(
eprintln!(
"\n{}",
formatdoc! {
r#"Your Aiken project {name} has been {s} created.
@ -64,10 +64,17 @@ fn print_success_message(package_name: &PackageName) {
{cd} {name}
{aiken} check
"#,
s = "successfully".bold().bright_green(),
cd = "cd".bold().purple(),
name = package_name.repo.bright_blue(),
aiken = "aiken".bold().purple(),
s = "successfully"
.if_supports_color(Stderr, |s| s.bright_green())
.if_supports_color(Stderr, |s| s.bold()),
cd = "cd"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
name = package_name
.if_supports_color(Stderr, |s| s.repo.bright_blue()),
aiken = "aiken"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
}
)
}

View File

@ -5,7 +5,7 @@ use aiken_project::{
pretty,
};
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stderr};
use std::{path::PathBuf, process, str::FromStr};
#[derive(clap::Args)]
@ -43,27 +43,29 @@ pub fn exec(args: Args) -> miette::Result<()> {
}
};
println!(
eprintln!(
"{} {}",
pretty::pad_left("Package".to_string(), 13, " ")
.bold()
.purple(),
dependency.name.bright_blue(),
dependency
.name
.if_supports_color(Stderr, |s| s.bright_blue()),
);
match config.insert(&dependency, args.overwrite) {
Some(config) => {
config.save(&root).into_diagnostic()?;
println!(
eprintln!(
"{} version → {}",
pretty::pad_left(
if args.overwrite { "Changed" } else { "Added" }.to_string(),
13,
" "
)
.bold()
.purple(),
dependency.version.yellow()
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
dependency.version.if_supports_color(Stderr, |s| s.yellow())
);
Ok(())
}

View File

@ -1,38 +1,38 @@
use aiken_project::{paths, pretty};
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stderr};
use std::fs;
pub fn exec() -> miette::Result<()> {
let dir = paths::packages_cache();
println!(
eprintln!(
"{} {}",
pretty::pad_left("Clearing".to_string(), 13, " ")
.bold()
.purple(),
dir.display().bold(),
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
dir.display().if_supports_color(Stderr, |s| s.bold()),
);
let packages = fs::read_dir(&dir).into_diagnostic()?;
for package in packages {
let path = package.into_diagnostic()?.path();
println!(
eprintln!(
"{} {}",
pretty::pad_left("Removing".to_string(), 13, " ")
.bold()
.purple(),
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
path.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.bright_blue(),
.if_supports_color(Stderr, |s| s.bright_blue()),
);
fs::remove_file(path).into_diagnostic()?;
}
println!(
"{}",
pretty::pad_left("Done".to_string(), 13, " ")
.bold()
.purple()
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
Ok(())
}

View File

@ -1,5 +1,5 @@
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use owo_colors::{OwoColorize, Stream::Stderr};
use pallas_primitives::{
babbage::{Redeemer, TransactionInput, TransactionOutput},
Fragment,
@ -54,7 +54,12 @@ pub fn exec(
zero_slot,
}: Args,
) -> miette::Result<()> {
eprintln!("{} script context", " Parsing".bold().purple(),);
eprintln!(
"{} script context",
" Parsing"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
let (tx_bytes, inputs_bytes, outputs_bytes) = if cbor {
(
@ -78,7 +83,13 @@ pub fn exec(
.or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes))
.into_diagnostic()?;
eprintln!("{} {}", " Simulating".bold().purple(), tx.hash());
eprintln!(
"{} {}",
" Simulating"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
tx.hash()
);
let inputs = Vec::<TransactionInput>::decode_fragment(&inputs_bytes).unwrap();
let outputs = Vec::<TransactionOutput>::decode_fragment(&outputs_bytes).unwrap();
@ -100,9 +111,11 @@ pub fn exec(
};
let with_redeemer = |redeemer: &Redeemer| {
println!(
eprintln!(
"{} {:?} → {}",
" Redeemer".bold().purple(),
" Redeemer"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
redeemer.tag,
redeemer.index
)
@ -147,7 +160,13 @@ pub fn exec(
}
fn display_tx_error(err: &tx::error::Error) -> String {
let mut msg = format!("{} {}", " Error".bold().red(), err.red());
let mut msg = format!(
"{} {}",
" Error"
.if_supports_color(Stderr, |s| s.red())
.if_supports_color(Stderr, |s| s.bold()),
err.red()
);
match err {
tx::error::Error::RedeemerError { err, .. } => {
msg.push_str(&format!(
@ -164,7 +183,15 @@ fn display_tx_error(err: &tx::error::Error) -> String {
msg.push_str(
traces
.iter()
.map(|s| format!("\n{} {}", " Trace".bold().yellow(), s.yellow()))
.map(|s| {
format!(
"\n{} {}",
" Trace"
.if_supports_color(Stderr, |s| s.yellow())
.if_supports_color(Stderr, |s| s.bold()),
s.if_supports_color(Stderr, |s| s.yellow())
)
})
.collect::<Vec<_>>()
.join("")
.as_str(),

View File

@ -1,6 +1,9 @@
use aiken_project::{pretty, script::EvalInfo, telemetry, Project};
use miette::IntoDiagnostic;
use owo_colors::OwoColorize;
use owo_colors::{
OwoColorize,
Stream::{self, Stderr},
};
use std::{collections::BTreeMap, env, path::PathBuf, process};
use uplc::machine::cost_model::ExBudget;
@ -41,7 +44,12 @@ where
err.report()
}
println!("\n{}", "Summary".purple().bold());
eprintln!(
"\n{}",
"Summary"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
let warning_text = format!("{warning_count} warning{plural}");
@ -49,17 +57,29 @@ where
let error_text = format!("{} error{}", errs.len(), plural);
let full_summary = format!(" {}, {}", error_text.red(), warning_text.yellow());
let full_summary = format!(
" {}, {}",
error_text.if_supports_color(Stderr, |s| s.red()),
warning_text.if_supports_color(Stderr, |s| s.yellow())
);
println!("{full_summary}");
eprintln!("{full_summary}");
process::exit(1);
} else {
println!("\n{}", "Summary".purple().bold());
eprintln!(
"\n{}",
"Summary"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold())
);
let warning_text = format!("{warning_count} warning{plural}");
println!(" 0 errors, {}", warning_text.yellow(),);
eprintln!(
" 0 errors, {}",
warning_text.if_supports_color(Stderr, |s| s.yellow()),
);
}
Ok(())
}
@ -75,12 +95,15 @@ impl telemetry::EventListener for Terminal {
version,
root,
} => {
println!(
eprintln!(
"{} {} {} ({})",
" Compiling".bold().purple(),
name.bold(),
" Compiling"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.display().bright_blue()
root.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::BuildingDocumentation {
@ -88,65 +111,103 @@ impl telemetry::EventListener for Terminal {
version,
root,
} => {
println!(
eprintln!(
"{} {} {} ({})",
" Generating documentation".bold().purple(),
name.bold(),
" Generating documentation"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold()),
version,
root.to_str().unwrap_or("").bright_blue()
root.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::WaitingForBuildDirLock => {
println!("{}", "Waiting for build directory lock ...".bold().purple());
eprintln!(
"{}",
"Waiting for build directory lock ..."
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple())
);
}
telemetry::Event::DumpingUPLC { path } => {
println!(
eprintln!(
"{} {} ({})",
" Exporting".bold().purple(),
"UPLC".bold(),
path.display().bright_blue()
" Exporting"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"UPLC".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingBlueprint { path } => {
println!(
eprintln!(
"{} {} ({})",
" Generating".bold().purple(),
"project's blueprint".bold(),
path.display().bright_blue()
" Generating"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
path.display()
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingDocFiles { output_path } => {
println!(
eprintln!(
"{} in {}",
" Generating documentation files".bold().purple(),
output_path.to_str().unwrap_or("").bright_blue()
" Generating documentation files"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
output_path
.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.bright_blue())
);
}
telemetry::Event::GeneratingUPLCFor { name, path } => {
println!(
eprintln!(
"{} {}.{{{}}}",
" Generating UPLC for".bold().purple(),
path.to_str().unwrap_or("").blue(),
name.bright_blue(),
" Generating UPLC for"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
path.to_str()
.unwrap_or("")
.if_supports_color(Stderr, |s| s.blue()),
name.if_supports_color(Stderr, |s| s.bright_blue()),
);
}
telemetry::Event::EvaluatingFunction { results } => {
println!("{}\n", " Evaluating function ...".bold().purple());
eprintln!(
"{}\n",
" Evaluating function ..."
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple())
);
let (max_mem, max_cpu) = find_max_execution_units(&results);
for eval_info in &results {
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu))
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu, Stderr))
}
}
telemetry::Event::RunningTests => {
println!("{} {}\n", " Testing".bold().purple(), "...".bold());
eprintln!(
"{} {}\n",
" Testing"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
"...".if_supports_color(Stderr, |s| s.bold())
);
}
telemetry::Event::FinishedTests { tests } => {
let (max_mem, max_cpu) = find_max_execution_units(&tests);
for (module, infos) in &group_by_module(&tests) {
let title = module.bold().blue().to_string();
let title = module
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.blue())
.to_string();
let tests = infos
.iter()
@ -156,11 +217,11 @@ impl telemetry::EventListener for Terminal {
let summary = fmt_test_summary(infos, true);
println!(
eprintln!(
"{}\n",
pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border
.bright_black()
.if_supports_color(Stderr, |s| s.bright_black())
.to_string()),
4
)
@ -168,7 +229,13 @@ impl telemetry::EventListener for Terminal {
}
}
telemetry::Event::DownloadingPackage { name } => {
println!("{} {}", " Downloading".bold().purple(), name.bold())
eprintln!(
"{} {}",
" Downloading"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
name.if_supports_color(Stderr, |s| s.bold())
)
}
telemetry::Event::PackagesDownloaded { start, count } => {
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
@ -178,10 +245,21 @@ impl telemetry::EventListener for Terminal {
_ => format!("{count} packages in {elapsed}"),
};
println!("{} {}", " Downloaded".bold().purple(), msg.bold())
eprintln!(
"{} {}",
" Downloaded"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
msg.if_supports_color(Stderr, |s| s.bold())
)
}
telemetry::Event::ResolvingVersions => {
println!("{}", " Resolving versions".bold().purple(),)
eprintln!(
"{}",
" Resolving versions"
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()),
)
}
}
}
@ -203,13 +281,27 @@ fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool)
let test = format!(
"{status} [mem: {mem_unit}, cpu: {cpu_unit}] {module}",
status = if *success {
pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string())
pretty::style_if(styled, "PASS".to_string(), |s| {
s.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.green())
.to_string()
})
} else {
pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string())
pretty::style_if(styled, "FAIL".to_string(), |s| {
s.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.red())
.to_string()
})
},
mem_unit = pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()),
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()),
module = pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()),
mem_unit = pretty::style_if(styled, mem_pad, |s| s
.if_supports_color(Stderr, |s| s.bright_white())
.to_string()),
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
.if_supports_color(Stderr, |s| s.bright_white())
.to_string()),
module = pretty::style_if(styled, script.name.clone(), |s| s
.if_supports_color(Stderr, |s| s.bright_blue())
.to_string()),
);
let logs = if logs.is_empty() {
@ -219,8 +311,8 @@ fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool)
.map(|line| {
format!(
"{arrow} {styled_line}",
arrow = "".bright_yellow(),
styled_line = line.bright_black()
arrow = "".if_supports_color(Stderr, |s| s.bright_yellow()),
styled_line = line.if_supports_color(Stderr, |s| s.bright_black())
)
})
.collect::<Vec<_>>()
@ -247,20 +339,20 @@ fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
format!(
"{} | {} | {}",
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
.bold()
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
.bright_green()
.bold()
.if_supports_color(Stderr, |s| s.bright_green())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
.bright_red()
.bold()
.if_supports_color(Stderr, |s| s.bright_red())
.if_supports_color(Stderr, |s| s.bold())
.to_string()),
)
}
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, stream: Stream) -> String {
let EvalInfo {
output,
script,
@ -272,8 +364,8 @@ fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
format!(
" {}::{} [mem: {}, cpu: {}]\n\n ╰─▶ {}",
script.module.blue(),
script.name.bright_blue(),
script.module.if_supports_color(stream, |s| s.blue()),
script.name.if_supports_color(stream, |s| s.bright_blue()),
pretty::pad_left(mem.to_string(), max_mem, " "),
pretty::pad_left(cpu.to_string(), max_cpu, " "),
output