pacbot/src/commands/nix.rs
2019-10-31 11:45:16 +01:00

107 lines
3.1 KiB
Rust

use crate::commands::*;
use serde::de;
use serde::{Deserialize, Deserializer};
use serenity::model::channel::Message;
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
pub fn query_nix(ctx: Context, msg: Message, args: Vec<&str>) {
// we know for sure that there’s at least one element here
let query = args[0];
match NIX_PACKAGES.packages.get(query) {
Some(result) => respond_with_results(msg.channel_id, &[result], &ctx),
None => send(msg.channel_id, "No results", &ctx),
}
}
lazy_static! {
#[derive(Debug)]
static ref NIX_PACKAGES: Response = {
search(
&"https://nixos.org/nixpkgs/packages-nixos-19.09.json",
|e| { panic!("{}", e)
},
)
};
}
#[derive(Deserialize, Debug)]
struct Response {
pub commit: String,
pub packages: HashMap<String, Package>,
}
#[derive(Deserialize, Debug)]
struct Package {
pname: String,
version: String,
meta: PackageMeta,
}
#[derive(Deserialize, Debug)]
struct PackageMeta {
description: Option<String>,
/**
* It appears as though the Nix repositories allow either no homepage, one homepage, or a list.
* Definitely no unnecessary complexity because of that.
*/
#[serde(default)]
#[serde(deserialize_with = "string_or_seq_string")]
homepage: String,
//homepage: Option<Homepage>,
}
impl fmt::Display for Package {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
// This is an en-dash, not a minus sign.
// Putting a minus here removes the first line from output,
// but only for some packages. en-dash works fine.
"{}–{}\n {}\n Homepage: {}",
self.pname,
self.version,
match &self.meta.description {
Some(description) => description,
None => "No description provided",
},
self.meta.homepage
)
}
}
// Custom deserializer to handle the string/vec<string> homepage field.
// Heavily inspired by
// https://stackoverflow.com/questions/41151080/deserialize-a-json-string-or-array-of-strings-into-a-vec
fn string_or_seq_string<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
struct StringOrVec(PhantomData<String>);
impl<'de> de::Visitor<'de> for StringOrVec {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or list of strings")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
Ok(value.to_owned())
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok("unspecified".to_owned())
}
fn visit_seq<S: de::SeqAccess<'de>>(self, visitor: S) -> Result<Self::Value, S::Error> {
let strings: Vec<String> =
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))?;
Ok(strings.into_iter().next().unwrap())
}
}
deserializer.deserialize_any(StringOrVec(PhantomData))
}