From ac67753f01b8a855514363f601c48fa4fc9ff820 Mon Sep 17 00:00:00 2001 From: fgirault Date: Sun, 14 Jul 2024 23:22:14 +0200 Subject: [PATCH] refactor: :recycle: Split main function into modules --- Cargo.toml | 3 +- rust-toolchain.toml | 2 + src/args.rs | 18 +++++++ src/channel_lib.rs | 30 +++++++++++ src/json_lib.rs | 68 +++++++++++++++++++++++++ src/main.rs | 118 ++++++-------------------------------------- 6 files changed, 135 insertions(+), 104 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 src/args.rs create mode 100644 src/channel_lib.rs create mode 100644 src/json_lib.rs diff --git a/Cargo.toml b/Cargo.toml index 142ea76..70ad08e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,6 @@ chrono = "0.4.38" env_logger = "0.11.3" log = "0.4.22" reqwest = { version = "0.12.5", features = ["blocking"] } -regex = "1.10.5" +# regex = "1.10.5" +# polodb_core = "4.4.1" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..6dda611 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,18 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + #[clap(short, long, required = true)] + pub input: String, + + #[clap(short, long, default_value_t=String::new())] + pub output: String, + + #[clap(short, long, default_value_t = false)] + pub pretty: bool, +} + +pub fn parse_args()-> Args { + return Args::parse(); +} \ No newline at end of file diff --git a/src/channel_lib.rs b/src/channel_lib.rs new file mode 100644 index 0000000..d29c201 --- /dev/null +++ b/src/channel_lib.rs @@ -0,0 +1,30 @@ +use std::fs::File; +use std::io::{BufReader, Cursor}; + +use log::{error, info}; +use rss::Channel; + +pub fn build_channel(input: String) -> Channel { + let channel: Channel; + + if input.starts_with("http://") || input.starts_with("https://") { + // process download + match reqwest::blocking::get(input).unwrap().bytes() { + Ok(content) => { + info!("Extracted {} bytes", content.len()); + let cursor = Cursor::new(content); + channel = Channel::read_from(cursor).unwrap(); + } + Err(e) => { + error!("{}", e); + std::process::exit(1); + } + } + } else { + // read locale file + let file = File::open(input).unwrap(); + channel = Channel::read_from(BufReader::new(file)).unwrap(); + } + + return channel; +} diff --git a/src/json_lib.rs b/src/json_lib.rs new file mode 100644 index 0000000..1dbc315 --- /dev/null +++ b/src/json_lib.rs @@ -0,0 +1,68 @@ +use chrono::DateTime; +use json::{object, JsonValue}; +use log::error; +use rss::Channel; + +pub fn build_json(channel: Channel) -> JsonValue { + // Initialize root object with channel informations, using object macro from json crate + let mut data = object! { + channel: object!{ + title: channel.title(), + link: channel.link(), + description: channel.description(), + last_build_date: channel.last_build_date(), + language: channel.language(), + copyright: channel.copyright(), + generator: channel.generator() + } + }; + + // declare an mutable array to populate it with channel item data + let mut items_data = json::JsonValue::new_array(); + + // populate the items array + for item in channel.items() { + let pub_datetime = DateTime::parse_from_rfc2822(item.pub_date().unwrap()).unwrap(); + + // create object to hold item data + let mut item_data = object! { + title: item.title(), + link: item.link(), + pub_date: item.pub_date(), + pub_ts: pub_datetime.timestamp(), + description: item.description(), + // author: item.author() + }; + + // populate categories + let mut categories = json::JsonValue::new_array(); + + for category in item.categories() { + match categories.push(category.name()) { + Ok(_) => {} + Err(e) => { + // memory overflow ? as a beginner, style puzzled by rust + error!("Error pushing to items_data {}", e); + } + } + } + + item_data["categories"] = categories; + + if item.content().is_some() { + item_data["content"] = json::JsonValue::String(item.content().unwrap().to_string()); + } + + match items_data.push(item_data) { + Ok(_) => {} + Err(e) => { + // memory overflow ? as a beginner, style puzzled by rust + error!("Error pushing to items_data {}", e); + } + } + } + + // attach the items to the json data root + data["items"] = items_data; + return data; +} diff --git a/src/main.rs b/src/main.rs index dbe047b..84f32c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,122 +1,34 @@ /** * A simple personnal tutorial for my first baby steps with Rust. */ +// TODO refactor and unit tests to improve code while knowledge grows ! +// TODO handle remote http feed discovery by parsing index and search for the feed link +// TODO serialize to local document database CRUD use std::fs; -use std::fs::File; -use std::io::{BufReader, Cursor}; - -use chrono::DateTime; -use clap::Parser; -use json::object; +use json::JsonValue; use rss::Channel; -// use log::debug; -use log::error; -use log::info; -// use log::warn; +// use log::{debug, info, warn, error }; +use log::{error, info}; -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(short, long, required = true)] - input: String, +mod args; +use crate::args::parse_args; - #[clap(short, long, default_value_t=String::new())] - output: String, +mod channel_lib; +use crate::channel_lib::build_channel; - #[clap(short, long, default_value_t = false)] - pretty: bool, -} +mod json_lib; +use crate::json_lib::build_json; fn main() -> std::io::Result<()> { env_logger::init(); - let args = Args::parse(); + let args = parse_args(); - // TODO handle remote http feed discovery by parsing index and search for the feed link + let channel: Channel = build_channel(args.input); - let channel: Channel; - - if args.input.starts_with("http://") || args.input.starts_with("https://") { - // process download - match reqwest::blocking::get(args.input).unwrap().bytes() { - Ok(content) => { - info!("Extracted {} bytes", content.len()); - let cursor = Cursor::new(content); - channel = Channel::read_from(cursor).unwrap(); - } - Err(e) => { - error!("{}", e); - std::process::exit(1); - } - } - } else { - // read locale file - let file = File::open(args.input).unwrap(); - channel = Channel::read_from(BufReader::new(file)).unwrap(); - } - - // Initialize root object with channel informations, using object macro from json crate - let mut data = object! { - channel: object!{ - title: channel.title(), - link: channel.link(), - description: channel.description(), - last_build_date: channel.last_build_date(), - language: channel.language(), - copyright: channel.copyright(), - generator: channel.generator() - } - }; - - // declare an mutable array to populate it with channel item data - let mut items_data = json::JsonValue::new_array(); - - // populate the items array - for item in channel.items() { - let pub_datetime = DateTime::parse_from_rfc2822(item.pub_date().unwrap()).unwrap(); - - // create object to hold item data - let mut item_data = object! { - title: item.title(), - link: item.link(), - pub_date: item.pub_date(), - pub_ts: pub_datetime.timestamp(), - description: item.description(), - // author: item.author() - }; - - // populate categories - let mut categories = json::JsonValue::new_array(); - - for category in item.categories() { - match categories.push(category.name()) { - Ok(_) => {} - Err(e) => { - // memory overflow ? as a beginner, style puzzled by rust - error!("Error pushing to items_data {}", e); - } - } - } - - item_data["categories"] = categories; - - if item.content().is_some() { - item_data["content"] = json::JsonValue::String(item.content().unwrap().to_string()); - } - - match items_data.push(item_data) { - Ok(_) => {} - Err(e) => { - // memory overflow ? as a beginner, style puzzled by rust - error!("Error pushing to items_data {}", e); - } - } - } - - // attach the items to the json data root - data["items"] = items_data; + let data: JsonValue = build_json(channel); // output result let output_string: String = if args.pretty {