use super::*;
use crate::output::fmt::{Span, TokenFmt};
use crate::types::TimeZone;
use serde_derive::Serialize;
#[derive(Debug, Clone, Serialize)]
pub enum Conversion {
None,
Expr(Expr),
Degree(Degree),
List(Vec<String>),
Offset(i64),
#[serde(skip)]
Timezone(TimeZone),
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "value")]
pub enum Query {
Expr(Expr),
Convert(Expr, Conversion, Option<u8>, Digits),
Factorize(Expr),
UnitsFor(Expr),
Search(String),
Error(String),
}
impl fmt::Display for Conversion {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Conversion::None => write!(fmt, "nothing"),
Conversion::Expr(ref expr) => write!(fmt, "{}", expr),
Conversion::Degree(ref deg) => write!(fmt, "{}", deg),
Conversion::List(ref list) => {
let list = list
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
write!(fmt, "{}", list)
}
Conversion::Offset(off) => write!(fmt, "{:02}:{:02}", off / 3600, (off / 60) % 60),
Conversion::Timezone(ref tz) => {
if let Some(name) = tz.iana_name() {
write!(fmt, "{}", name)
} else if let Ok(offset) = tz.to_fixed_offset() {
write!(fmt, "{}", offset)
} else {
write!(fmt, "unknown")
}
}
}
}
}
impl<'a> TokenFmt<'a> for Conversion {
fn to_spans(&'a self) -> Vec<Span<'a>> {
match self {
Conversion::None => vec![],
Conversion::Expr(ref expr) => expr.to_spans(),
Conversion::Degree(ref deg) => vec![Span::keyword(deg.as_str())],
Conversion::List(ref list) => crate::output::fmt::join(
list.iter().map(|name| Span::unit(name)),
Span::plain(", "),
)
.collect(),
Conversion::Offset(off) => {
vec![Span::number(format!(
"{:+02}:{:02}",
off / 3600,
(off / 60) % 60
))]
}
Conversion::Timezone(ref tz) => {
if let Some(name) = tz.iana_name() {
vec![Span::timezone(format!("[{}]", name))]
} else if let Ok(offset) = tz.to_fixed_offset() {
let seconds = offset.seconds();
vec![Span::number(format!(
"{:+02}:{:02}",
seconds / 3600,
(seconds / 60) % 60
))]
} else {
vec![Span::error("<failed to format timezone>")]
}
}
}
}
}
impl<'a> TokenFmt<'a> for Query {
fn to_spans(&'a self) -> Vec<Span<'a>> {
match self {
Query::Expr(ref expr) => expr.to_spans(),
Query::Convert(ref expr, ref conversion, base, digits) => {
let mut res = vec![Span::child(expr), Span::plain(" "), Span::plain("->")];
match digits {
Digits::Default => (),
Digits::FullInt => {
res.push(Span::plain(" "));
res.push(Span::keyword("digits"));
}
Digits::Digits(digits) => {
res.push(Span::plain(" "));
res.push(Span::keyword("digits"));
res.push(Span::plain(" "));
res.push(Span::number(format!("{}", digits)));
}
Digits::Fraction => {
res.push(Span::plain(" "));
res.push(Span::keyword("fraction"));
}
Digits::Scientific => {
res.push(Span::plain(" "));
res.push(Span::keyword("scientific"));
}
Digits::Engineering => {
res.push(Span::plain(" "));
res.push(Span::keyword("engineering"));
}
}
if let Some(base) = base {
res.push(Span::plain(" "));
match base {
2 => res.push(Span::keyword("binary")),
8 => res.push(Span::keyword("octal")),
16 => res.push(Span::keyword("hexadecimal")),
x => {
res.push(Span::keyword("base"));
res.push(Span::plain(" "));
res.push(Span::number(format!("{}", x)))
}
}
}
if let Conversion::None = *conversion {
} else {
res.push(Span::plain(" "));
res.push(Span::child(conversion));
}
res
}
Query::Factorize(ref expr) => vec![
Span::keyword("factorize"),
Span::plain(" "),
Span::child(expr),
],
Query::UnitsFor(expr) => vec![
Span::keyword("units for"),
Span::plain(" "),
Span::child(expr),
],
Query::Search(ref term) => vec![
Span::keyword("search"),
Span::plain(" "),
Span::user_input(term),
],
Query::Error(ref message) => vec![Span::error(message)],
}
}
}