use pyo3::prelude::*;
use pyo3::types::PyDict;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct MetaTags {
pub title: Option<String>,
pub description: Option<String>,
pub keywords: Option<Vec<String>>,
pub author: Option<String>,
pub generator: Option<String>,
pub canonical: Option<String>,
pub alternate: Vec<AlternateLink>,
pub feeds: Vec<FeedLink>,
pub shortlink: Option<String>,
pub icon: Option<String>, pub apple_touch_icon: Option<String>, pub manifest: Option<String>, pub prev: Option<String>, pub next: Option<String>,
pub robots: Option<RobotsDirective>,
pub googlebot: Option<RobotsDirective>,
pub viewport: Option<String>,
pub theme_color: Option<String>,
pub charset: Option<String>,
pub language: Option<String>,
pub application_name: Option<String>,
pub referrer: Option<String>,
pub google_site_verification: Option<String>,
pub google_signin_client_id: Option<String>,
pub msvalidate_01: Option<String>, pub yandex_verification: Option<String>,
pub p_domain_verify: Option<String>, pub facebook_domain_verification: Option<String>,
pub google_analytics: Option<String>,
pub fb_app_id: Option<String>,
pub fb_pages: Option<String>,
pub mobile_web_app_capable: Option<String>,
pub apple_mobile_web_app_capable: Option<String>, pub apple_mobile_web_app_status_bar_style: Option<String>, pub apple_mobile_web_app_title: Option<String>,
pub apple_itunes_app: Option<String>, pub google_play_app: Option<String>, pub format_detection: Option<String>,
pub msapplication_tile_color: Option<String>, pub msapplication_tile_image: Option<String>, pub msapplication_config: Option<String>, }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlternateLink {
pub href: String,
pub hreflang: Option<String>,
pub media: Option<String>,
pub r#type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeedLink {
pub href: String,
pub title: Option<String>,
pub r#type: String, }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct RobotsDirective {
pub index: Option<bool>, pub follow: Option<bool>, pub archive: Option<bool>, pub snippet: Option<bool>, pub translate: Option<bool>, pub imageindex: Option<bool>, pub raw: String, }
impl RobotsDirective {
pub fn parse(content: &str) -> Self {
let content_lower = content.to_lowercase();
let directives: Vec<&str> = content_lower.split(',').map(|s| s.trim()).collect();
let mut directive = RobotsDirective { raw: content.to_string(), ..Default::default() };
for d in directives {
match d {
"index" => directive.index = Some(true),
"noindex" => directive.index = Some(false),
"follow" => directive.follow = Some(true),
"nofollow" => directive.follow = Some(false),
"archive" => directive.archive = Some(true),
"noarchive" => directive.archive = Some(false),
"snippet" => directive.snippet = Some(true),
"nosnippet" => directive.snippet = Some(false),
"translate" => directive.translate = Some(true),
"notranslate" => directive.translate = Some(false),
"imageindex" => directive.imageindex = Some(true),
"noimageindex" => directive.imageindex = Some(false),
"all" => {
directive.index = Some(true);
directive.follow = Some(true);
}
"none" => {
directive.index = Some(false);
directive.follow = Some(false);
}
_ => {} }
}
directive
}
}
impl MetaTags {
pub fn to_py_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new_bound(py);
if let Some(ref v) = self.title {
dict.set_item("title", v).unwrap();
}
if let Some(ref v) = self.description {
dict.set_item("description", v).unwrap();
}
if let Some(ref v) = self.keywords {
dict.set_item("keywords", v.clone()).unwrap();
}
if let Some(ref v) = self.author {
dict.set_item("author", v).unwrap();
}
if let Some(ref v) = self.canonical {
dict.set_item("canonical", v).unwrap();
}
if let Some(ref v) = self.viewport {
dict.set_item("viewport", v).unwrap();
}
if let Some(ref v) = self.charset {
dict.set_item("charset", v).unwrap();
}
if let Some(ref v) = self.language {
dict.set_item("language", v).unwrap();
}
if let Some(ref v) = self.theme_color {
dict.set_item("theme_color", v).unwrap();
}
if let Some(ref v) = self.generator {
dict.set_item("generator", v).unwrap();
}
if let Some(ref v) = self.application_name {
dict.set_item("application_name", v).unwrap();
}
if let Some(ref v) = self.referrer {
dict.set_item("referrer", v).unwrap();
}
if let Some(ref v) = self.shortlink {
dict.set_item("shortlink", v).unwrap();
}
if let Some(ref v) = self.icon {
dict.set_item("icon", v).unwrap();
}
if let Some(ref v) = self.apple_touch_icon {
dict.set_item("apple_touch_icon", v).unwrap();
}
if let Some(ref v) = self.manifest {
dict.set_item("manifest", v).unwrap();
}
if let Some(ref v) = self.prev {
dict.set_item("prev", v).unwrap();
}
if let Some(ref v) = self.next {
dict.set_item("next", v).unwrap();
}
if let Some(ref v) = self.google_site_verification {
dict.set_item("google_site_verification", v).unwrap();
}
if let Some(ref v) = self.google_signin_client_id {
dict.set_item("google_signin_client_id", v).unwrap();
}
if let Some(ref v) = self.msvalidate_01 {
dict.set_item("msvalidate_01", v).unwrap();
}
if let Some(ref v) = self.yandex_verification {
dict.set_item("yandex_verification", v).unwrap();
}
if let Some(ref v) = self.p_domain_verify {
dict.set_item("p_domain_verify", v).unwrap();
}
if let Some(ref v) = self.facebook_domain_verification {
dict.set_item("facebook_domain_verification", v).unwrap();
}
if let Some(ref v) = self.google_analytics {
dict.set_item("google_analytics", v).unwrap();
}
if let Some(ref v) = self.fb_app_id {
dict.set_item("fb_app_id", v).unwrap();
}
if let Some(ref v) = self.fb_pages {
dict.set_item("fb_pages", v).unwrap();
}
if let Some(ref v) = self.mobile_web_app_capable {
dict.set_item("mobile_web_app_capable", v).unwrap();
}
if let Some(ref v) = self.apple_mobile_web_app_capable {
dict.set_item("apple_mobile_web_app_capable", v).unwrap();
}
if let Some(ref v) = self.apple_mobile_web_app_status_bar_style {
dict.set_item("apple_mobile_web_app_status_bar_style", v).unwrap();
}
if let Some(ref v) = self.apple_mobile_web_app_title {
dict.set_item("apple_mobile_web_app_title", v).unwrap();
}
if let Some(ref v) = self.apple_itunes_app {
dict.set_item("apple_itunes_app", v).unwrap();
}
if let Some(ref v) = self.google_play_app {
dict.set_item("google_play_app", v).unwrap();
}
if let Some(ref v) = self.format_detection {
dict.set_item("format_detection", v).unwrap();
}
if let Some(ref v) = self.msapplication_tile_color {
dict.set_item("msapplication_tile_color", v).unwrap();
}
if let Some(ref v) = self.msapplication_tile_image {
dict.set_item("msapplication_tile_image", v).unwrap();
}
if let Some(ref v) = self.msapplication_config {
dict.set_item("msapplication_config", v).unwrap();
}
if let Some(ref robots) = self.robots {
dict.set_item("robots", robots.to_py_dict(py)).unwrap();
}
if let Some(ref googlebot) = self.googlebot {
dict.set_item("googlebot", googlebot.to_py_dict(py)).unwrap();
}
if !self.alternate.is_empty() {
let alternates: Vec<_> = self.alternate.iter().map(|a| a.to_py_dict(py)).collect();
dict.set_item("alternate", alternates).unwrap();
}
if !self.feeds.is_empty() {
let feeds: Vec<_> = self.feeds.iter().map(|f| f.to_py_dict(py)).collect();
dict.set_item("feeds", feeds).unwrap();
}
dict.unbind()
}
}
impl RobotsDirective {
pub fn to_py_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new_bound(py);
dict.set_item("raw", &self.raw).unwrap();
if let Some(v) = self.index {
dict.set_item("index", v).unwrap();
}
if let Some(v) = self.follow {
dict.set_item("follow", v).unwrap();
}
if let Some(v) = self.archive {
dict.set_item("archive", v).unwrap();
}
if let Some(v) = self.snippet {
dict.set_item("snippet", v).unwrap();
}
if let Some(v) = self.translate {
dict.set_item("translate", v).unwrap();
}
if let Some(v) = self.imageindex {
dict.set_item("imageindex", v).unwrap();
}
dict.unbind()
}
}
impl AlternateLink {
pub fn to_py_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new_bound(py);
dict.set_item("href", &self.href).unwrap();
if let Some(ref v) = self.hreflang {
dict.set_item("hreflang", v).unwrap();
}
if let Some(ref v) = self.media {
dict.set_item("media", v).unwrap();
}
if let Some(ref v) = self.r#type {
dict.set_item("type", v).unwrap();
}
dict.unbind()
}
}
impl FeedLink {
pub fn to_py_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new_bound(py);
dict.set_item("href", &self.href).unwrap();
dict.set_item("type", &self.r#type).unwrap();
if let Some(ref v) = self.title {
dict.set_item("title", v).unwrap();
}
dict.unbind()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_robots_directive_parse_index() {
let directive = RobotsDirective::parse("index, follow");
assert_eq!(directive.index, Some(true));
assert_eq!(directive.follow, Some(true));
}
#[test]
fn test_robots_directive_parse_noindex() {
let directive = RobotsDirective::parse("noindex, nofollow");
assert_eq!(directive.index, Some(false));
assert_eq!(directive.follow, Some(false));
}
#[test]
fn test_robots_directive_parse_all() {
let directive = RobotsDirective::parse("all");
assert_eq!(directive.index, Some(true));
assert_eq!(directive.follow, Some(true));
}
#[test]
fn test_robots_directive_parse_none() {
let directive = RobotsDirective::parse("none");
assert_eq!(directive.index, Some(false));
assert_eq!(directive.follow, Some(false));
}
#[test]
fn test_robots_directive_parse_mixed() {
let directive = RobotsDirective::parse("noindex, follow, nosnippet");
assert_eq!(directive.index, Some(false));
assert_eq!(directive.follow, Some(true));
assert_eq!(directive.snippet, Some(false));
}
}