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
use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize};

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
    #[arg(short, long)]
    json: bool,
    #[arg(long)]
    only_value: bool,
    #[arg(value_enum)]
    temperature_in: Temperature,
    #[arg(value_enum)]
    temperature_out: Temperature,
    value: Option<f64>,
    #[arg(short, long)]
    verbose: bool,
}

impl Cli {
    pub fn json(&self) -> bool {
        self.json
    }

    pub fn only_value(&self) -> bool {
        self.only_value
    }

    pub fn value(&self) -> f64 {
        self.value.expect("Value missing")
    }

    pub fn verbose(&self) -> bool {
        self.verbose
    }

    pub fn temperature_in(&self) -> Temperature {
        self.temperature_in
    }

    pub fn temperature_out(&self) -> Temperature {
        self.temperature_out
    }

    pub fn convert(&self) -> f64 {
        let temp = self.value.expect("No value given");
        if self.temperature_in == self.temperature_out {
            return temp;
        };

        match self.temperature_in {
            Temperature::Celsius => match self.temperature_out {
                Temperature::Celsius => unreachable!(),
                Temperature::Kelvin => temp + 273.15,
                Temperature::Fahrenheit => temp * 1.8 + 32.0,
            },
            Temperature::Kelvin => match self.temperature_out {
                Temperature::Celsius => temp - 273.15,
                Temperature::Kelvin => unreachable!(),
                Temperature::Fahrenheit => (temp - 273.15) * 1.8 + 32.0,
            },
            Temperature::Fahrenheit => match self.temperature_out {
                Temperature::Celsius => (temp - 32.0) * 1.8,
                Temperature::Kelvin => (temp - 32.0) * 1.8 + 273.15,
                Temperature::Fahrenheit => unreachable!(),
            },
        }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
enum Temperature {
    Celsius,
    Fahrenheit,
    Kelvin,
}

#[derive(Serialize, Deserialize)]
struct Output {
    temperature_in: Temperature,
    temperature_out: Temperature,
    value_in: f64,
    value_out: f64,
}

impl Output {
    /// Creates a new [`Output`].
    fn new(
        temperature_in: Temperature,
        value_in: f64,
        temperature_out: Temperature,
        value_out: f64,
    ) -> Self {
        Self {
            temperature_in,
            value_in,
            temperature_out,
            value_out,
        }
    }
}

impl std::fmt::Display for Temperature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Temperature::Celsius => write!(f, "Celsius"),
            Temperature::Fahrenheit => write!(f, "Fahrenheit"),
            Temperature::Kelvin => write!(f, "Kelvin"),
        }
    }
}

pub fn run() {
    let cli = Cli::parse();

    let temperature_in = cli.temperature_in();
    let temperature_out = cli.temperature_out();
    let original_value = cli.value();

    let result = cli.convert();

    let output = Output::new(temperature_in, original_value, temperature_out, result);

    if cli.only_value() {
        println!("{result}");
    } else if cli.verbose() {
        println!("From {original_value} {temperature_in} to {result} {temperature_out}");
    } else if cli.json() {
        let json_string = serde_json::to_string(&output).unwrap();
        println!("{}", json_string);
    } else {
        println!("Result: {result}")
    }
}