diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-05-23 13:26:22 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-05-23 13:26:22 +0200 |
commit | 204731c0a69136c9cebcb54f1afecf5145e26bbe (patch) | |
tree | fc9132e5dc74e4a8e1327cdd411839a90f9410aa /pkgs/by-name/ge/generate_moz_extension/src | |
parent | refactor(sys): Modularize and move to `modules/system` or `pkgs` (diff) | |
download | nixos-config-204731c0a69136c9cebcb54f1afecf5145e26bbe.tar.gz nixos-config-204731c0a69136c9cebcb54f1afecf5145e26bbe.zip |
refactor(pkgs): Categorize into `by-name` shards
This might not be the perfect way to organize a package set -- especially if the set is not nearly the size of nixpkgs -- but it is _at_ least a way of organization.
Diffstat (limited to 'pkgs/by-name/ge/generate_moz_extension/src')
-rw-r--r-- | pkgs/by-name/ge/generate_moz_extension/src/main.rs | 138 | ||||
-rw-r--r-- | pkgs/by-name/ge/generate_moz_extension/src/types.rs | 71 |
2 files changed, 209 insertions, 0 deletions
diff --git a/pkgs/by-name/ge/generate_moz_extension/src/main.rs b/pkgs/by-name/ge/generate_moz_extension/src/main.rs new file mode 100644 index 00000000..bde986a3 --- /dev/null +++ b/pkgs/by-name/ge/generate_moz_extension/src/main.rs @@ -0,0 +1,138 @@ +use std::env::args; + +use anyhow::{bail, Context}; +use futures::StreamExt; +use reqwest::Client; +use serde_json::{json, Map, Value}; + +pub mod types; + +macro_rules! get_json_value { + ($key:expr, $json_value:ident, $type:ident, $get:ident) => { + match $json_value.get($key) { + Some(resp) => { + let resp = resp.to_owned(); + if resp.$type() { + resp.$get().expect( + "The should have been checked in the if guard, so unpacking here is fine", + ).to_owned() + } else { + bail!( + "Value {} => \n{}\n is not of type: {}", + $key, + resp, + stringify!($type) + ); + } + } + None => { + bail!( + "There seems to be no '{}' in your json data (json value: '{}')\n Has the api changend?", + $key, serde_json::to_string_pretty(&$json_value).expect("Will always work") + ); + } + } + }; +} + +use futures::stream::futures_unordered::FuturesUnordered; +use types::{Extension, InputExtension}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let mut extensions: Vec<InputExtension> = vec![]; + for input_extension in args() + .skip(1) + .map(|str| InputExtension::try_from(str)) + .collect::<Vec<anyhow::Result<InputExtension>>>() + { + extensions.push(input_extension?); + } + + let resulting_extensions = process_extensions(extensions).await?; + + let mut output = Map::new(); + for extension in resulting_extensions { + output.insert(extension.pname.clone(), json!(extension)); + } + + println!( + "{}", + serde_json::to_string_pretty(&serde_json::Value::Object(output)).expect( + "This is constructed from json, it should also be possible to serialize it again" + ) + ); + Ok(()) +} + +async fn process_extensions(extensions: Vec<InputExtension>) -> anyhow::Result<Vec<Extension>> { + let mut output = Vec::with_capacity(extensions.len()); + + let client = Client::new(); + for extension in extensions + .iter() + .map(|ext| { + let local_client = &client; + index_extension(ext, local_client) + }) + .collect::<FuturesUnordered<_>>() + .collect::<Vec<_>>() + .await + { + output.push(extension?); + } + Ok(output) +} + +async fn index_extension(extension: &InputExtension, client: &Client) -> anyhow::Result<Extension> { + let response = client + .get(format!( + "https://addons.mozilla.org/api/v5/addons/addon/{}", + extension, + )) + .send() + .await + .context("Accessing the mozzila extenios api failed with error: {e}")?; + + eprintln!("Indexing {} ({})...", extension, response.status()); + let response: Value = serde_json::from_str( + &response + .text() + .await + .context("Turning the response to text fail with error: {e}")?, + ) + .context("Deserializing the response failed! Error: {e}")?; + + if let Some(detail) = response.get("detail") { + if detail == "Not found." { + bail!("Your extension ('{}') was not found!", extension); + } + }; + + let release = { get_json_value!("current_version", response, is_object, as_object) }; + + #[allow(non_snake_case)] + let addonId = { get_json_value!("guid", response, is_string, as_str) }; + + let version = { get_json_value!("version", release, is_string, as_str) }; + let file = { get_json_value!("file", release, is_object, as_object) }; + + let url = { get_json_value!("url", file, is_string, as_str) }; + let sha256 = { + let hash = get_json_value!("hash", file, is_string, as_str); + if hash.starts_with("sha256:") { + hash + } else { + bail!("This hash type is unhandled: {}", hash); + } + }; + + Ok(Extension { + pname: extension.moz_name.clone(), + default_area: extension.default_area, + version, + addonId, + url, + sha256, + }) +} diff --git a/pkgs/by-name/ge/generate_moz_extension/src/types.rs b/pkgs/by-name/ge/generate_moz_extension/src/types.rs new file mode 100644 index 00000000..b830fe0d --- /dev/null +++ b/pkgs/by-name/ge/generate_moz_extension/src/types.rs @@ -0,0 +1,71 @@ +use std::fmt::Display; + +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Extension { + pub pname: String, + pub default_area: DefaultArea, + pub version: String, + pub addonId: String, + pub url: String, + pub sha256: String, +} + +#[derive(Debug, Clone)] +pub struct InputExtension { + pub moz_name: String, + pub default_area: DefaultArea, +} +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[allow(non_camel_case_types)] +pub enum DefaultArea { + navbar, + menupanel, +} + +impl Display for DefaultArea { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DefaultArea::navbar => f.write_str("navbar"), + DefaultArea::menupanel => f.write_str("menupanel"), + } + } +} + +impl TryFrom<&str> for DefaultArea { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result<Self, Self::Error> { + match value { + "navbar" => Ok(Self::navbar), + "menupanel" => Ok(Self::menupanel), + _ => Err(anyhow!( + "Your <default_area> needs to be one of 'navbar' or 'menupanel', but is: '{}'", + value + )), + } + } +} + +impl Display for InputExtension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.moz_name) + } +} +impl TryFrom<String> for InputExtension { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result<Self, Self::Error> { + if let Some((moz_name, default_area)) = value.split_once(':') { + Ok(Self { + moz_name: moz_name.to_owned(), + default_area: default_area.try_into()?, + }) + } else { + Err(anyhow!("Can't parse the input string as a InputExtension!\n Needs to be: '<moz_name>:<default_area>'")) + } + } +} |