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, } #[derive(Deserialize, Debug)] struct Package { pname: String, version: String, meta: PackageMeta, } #[derive(Deserialize, Debug)] struct PackageMeta { description: Option, /** * 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, } 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 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 where D: Deserializer<'de>, { struct StringOrVec(PhantomData); 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(self, value: &str) -> Result { Ok(value.to_owned()) } fn visit_none(self) -> Result { Ok("unspecified".to_owned()) } fn visit_seq>(self, visitor: S) -> Result { let strings: Vec = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))?; Ok(strings.into_iter().next().unwrap()) } } deserializer.deserialize_any(StringOrVec(PhantomData)) }