Skip to content

Commit 16dfe5e

Browse files
authored
Merge pull request #118 from 0xnim/feature/better_logging
LGTM
2 parents aa19a09 + 3b5e1d1 commit 16dfe5e

File tree

5 files changed

+302
-9
lines changed

5 files changed

+302
-9
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ members = [
2020
"src/lib/net/crates/codec",
2121
"src/lib/plugins",
2222
"src/lib/storage",
23-
"src/lib/utils", "src/lib/utils/logging", "src/lib/utils/profiling", "src/lib/utils/general_purpose",
23+
"src/lib/utils", "src/lib/utils/logging", "src/lib/utils/profiling", "src/lib/utils/general_purpose", "src/lib/utils/color",
2424
"src/lib/world",
2525
"src/lib/derive_macros",
2626
"src/lib/adapters/nbt", "src/lib/adapters/mca",
@@ -83,6 +83,7 @@ ferrumc-profiling = { path = "src/lib/utils/profiling" }
8383
ferrumc-logging = { path = "src/lib/utils/logging" }
8484
ferrumc-config = { path = "src/lib/utils/config" }
8585
ferrumc-general-purpose = { path = "src/lib/utils/general_purpose" }
86+
ferrumc-color = { path = "src/lib/utils/color" }
8687
ferrumc-macros = { path = "src/lib/derive_macros" }
8788
ferrumc-world = { path = "src/lib/world" }
8889
ferrumc-nbt = { path = "src/lib/adapters/nbt" }
@@ -154,6 +155,8 @@ bzip2 = "0.4.1"
154155
# CLI
155156
clap = "4.5.20"
156157
indicatif = "0.17.8"
158+
colored = "2.1.0"
159+
atty = "0.2.14"
157160

158161
# Misc
159162
deepsize = "0.2.0"

src/lib/derive_macros/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ edition = "2021"
77
proc-macro = true
88

99
[dependencies]
10+
colored = { workspace = true }
11+
ferrumc-color = { workspace = true }
12+
1013
quote = { workspace = true }
1114
syn = { workspace = true, features = ["full"] }
1215
thiserror = { workspace = true }

src/lib/derive_macros/src/net/packets/mod.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ use quote::quote;
33
use std::env;
44
use std::ops::Add;
55
use syn::{parse_macro_input, LitInt, LitStr};
6+
use colored::Colorize;
7+
8+
use ferrumc_color::supports_color;
9+
610

711
/// Essentially, this just reads all the files in the directory and generates a match arm for each packet.
812
/// (packet_id, state) => { ... }
913
pub fn bake_registry(input: TokenStream) -> TokenStream {
14+
if supports_color() {
15+
colored::control::set_override(true);
16+
}
17+
1018
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
1119
let module_path = parse_macro_input!(input as syn::LitStr).value();
1220

@@ -18,7 +26,11 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
1826
let base_path = module_path.split("\\").collect::<Vec<&str>>()[2..].join("::");
1927
let base_path = format!("crate::{}", base_path);
2028

21-
println!("[FERRUMC_MACROS] Parsing packets in {}", dir_path.display());
29+
println!(
30+
" {} {}",
31+
"[FERRUMC_MACROS]".blue().bold(),
32+
format!("Parsing packets in {}", dir_path.display()).white().bold()
33+
);
2234

2335
if !std::fs::metadata(dir_path).unwrap().is_dir() {
2436
return TokenStream::from(quote! {
@@ -90,17 +102,20 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
90102
let struct_name = &item_struct.ident;
91103

92104
println!(
93-
"[FERRUMC_MACROS] Found Packet (ID: 0x{:02X}, State: {}, Struct Name: {})",
94-
packet_id, state, struct_name
105+
" {} {} (ID: {}, State: {}, Struct Name: {})",
106+
"[FERRUMC_MACROS]".bold().blue(),
107+
"Found Packet".white().bold(),
108+
format!("0x{:02X}", packet_id).cyan(),
109+
state.green(),
110+
struct_name.to_string().yellow()
95111
);
96-
112+
97113
let path = format!(
98114
// "crate::net::packets::incoming::{}",
99115
"{}::{}",
100116
base_path,
101117
file_name.to_string_lossy().replace(".rs", "")
102118
);
103-
104119
let struct_path = format!("{}::{}", path, struct_name);
105120

106121
let struct_path = syn::parse_str::<syn::Path>(&struct_path).expect("parse_str failed");
@@ -119,10 +134,15 @@ pub fn bake_registry(input: TokenStream) -> TokenStream {
119134
}
120135

121136
let elapsed = start.elapsed();
122-
println!("[FERRUMC_MACROS] Found {} packets", match_arms.len());
123137
println!(
124-
"[FERRUMC_MACROS] It took: {:?} to parse all the files and generate the packet registry",
125-
elapsed
138+
" {} {}",
139+
"[FERRUMC_MACROS]".bold().blue(),
140+
format!("Found {} packets", match_arms.len()).purple().bold()
141+
);
142+
println!(
143+
" {} {}",
144+
"[FERRUMC_MACROS]".bold().blue(),
145+
format!("It took: {:?} to parse all the files and generate the packet registry", elapsed).red().bold()
126146
);
127147

128148
let match_arms = match_arms.into_iter();

src/lib/utils/color/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "ferrumc-color"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
atty = { workspace = true }

src/lib/utils/color/src/lib.rs

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
use std::env;
2+
use std::sync::LazyLock;
3+
4+
#[derive(Debug, PartialEq, Clone, Copy)]
5+
pub enum ColorLevel {
6+
None,
7+
Basic,
8+
Enhanced,
9+
TrueColor,
10+
}
11+
12+
impl ColorLevel {
13+
pub fn to_bool(&self) -> bool {
14+
matches!(self, ColorLevel::Basic | ColorLevel::Enhanced | ColorLevel::TrueColor)
15+
}
16+
17+
pub fn new() -> Self {
18+
Self::None
19+
}
20+
21+
pub fn update(self, new_level: Self) -> Self {
22+
match (&self, new_level) {
23+
(ColorLevel::None, level) => level,
24+
(_, level) if level > self => level,
25+
_ => self,
26+
}
27+
}
28+
29+
pub fn force_none() -> Self {
30+
Self::None
31+
}
32+
}
33+
34+
impl Default for ColorLevel {
35+
fn default() -> Self {
36+
Self::None
37+
}
38+
}
39+
40+
impl PartialOrd for ColorLevel {
41+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42+
Some(match (self, other) {
43+
(Self::None, _) => std::cmp::Ordering::Less,
44+
(_, Self::None) => std::cmp::Ordering::Greater,
45+
(Self::Basic, Self::Basic) => std::cmp::Ordering::Equal,
46+
(Self::Basic, Self::Enhanced) => std::cmp::Ordering::Less,
47+
(Self::Basic, Self::TrueColor) => std::cmp::Ordering::Less,
48+
(Self::Enhanced, Self::Basic) => std::cmp::Ordering::Greater,
49+
(Self::Enhanced, Self::Enhanced) => std::cmp::Ordering::Equal,
50+
(Self::Enhanced, Self::TrueColor) => std::cmp::Ordering::Less,
51+
(Self::TrueColor, Self::Basic) => std::cmp::Ordering::Greater,
52+
(Self::TrueColor, Self::Enhanced) => std::cmp::Ordering::Greater,
53+
(Self::TrueColor, Self::TrueColor) => std::cmp::Ordering::Equal,
54+
})
55+
}
56+
}
57+
58+
static COLOR_LEVEL: LazyLock<ColorLevel> = LazyLock::new(determine_color_level);
59+
60+
pub fn supports_color() -> bool {
61+
COLOR_LEVEL.to_bool()
62+
}
63+
64+
fn determine_color_level() -> ColorLevel {
65+
66+
// Check FORCE_COLOR environment variable first
67+
if let Some(color) = force_color_check() {
68+
return color;
69+
}
70+
71+
// Windows checks
72+
if let Some(color) = windows_color_check() {
73+
return color;
74+
}
75+
76+
// CI check
77+
if let Some(color) = ci_color_check() {
78+
return color;
79+
}
80+
81+
// Term check
82+
if let Some(color) = term_color_check() {
83+
return color;
84+
}
85+
86+
// Term Program check
87+
if let Some(color) = term_program_check() {
88+
return color;
89+
}
90+
91+
// Final fallback based on whether the stream is a TTY
92+
if let Some(color) = tty_color_check() {
93+
return color;
94+
}
95+
96+
ColorLevel::None
97+
}
98+
99+
fn force_color_check() -> Option<ColorLevel> {
100+
env::var("FORCE_COLOR").ok().and_then(|val| match val.as_str() {
101+
"0" => Some(ColorLevel::None),
102+
"1" => Some(ColorLevel::Basic),
103+
"2" => Some(ColorLevel::Enhanced),
104+
"3" => Some(ColorLevel::TrueColor),
105+
_ => None,
106+
})
107+
}
108+
109+
fn windows_color_check() -> Option<ColorLevel> {
110+
if std::env::consts::OS == "windows" {
111+
return Some(ColorLevel::Basic);
112+
}
113+
None
114+
}
115+
116+
fn ci_color_check() -> Option<ColorLevel> {
117+
if env::var("CI").is_ok() {
118+
if env::var("GITHUB_ACTIONS").is_ok() || env::var("GITEA_ACTIONS").is_ok() {
119+
return Some(ColorLevel::TrueColor);
120+
}
121+
let ci_providers = ["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE", "codeship"];
122+
if ci_providers.iter().any(|ci| env::var(ci).is_ok()) {
123+
return Some(ColorLevel::Basic);
124+
}
125+
}
126+
None
127+
}
128+
129+
fn term_color_check() -> Option<ColorLevel> {
130+
match env::var("TERM").as_deref() {
131+
Ok("dumb") => None,
132+
Ok("xterm-kitty") | Ok("truecolor") | Ok("ansi") => Some(ColorLevel::TrueColor),
133+
Ok(term) if term.ends_with("-256color") => Some(ColorLevel::Enhanced),
134+
Ok(term) if term.starts_with("xterm") || term.starts_with("screen") => Some(ColorLevel::Basic),
135+
Ok("cygwin") => Some(ColorLevel::Basic),
136+
_ => None,
137+
}
138+
}
139+
140+
fn term_program_check() -> Option<ColorLevel> {
141+
match env::var("TERM_PROGRAM").as_deref() {
142+
Ok("iTerm.app") => Some(ColorLevel::TrueColor),
143+
Ok("Apple_Terminal") => Some(ColorLevel::Enhanced),
144+
_ => None,
145+
}
146+
}
147+
148+
fn tty_color_check() -> Option<ColorLevel> {
149+
if atty::is(atty::Stream::Stdout) {
150+
return Some(ColorLevel::TrueColor);
151+
}
152+
None
153+
}
154+
155+
#[cfg(test)]
156+
mod tests {
157+
use super::*;
158+
use std::env;
159+
160+
// test to_bool
161+
#[test]
162+
fn test_to_bool() {
163+
assert!(!ColorLevel::None.to_bool(), "None should not be true");
164+
assert!(ColorLevel::Basic.to_bool(), "Basic should be true");
165+
assert!(ColorLevel::Enhanced.to_bool(), "Enhanced should be true");
166+
assert!(ColorLevel::TrueColor.to_bool(), "TrueColor should be true");
167+
}
168+
169+
// test new
170+
#[test]
171+
fn test_new() {
172+
assert_eq!(ColorLevel::None, ColorLevel::new(), "None should be the default color level");
173+
}
174+
175+
// test update
176+
#[test]
177+
fn test_update() {
178+
assert_eq!(ColorLevel::Basic, ColorLevel::None.update(ColorLevel::Basic), "Updating from None to Basic should change the color level");
179+
assert_eq!(ColorLevel::Enhanced, ColorLevel::Basic.update(ColorLevel::Enhanced), "Updating from Basic to Enhanced should change the color level");
180+
assert_eq!(ColorLevel::TrueColor, ColorLevel::Enhanced.update(ColorLevel::TrueColor), "Updating from Enhanced to TrueColor should change the color level");
181+
// should never downgrade
182+
// updating to none should not change the color level
183+
assert_eq!(ColorLevel::Basic, ColorLevel::Basic.update(ColorLevel::None), "Updating from Basic to None should not change the color level");
184+
assert_eq!(ColorLevel::Enhanced, ColorLevel::Enhanced.update(ColorLevel::None), "Updating from Enhanced to None should not change the color level");
185+
assert_eq!(ColorLevel::TrueColor, ColorLevel::TrueColor.update(ColorLevel::None), "Updating from TrueColor to None should not change the color level");
186+
// updating to a lower level should not change the color level
187+
assert_eq!(ColorLevel::Enhanced, ColorLevel::Enhanced.update(ColorLevel::Basic), "Updating from Enhanced to Basic should not change the color level");
188+
assert_eq!(ColorLevel::TrueColor, ColorLevel::TrueColor.update(ColorLevel::Enhanced), "Updating from TrueColor to Enhanced should not change the color level");
189+
190+
}
191+
192+
#[test]
193+
fn test_force_color_levels() {
194+
use std::env;
195+
196+
// Define test cases as tuples: (env_var_value, expected_color_level)
197+
let test_cases = [
198+
("0", ColorLevel::None),
199+
("1", ColorLevel::Basic),
200+
("2", ColorLevel::Enhanced),
201+
("3", ColorLevel::TrueColor),
202+
// Add a case for an invalid test that should fail
203+
("0", ColorLevel::Basic), // this case is intentionally wrong
204+
];
205+
206+
for (value, expected) in test_cases.iter() {
207+
env::set_var("FORCE_COLOR", value);
208+
let color_level = determine_color_level();
209+
210+
// If the expected color level is ColorLevel::Basic, this case should fail
211+
if *value == "0" && *expected == ColorLevel::Basic {
212+
assert_ne!(color_level, *expected, "Expected failure for FORCE_COLOR = {}", value);
213+
} else {
214+
assert_eq!(color_level, *expected, "Unexpected color level for FORCE_COLOR = {}", value);
215+
}
216+
217+
env::remove_var("FORCE_COLOR");
218+
}
219+
}
220+
221+
// Test CI color level
222+
#[test]
223+
fn test_ci_color_levels() {
224+
// Define a list of CI providers and their expected ColorLevel
225+
let ci_providers = [
226+
("GITHUB_ACTIONS", ColorLevel::TrueColor),
227+
("GITEA_ACTIONS", ColorLevel::TrueColor),
228+
("TRAVIS", ColorLevel::Basic),
229+
("CIRCLECI", ColorLevel::Basic),
230+
("APPVEYOR", ColorLevel::Basic),
231+
("GITLAB_CI", ColorLevel::Basic),
232+
("BUILDKITE", ColorLevel::Basic),
233+
("DRONE", ColorLevel::Basic),
234+
("codeship", ColorLevel::Basic),
235+
];
236+
237+
// Outer loop: Set the main "CI" environment variable to enable CI mode
238+
env::set_var("CI", "true");
239+
240+
for (provider, expected_level) in ci_providers.iter() {
241+
// Set the specific CI provider environment variable
242+
env::set_var(provider, "true");
243+
244+
// Check if the color level matches the expected level
245+
assert_eq!(
246+
&ci_color_check().unwrap_or(ColorLevel::None),
247+
expected_level,
248+
"Unexpected color level for CI provider {}",
249+
provider
250+
);
251+
252+
// Clean up by removing the provider-specific environment variable
253+
env::remove_var(provider);
254+
}
255+
256+
// Remove the "CI" environment variable to clean up
257+
env::remove_var("CI");
258+
}
259+
260+
}

0 commit comments

Comments
 (0)