1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*! Logging library.
 * 
 * This is probably the only part of squeekboard
 * that should be doing any direct printing.
 * 
 * There are several approaches to logging,
 * in the order of increasing flexibility and/or purity:
 * 
 * 1. `println!` directly
 * 
 *   It can't be easily replaced by a different solution
 * 
 * 2. simple `log!` macro
 * 
 *   Replacing the destination at runtime other than globally would be awkward,
 *   so no easy way to suppress errors for things that don't matter,
 *   but formatting is still easy.
 * 
 * 3. logging to a mutable destination type
 * 
 *   Can be easily replaced, but logging `Result` types,
 *   which should be done by calling a method on the result,
 *   can't be formatted directly.
 *   Cannot be parallelized.
 * 
 * 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::fmt::Display;

/// Levels are not in order.
pub enum Level {
    // Levels for reporting violated constraints
    /// The program violated a self-imposed constraint,
    /// ended up in an inconsistent state, and cannot recover.
    /// Handlers must not actually panic. (should they?)
    Panic,
    /// The program violated a self-imposed constraint,
    /// ended up in an inconsistent state, but some state can be recovered.
    Bug,
    /// Invalid data given by an external source,
    /// some state of the program must be dropped.
    Error,
    // Still violated constraints, but harmless
    /// Invalid data given by an external source, parts of data are ignored.
    /// No previous program state needs to be dropped.
    Warning,
    /// External source not in an expected state,
    /// but not violating any protocols (including no relevant protocol).
    Surprise,
    // Informational
    /// A change in internal state that results in a change of behaviour
    /// that a user can observe, and a tinkerer might find useful.
    /// E.g. selection of external sources, like loading user's UI files,
    /// language switch, overrides.
    Info,
    /// Information useful for application developer only.
    /// Should be limited to information gotten from external sources,
    /// and more tricky parts of internal state.
    Debug,
}

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 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 approach 2
// TODO: avoid, deprecate.
// Handler instances should be long lived, not one per call.
macro_rules! log_print {
    ($level:expr, $($arg:tt)*) => (crate::logging::print($level, &format!($($arg)*)))
}

/// Approach 2
pub fn print(level: Level, message: &str) {
    Print{}.handle(level, message)
}

/// 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<H: Handler>(
        self,
        handler: &mut H,
        level: Problem,
        message: &str,
    ) -> Option<T> {
        self.map_err(|e| {
            handler.handle(level.into(), &format!("{}: {}", message, e));
            e
        }).ok()
    }
}

impl<T> Warn for Option<T> {
    type Value = T;
    fn or_warn<H: Handler>(
        self,
        handler: &mut H,
        level: Problem,
        message: &str,
    ) -> Option<T> {
        self.or_else(|| {
            handler.handle(level.into(), message);
            None
        })
    }
}

/// A mutable handler for text warnings.
/// Approach 3.
pub trait Handler {
    /// Handle a log message
    fn handle(&mut self, level: Level, message: &str);
}

/// Prints info to stdout, everything else to stderr
pub struct Print;

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, error, surprise, bug, or panic.
/// Don't use except in tests
pub struct ProblemPanic;

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),
        }
    }
}