logging: Try to improve common operations
This adds sugar for logging `Result`s with a handler, makes names evoke something closer to "logging" than "warning", tries to remove any redundant `Logging` where the module name will do, and introduces a type strictly for bad things happening.
This commit is contained in:
96
src/data.rs
96
src/data.rs
@ -21,7 +21,7 @@ use ::keyboard::{
|
||||
};
|
||||
use ::layout;
|
||||
use ::layout::ArrangementKind;
|
||||
use ::logging::PrintWarnings;
|
||||
use ::logging;
|
||||
use ::resources;
|
||||
use ::util::c::as_str;
|
||||
use ::util::hash_map_map;
|
||||
@ -31,7 +31,7 @@ use ::xdg;
|
||||
use serde::Deserialize;
|
||||
use std::io::BufReader;
|
||||
use std::iter::FromIterator;
|
||||
use ::logging::WarningHandler;
|
||||
use ::logging::Warn;
|
||||
|
||||
/// Gathers stuff defined in C or called by C
|
||||
pub mod c {
|
||||
@ -157,7 +157,7 @@ fn list_layout_sources(
|
||||
fn load_layout_data(source: DataSource)
|
||||
-> Result<::layout::LayoutData, LoadError>
|
||||
{
|
||||
let handler = PrintWarnings{};
|
||||
let handler = logging::Print {};
|
||||
match source {
|
||||
DataSource::File(path) => {
|
||||
Layout::from_file(path.clone())
|
||||
@ -330,7 +330,7 @@ impl Layout {
|
||||
serde_yaml::from_reader(infile).map_err(Error::Yaml)
|
||||
}
|
||||
|
||||
pub fn build<H: WarningHandler>(self, mut warning_handler: H)
|
||||
pub fn build<H: logging::Handler>(self, mut warning_handler: H)
|
||||
-> (Result<::layout::LayoutData, FormattingError>, H)
|
||||
{
|
||||
let button_names = self.views.values()
|
||||
@ -464,7 +464,7 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_action<H: WarningHandler>(
|
||||
fn create_action<H: logging::Handler>(
|
||||
button_info: &HashMap<String, ButtonMeta>,
|
||||
name: &str,
|
||||
view_names: Vec<&String>,
|
||||
@ -494,15 +494,18 @@ fn create_action<H: WarningHandler>(
|
||||
(None, None, Some(text)) => SubmitData::Text(text.clone()),
|
||||
(None, None, None) => SubmitData::Text(name.into()),
|
||||
_ => {
|
||||
warning_handler.handle(&format!(
|
||||
warning_handler.handle(
|
||||
logging::Level::Warning,
|
||||
&format!(
|
||||
"Button {} has more than one of (action, keysym, text)",
|
||||
name
|
||||
));
|
||||
name,
|
||||
),
|
||||
);
|
||||
SubmitData::Text("".into())
|
||||
},
|
||||
};
|
||||
|
||||
fn filter_view_name<H: WarningHandler>(
|
||||
fn filter_view_name<H: logging::Handler>(
|
||||
button_name: &str,
|
||||
view_name: String,
|
||||
view_names: &Vec<&String>,
|
||||
@ -511,10 +514,13 @@ fn create_action<H: WarningHandler>(
|
||||
if view_names.contains(&&view_name) {
|
||||
view_name
|
||||
} else {
|
||||
warning_handler.handle(&format!("Button {} switches to missing view {}",
|
||||
warning_handler.handle(
|
||||
logging::Level::Warning,
|
||||
&format!("Button {} switches to missing view {}",
|
||||
button_name,
|
||||
view_name,
|
||||
));
|
||||
),
|
||||
);
|
||||
"base".into()
|
||||
}
|
||||
}
|
||||
@ -553,27 +559,24 @@ fn create_action<H: WarningHandler>(
|
||||
match keysym_valid(keysym.as_str()) {
|
||||
true => keysym.clone(),
|
||||
false => {
|
||||
warning_handler.handle(&format!(
|
||||
warning_handler.handle(
|
||||
logging::Level::Warning,
|
||||
&format!(
|
||||
"Keysym name invalid: {}",
|
||||
keysym,
|
||||
));
|
||||
),
|
||||
);
|
||||
"space".into() // placeholder
|
||||
},
|
||||
}
|
||||
)),
|
||||
},
|
||||
SubmitData::Text(text) => ::action::Action::Submit {
|
||||
text: {
|
||||
CString::new(text.clone())
|
||||
.map_err(|e| {
|
||||
warning_handler.handle(&format!(
|
||||
"Text {} contains problems: {:?}",
|
||||
text,
|
||||
e
|
||||
));
|
||||
e
|
||||
}).ok()
|
||||
},
|
||||
text: CString::new(text.clone()).or_warn(
|
||||
warning_handler,
|
||||
logging::Problem::Warning,
|
||||
&format!("Text {} contains problems", text),
|
||||
),
|
||||
keys: text.chars().map(|codepoint| {
|
||||
let codepoint_string = codepoint.to_string();
|
||||
::action::KeySym(match keysym_valid(codepoint_string.as_str()) {
|
||||
@ -587,7 +590,7 @@ fn create_action<H: WarningHandler>(
|
||||
|
||||
/// TODO: Since this will receive user-provided data,
|
||||
/// all .expect() on them should be turned into soft fails
|
||||
fn create_button<H: WarningHandler>(
|
||||
fn create_button<H: logging::Handler>(
|
||||
button_info: &HashMap<String, ButtonMeta>,
|
||||
outlines: &HashMap<String, Outline>,
|
||||
name: &str,
|
||||
@ -611,14 +614,11 @@ fn create_button<H: WarningHandler>(
|
||||
} else if let Some(text) = &button_meta.text {
|
||||
::layout::Label::Text(
|
||||
CString::new(text.as_str())
|
||||
.unwrap_or_else(|e| {
|
||||
warning_handler.handle(&format!(
|
||||
"Text {} is invalid: {}",
|
||||
text,
|
||||
e,
|
||||
));
|
||||
CString::new("").unwrap()
|
||||
})
|
||||
.or_warn(
|
||||
warning_handler,
|
||||
logging::Problem::Warning,
|
||||
&format!("Text {} is invalid", text),
|
||||
).unwrap_or_else(|| CString::new("").unwrap())
|
||||
)
|
||||
} else {
|
||||
::layout::Label::Text(cname.clone())
|
||||
@ -629,7 +629,10 @@ fn create_button<H: WarningHandler>(
|
||||
if outlines.contains_key(outline) {
|
||||
outline.clone()
|
||||
} else {
|
||||
warning_handler.handle(&format!("Outline named {} does not exist! Using default for button {}", outline, name));
|
||||
warning_handler.handle(
|
||||
logging::Level::Warning,
|
||||
&format!("Outline named {} does not exist! Using default for button {}", outline, name)
|
||||
);
|
||||
"default".into()
|
||||
}
|
||||
}
|
||||
@ -638,12 +641,11 @@ fn create_button<H: WarningHandler>(
|
||||
|
||||
let outline = outlines.get(&outline_name)
|
||||
.map(|outline| (*outline).clone())
|
||||
.unwrap_or_else(|| {
|
||||
warning_handler.handle(
|
||||
&format!("No default outline defined! Using 1x1!")
|
||||
);
|
||||
Outline { width: 1f64, height: 1f64 }
|
||||
});
|
||||
.or_warn(
|
||||
warning_handler,
|
||||
logging::Problem::Warning,
|
||||
"No default outline defined! Using 1x1!",
|
||||
).unwrap_or(Outline { width: 1f64, height: 1f64 });
|
||||
|
||||
layout::Button {
|
||||
name: cname,
|
||||
@ -663,7 +665,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::error::Error as ErrorTrait;
|
||||
use ::logging::PanicWarn;
|
||||
use ::logging::ProblemPanic;
|
||||
|
||||
#[test]
|
||||
fn test_parse_path() {
|
||||
@ -733,7 +735,7 @@ mod tests {
|
||||
fn test_layout_punctuation() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
|
||||
.unwrap()
|
||||
.build(PanicWarn).0
|
||||
.build(ProblemPanic).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -748,7 +750,7 @@ mod tests {
|
||||
fn test_layout_unicode() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
|
||||
.unwrap()
|
||||
.build(PanicWarn).0
|
||||
.build(ProblemPanic).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -764,7 +766,7 @@ mod tests {
|
||||
fn test_layout_unicode_multi() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
|
||||
.unwrap()
|
||||
.build(PanicWarn).0
|
||||
.build(ProblemPanic).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -779,7 +781,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parsing_fallback() {
|
||||
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
||||
.map(|layout| layout.build(PanicWarn).0.unwrap())
|
||||
.map(|layout| layout.build(ProblemPanic).0.unwrap())
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
@ -827,7 +829,7 @@ mod tests {
|
||||
},
|
||||
".",
|
||||
Vec::new(),
|
||||
&mut PanicWarn,
|
||||
&mut ProblemPanic,
|
||||
),
|
||||
::action::Action::Submit {
|
||||
text: Some(CString::new(".").unwrap()),
|
||||
@ -840,7 +842,7 @@ mod tests {
|
||||
fn test_layout_margins() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_margins.yaml"))
|
||||
.unwrap()
|
||||
.build(PanicWarn).0
|
||||
.build(ProblemPanic).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.margins,
|
||||
|
||||
117
src/logging.rs
117
src/logging.rs
@ -26,13 +26,15 @@
|
||||
* 4. logging to an immutable destination type
|
||||
*
|
||||
* Same as above, except it can be parallelized.
|
||||
* Logs being outputs, they get returned
|
||||
* instead of being misleadingly passed back through arguments.
|
||||
* It seems more difficult to pass the logger around,
|
||||
* but this may be a solved problem from the area of functional programming.
|
||||
*
|
||||
* This library generally aims at the approach in 3.
|
||||
* */
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Levels are not in order.
|
||||
pub enum Level {
|
||||
@ -66,18 +68,72 @@ pub enum Level {
|
||||
Debug,
|
||||
}
|
||||
|
||||
/// Sugar for logging errors in results.
|
||||
/// Approach 2.
|
||||
pub trait Warn {
|
||||
type Value;
|
||||
fn or_warn(self, msg: &str) -> Option<Self::Value>;
|
||||
impl Level {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Level::Panic => "Panic",
|
||||
Level::Bug => "Bug",
|
||||
Level::Error => "Error",
|
||||
Level::Warning => "Warning",
|
||||
Level::Surprise => "Surprise",
|
||||
Level::Info => "Info",
|
||||
Level::Debug => "Debug",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: Error> Warn for Result<T, E> {
|
||||
impl From<Problem> for Level {
|
||||
fn from(problem: Problem) -> Level {
|
||||
use self::Level::*;
|
||||
match problem {
|
||||
Problem::Panic => Panic,
|
||||
Problem::Bug => Bug,
|
||||
Problem::Error => Error,
|
||||
Problem::Warning => Warning,
|
||||
Problem::Surprise => Surprise,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Only levels which indicate problems
|
||||
/// To use with `Result::Err` handlers,
|
||||
/// which are needed only when something went off the optimal path.
|
||||
/// A separate type ensures that `Err`
|
||||
/// can't end up misclassified as a benign event like `Info`.
|
||||
pub enum Problem {
|
||||
Panic,
|
||||
Bug,
|
||||
Error,
|
||||
Warning,
|
||||
Surprise,
|
||||
}
|
||||
|
||||
/// Sugar for logging errors in results.
|
||||
pub trait Warn where Self: Sized{
|
||||
type Value;
|
||||
/// Approach 2.
|
||||
fn or_print(self, level: Problem, message: &str) -> Option<Self::Value> {
|
||||
self.or_warn(&mut Print {}, level.into(), message)
|
||||
}
|
||||
/// Approach 3.
|
||||
fn or_warn<H: Handler>(
|
||||
self,
|
||||
handler: &mut H,
|
||||
level: Problem,
|
||||
message: &str,
|
||||
) -> Option<Self::Value>;
|
||||
}
|
||||
|
||||
impl<T, E: Display> Warn for Result<T, E> {
|
||||
type Value = T;
|
||||
fn or_warn(self, msg: &str) -> Option<T> {
|
||||
fn or_warn<H: Handler>(
|
||||
self,
|
||||
handler: &mut H,
|
||||
level: Problem,
|
||||
message: &str,
|
||||
) -> Option<T> {
|
||||
self.map_err(|e| {
|
||||
eprintln!("{}: {}", msg, e);
|
||||
handler.handle(level.into(), &format!("{}: {}", message, e));
|
||||
e
|
||||
}).ok()
|
||||
}
|
||||
@ -85,9 +141,14 @@ impl<T, E: Error> Warn for Result<T, E> {
|
||||
|
||||
impl<T> Warn for Option<T> {
|
||||
type Value = T;
|
||||
fn or_warn(self, msg: &str) -> Option<T> {
|
||||
fn or_warn<H: Handler>(
|
||||
self,
|
||||
handler: &mut H,
|
||||
level: Problem,
|
||||
message: &str,
|
||||
) -> Option<T> {
|
||||
self.or_else(|| {
|
||||
eprintln!("{}", msg);
|
||||
handler.handle(level.into(), message);
|
||||
None
|
||||
})
|
||||
}
|
||||
@ -95,26 +156,34 @@ impl<T> Warn for Option<T> {
|
||||
|
||||
/// A mutable handler for text warnings.
|
||||
/// Approach 3.
|
||||
pub trait WarningHandler {
|
||||
/// Handle a warning
|
||||
fn handle(&mut self, warning: &str);
|
||||
pub trait Handler {
|
||||
/// Handle a log message
|
||||
fn handle(&mut self, level: Level, message: &str);
|
||||
}
|
||||
|
||||
/// Prints warnings to stderr
|
||||
pub struct PrintWarnings;
|
||||
/// Prints info to stdout, everything else to stderr
|
||||
pub struct Print;
|
||||
|
||||
impl WarningHandler for PrintWarnings {
|
||||
fn handle(&mut self, warning: &str) {
|
||||
eprintln!("{}", warning);
|
||||
impl Handler for Print {
|
||||
fn handle(&mut self, level: Level, message: &str) {
|
||||
match level {
|
||||
Level::Info => println!("Info: {}", message),
|
||||
l => eprintln!("{}: {}", l.as_str(), message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Warning handler that will panic at any warning.
|
||||
/// Warning handler that will panic
|
||||
/// at any warning, error, surprise, bug, or panic.
|
||||
/// Don't use except in tests
|
||||
pub struct PanicWarn;
|
||||
pub struct ProblemPanic;
|
||||
|
||||
impl WarningHandler for PanicWarn {
|
||||
fn handle(&mut self, warning: &str) {
|
||||
panic!("{}", warning);
|
||||
impl Handler for ProblemPanic {
|
||||
fn handle(&mut self, level: Level, message: &str) {
|
||||
use self::Level::*;
|
||||
match level {
|
||||
Panic | Bug | Error | Warning | Surprise => panic!("{}", message),
|
||||
l => Print{}.handle(l, message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use ::layout::c::{ Bounds, EekGtkKeyboard };
|
||||
use ::locale;
|
||||
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
|
||||
use ::locale_config::system_locale;
|
||||
use ::logging;
|
||||
use ::manager;
|
||||
use ::resources;
|
||||
|
||||
@ -242,10 +243,13 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
|
||||
.as_ref()
|
||||
.to_owned()
|
||||
)
|
||||
.or_warn("No locale detected")
|
||||
.or_print(logging::Problem::Surprise, "No locale detected")
|
||||
.and_then(|lang| {
|
||||
resources::get_layout_names(lang.as_str())
|
||||
.or_warn(&format!("No translations for locale {}", lang))
|
||||
.or_print(
|
||||
logging::Problem::Surprise,
|
||||
&format!("No translations for locale {}", lang),
|
||||
)
|
||||
});
|
||||
|
||||
match builtin_translations {
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
/*! CSS data loading. */
|
||||
|
||||
use std::env;
|
||||
use ::logging;
|
||||
|
||||
use glib::object::ObjectExt;
|
||||
use logging::Warn;
|
||||
@ -94,15 +95,13 @@ fn get_theme_name(settings: >k::Settings) -> GtkTheme {
|
||||
None => GtkTheme {
|
||||
name: {
|
||||
settings.get_property("gtk-theme-name")
|
||||
// maybe TODO: is this worth a warning?
|
||||
.or_warn("No theme name")
|
||||
.or_print(logging::Problem::Surprise, "No theme name")
|
||||
.and_then(|value| value.get::<String>())
|
||||
.unwrap_or(DEFAULT_THEME_NAME.into())
|
||||
},
|
||||
variant: {
|
||||
settings.get_property("gtk-application-prefer-dark-theme")
|
||||
// maybe TODO: is this worth a warning?
|
||||
.or_warn("No settings key")
|
||||
.or_print(logging::Problem::Surprise, "No settings key")
|
||||
.and_then(|value| value.get::<bool>())
|
||||
.and_then(|dark_preferred| match dark_preferred {
|
||||
true => Some("dark".into()),
|
||||
|
||||
19
src/tests.rs
19
src/tests.rs
@ -1,17 +1,22 @@
|
||||
/*! Testing functionality */
|
||||
|
||||
use ::data::Layout;
|
||||
use ::logging;
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use ::logging::WarningHandler;
|
||||
|
||||
|
||||
pub struct CountAndPrint(u32);
|
||||
|
||||
impl WarningHandler for CountAndPrint {
|
||||
fn handle(&mut self, warning: &str) {
|
||||
self.0 = self.0 + 1;
|
||||
println!("{}", warning);
|
||||
impl logging::Handler for CountAndPrint {
|
||||
fn handle(&mut self, level: logging::Level, warning: &str) {
|
||||
use logging::Level::*;
|
||||
match level {
|
||||
Panic | Bug | Error | Warning | Surprise => {
|
||||
self.0 += 1;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
logging::Print{}.handle(level, warning)
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +39,7 @@ fn check_layout(layout: Layout) {
|
||||
let (layout, handler) = layout.build(handler);
|
||||
|
||||
if handler.0 > 0 {
|
||||
println!("{} mistakes in layout", handler.0)
|
||||
println!("{} problems while parsing layout", handler.0)
|
||||
}
|
||||
|
||||
let layout = layout.expect("layout broken");
|
||||
|
||||
Reference in New Issue
Block a user