diff --git a/Cargo.lock b/Cargo.lock index ac3bc579..944f213b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index 396047b5..aba8df2c 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -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" } diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 933fabbe..5ffcefca 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -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: diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 639ce3cc..929e692e 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -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, } diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index e73622c7..6d6d418f 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -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::>() .join("\n") })) @@ -85,10 +88,14 @@ pub enum Error { errors: Vec, }, - #[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::>().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::>() + .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, }, - #[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), - #[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, }, - #[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, }, - #[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, }, - #[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, }, - #[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::>() diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index 5f57fd36..2eca9582 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -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" diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index dfb5301d..1c8877fd 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -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 }, } diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index aca47dbd..8b5b4009 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -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::>() diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index 18d4bedf..edcf76ac 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -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, path: &Path, src: &str) -> Vec { @@ -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::>().join("\n") + known_validators + .iter() + .map(|title| format!( + "→ {title}", + title = title.if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .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::>().join("\n") + known_validators + .iter() + .map(|title| format!( + "→ {title}", + title = title.if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .join("\n") ))) }, Error::Module(e) => e.help(), @@ -526,7 +540,7 @@ impl Warning { } pub fn report(&self) { - eprintln!("Warning: {self:?}") + eprintln!("{self:?}") } } diff --git a/crates/aiken-project/src/package_name.rs b/crates/aiken-project/src/package_name.rs index fd6cb73d..6acd3aa8 100644 --- a/crates/aiken-project/src/package_name.rs +++ b/crates/aiken-project/src/package_name.rs @@ -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()), ), } } diff --git a/crates/aiken/Cargo.toml b/crates/aiken/Cargo.toml index 117ce67a..2b05b4c8 100644 --- a/crates/aiken/Cargo.toml +++ b/crates/aiken/Cargo.toml @@ -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" diff --git a/crates/aiken/src/cmd/new.rs b/crates/aiken/src/cmd/new.rs index 28be1687..4897c7f0 100644 --- a/crates/aiken/src/cmd/new.rs +++ b/crates/aiken/src/cmd/new.rs @@ -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()) } ) } diff --git a/crates/aiken/src/cmd/packages/add.rs b/crates/aiken/src/cmd/packages/add.rs index 522f8cbe..3ab5aad6 100644 --- a/crates/aiken/src/cmd/packages/add.rs +++ b/crates/aiken/src/cmd/packages/add.rs @@ -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(()) } diff --git a/crates/aiken/src/cmd/packages/clear_cache.rs b/crates/aiken/src/cmd/packages/clear_cache.rs index a0c4d15c..d747a5df 100644 --- a/crates/aiken/src/cmd/packages/clear_cache.rs +++ b/crates/aiken/src/cmd/packages/clear_cache.rs @@ -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(()) } diff --git a/crates/aiken/src/cmd/tx/simulate.rs b/crates/aiken/src/cmd/tx/simulate.rs index e61ff88c..fad6ac01 100644 --- a/crates/aiken/src/cmd/tx/simulate.rs +++ b/crates/aiken/src/cmd/tx/simulate.rs @@ -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::::decode_fragment(&inputs_bytes).unwrap(); let outputs = Vec::::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::>() .join("") .as_str(), diff --git a/crates/aiken/src/lib.rs b/crates/aiken/src/lib.rs index 4ed860ea..6d8b7f76 100644 --- a/crates/aiken/src/lib.rs +++ b/crates/aiken/src/lib.rs @@ -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::>() @@ -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