diff --git a/Cargo.toml b/Cargo.toml index 37458b6..b99b3a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ license = "ACSL" reqwest = { version = "0.11.22", features = ["default-tls", "json", "gzip"] } serde = { version = "1.0.190", features = ["derive"] } tokio = { version = "1.33.0", features = ["rt-multi-thread", "macros", "net"] } + +[[bin]] +name = "estrus" diff --git a/README.md b/README.md new file mode 100644 index 0000000..19aba1d --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Estrus Elixir Navigator +=== + +CLI navigator for the Elixir linux project. diff --git a/src/ident.rs b/src/ident.rs new file mode 100644 index 0000000..12e4f35 --- /dev/null +++ b/src/ident.rs @@ -0,0 +1,182 @@ +//! Structures structuring identifiers +//! + +use std::fmt::{self, Display}; +use std::iter::FromIterator; +use std::marker::PhantomData as Phantom; +use std::path::PathBuf; +use std::str::FromStr; + +use serde::de::{self, Deserializer, Visitor}; + +use reqwest::Client; + +/// Response to an query on the Elixir API +/// +/// Holds the deserialized response to a query to the elixir API regarding definitions, references, +/// and documentation of an identifier. +#[derive(serde::Deserialize, Debug, Default)] +pub struct Response { + /// List of definitions + definitions: Vec, + /// List of references + references: Vec, + /// List of positions in the documentation + documentations: Vec, +} + +impl Response { + /// Retrieve definitions of the identifier + pub fn get_definitions(&self) -> &[Definition] { + &self.definitions + } + + /// Retrieve references of the identifier + pub fn get_references(&self) -> &[Reference] { + &self.references + } + + /// Retrieve documentation for the identifier + pub fn get_documentations(&self) -> &[Documentation] { + &self.documentations + } +} + +#[derive(serde::Deserialize, Debug)] +/// Different possible types of ifier Definitions +pub enum DefinitionType { + #[serde(rename(deserialize = "prototype"))] + Prototype, + #[serde(rename(deserialize = "member"))] + Member, + #[serde(rename(deserialize = "function"))] + Function, + #[serde(rename(deserialize = "enumerator"))] + Enumerator, + #[serde(rename(deserialize = "struct"))] + Struct, + #[serde(rename(deserialize = "variable"))] + Variable, +} + +impl std::fmt::Display for DefinitionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::fmt::Display for Definition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{:^9}] {}:{}", + self.deftype.to_string(), + self.path.to_str().unwrap_or(""), + self.line + ) + } +} + +#[derive(serde::Deserialize, Debug)] +pub struct Definition { + path: PathBuf, + line: u32, + #[serde(rename(deserialize = "type"))] + deftype: DefinitionType, +} + +fn comma_separated<'de, V, T, D>(deserializer: D) -> Result +where + V: FromIterator, + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, +{ + struct CommaSeparated(Phantom, Phantom); + + impl<'de, V, T> Visitor<'de> for CommaSeparated + where + V: FromIterator, + T: FromStr, + T::Err: Display, + { + type Value = V; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string containing comma-separated elements") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + s.split(',') + .map(FromStr::from_str) + .collect::>() + .map_err(de::Error::custom) + } + } + + let visitor = CommaSeparated(Phantom, Phantom); + deserializer.deserialize_str(visitor) +} + +#[derive(serde::Deserialize, Debug)] +pub struct Reference { + path: PathBuf, + #[serde(deserialize_with = "comma_separated")] + line: Vec, + //#[serde(rename(serialize = "type"))] + //reftype: Option<()>, +} + +impl std::fmt::Display for Reference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}:{}", + self.path.to_str().unwrap_or(""), + self.line + .iter() + .map(u32::to_string) + .collect::>() + .join(",") + ) + } +} + +#[derive(serde::Deserialize, Debug)] +pub struct Documentation { + path: PathBuf, + #[serde(deserialize_with = "comma_separated")] + line: Vec, + //#[serde(rename(serialize = "type"))] + //doctype: Option<()>, +} + +impl std::fmt::Display for Documentation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}:{}", + self.path.to_str().unwrap_or(""), + self.line + .iter() + .map(u32::to_string) + .collect::>() + .join(",") + ) + } +} + +pub async fn find(ident: &str) -> Response { + let client = Client::new(); + let res = client + .get(format!( + "https://elixir.bootlin.com/api/ident/linux/{ident}?version=latest" + )) + .send() + .await + .expect("No future failure"); + res.json::().await.expect("No failure") +} diff --git a/src/main.rs b/src/main.rs index a8a0abe..0bd113d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,60 +1,40 @@ //! Main module for the project //! -use std::path::PathBuf; +#![deny(clippy::perf)] +#![deny(clippy::pedantic)] +#![deny(clippy::complexity)] +#![deny(clippy::correctness)] -use reqwest::Client; +use std::env::args; -/// Response to an Ident query on the Elixir API -/// -/// Holds the deserialized response to a query to the elixir API regarding definitions, references, -/// and documentation of an identifier. -#[derive(serde::Deserialize, Debug)] -struct IdentResponse { - /// List of definitions - definitions: Vec, - /// List of references - references: Vec, - /// List of positions in the documentation - documentations: Vec, -} - -#[derive(serde::Deserialize, Debug)] -/// Different possible types of Identifier Definitions -enum IdentDefinitionType { - Prototype, -} - -#[derive(serde::Deserialize, Debug)] -struct IdentDefinition { - path: PathBuf, - line: u32, - deftype: IdentDefinitionType, -} - -#[derive(serde::Deserialize, Debug)] -struct IdentReference { - path: PathBuf, - line: u32, - reftype: Option<()> -} - -#[derive(serde::Deserialize, Debug)] -struct IdentDocumentation { - path: PathBuf, - line: u32, - doctype: Option<()> -} - -async fn find_ident(ident: &str) -> IdentResponse { - let client = Client::new(); - let res = client.get(format!("https://elixir.bootlin.com/api/ident/linux/{ident}?version=latest")) - .send() - .await.expect("No future failure"); - res.json::().await.expect("No failure") -} +mod ident; #[tokio::main] async fn main() { - println!("{:?}", find_ident("clear_page_erms").await) + let arg_list = args().skip(1); + for arg in arg_list { + println!("{:=^60}", format!(" {arg} ")); + show_one_ident(&arg).await; + std::thread::sleep(std::time::Duration::from_millis(1000)); + println!("") + } +} + +async fn show_one_ident(ident: &str) { + let id_resp = ident::find(ident).await; + println!("Definitions:"); + for defs in id_resp.get_definitions() { + println!(" - {defs}"); + } + + println!("References:"); + for refs in id_resp.get_references() { + println!(" - {refs}"); + } + + println!("Documentation:"); + for docs in id_resp.get_documentations() { + println!(" - {docs}"); + } }