Compare commits
	
		
			No commits in common. "92cc323f9359ecf7a29e2bc2308c8b8c6f5f3fdc" and "f5a6b9d6bad33ab6e28f20397415680d8190995e" have entirely different histories.
		
	
	
		
			92cc323f93
			...
			f5a6b9d6ba
		
	
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| /target |  | ||||||
							
								
								
									
										20
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -1,20 +0,0 @@ | |||||||
| cargo-features = ["edition2024"] |  | ||||||
| 
 |  | ||||||
| [package] |  | ||||||
| name = "rss2json" |  | ||||||
| version = "0.1.0" |  | ||||||
| edition = "2024" |  | ||||||
| resolver = "2" |  | ||||||
| authors = ["François Girault <fgirault@gmail.com>"] |  | ||||||
| 
 |  | ||||||
| [dependencies] |  | ||||||
| rss = "2.0" |  | ||||||
| url = "2.5.2" |  | ||||||
| json = "0.12.4" |  | ||||||
| clap = { version = "4.5.9", features = ["derive"] } |  | ||||||
| chrono = "0.4.38" |  | ||||||
| env_logger = "0.11.3" |  | ||||||
| log = "0.4.22" |  | ||||||
| reqwest = { version = "0.12.5", features = ["blocking"] } |  | ||||||
| regex = "1.10.5" |  | ||||||
| 
 |  | ||||||
							
								
								
									
										71
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								README.md
									
									
									
									
									
								
							| @ -1,72 +1,3 @@ | |||||||
| # rss2json | # rss2json | ||||||
| 
 | 
 | ||||||
| rss2json converts rss xml feeds to a json format. | My Rust tutorial | ||||||
| 
 |  | ||||||
| ## About |  | ||||||
| 
 |  | ||||||
| This is my first Rust tutorial project, so surely not coded with all the best practices. |  | ||||||
| 
 |  | ||||||
| Output format is a complete personnal choice, inspired by some formats I've seen when using other's api.  |  | ||||||
| 
 |  | ||||||
| >It handles a *very* minimal subset and not pretends to fullfill industrial needs :) |  | ||||||
| 
 |  | ||||||
| ## Installation |  | ||||||
| 
 |  | ||||||
| Setup Rust using rust-up, then run in a terminal: |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| cargo install  --git https://git.tetalab.org/Mutah/rss2json.git |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| Usage: rss2json.exe [OPTIONS] --input <INPUT> |  | ||||||
| 
 |  | ||||||
| Options: |  | ||||||
|   -i, --input <INPUT> |  | ||||||
|   -o, --output <OUTPUT>  [default: ] |  | ||||||
|   -p, --pretty |  | ||||||
|   -h, --help             Print help |  | ||||||
|   -V, --version          Print version |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Local file |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| rss2json -i some_downloaded_rss_file.xml |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Remote |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| rss2json -i https://git.tetalab.org/Mutah/rss2json.rss |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Output format |  | ||||||
| 
 |  | ||||||
| Only translate a minimal subset of attributes (if available). |  | ||||||
| 
 |  | ||||||
| ```javascript |  | ||||||
| { |  | ||||||
|     "channel": { |  | ||||||
|         "title": "", |  | ||||||
|         "link": "", |  | ||||||
|         "description": "", |  | ||||||
|         "last_build_date": "", |  | ||||||
|         "language": "", |  | ||||||
|         "copyright": "", |  | ||||||
|         "generator": "" |  | ||||||
|     }, |  | ||||||
|     "items": [ |  | ||||||
|         { |  | ||||||
|         "title": "", |  | ||||||
|         "link": "", |  | ||||||
|         "pub_date": "Sun, 14 Jul 2024 19:53:25 +0200", |  | ||||||
|         "pub_ts": 1720979605, |  | ||||||
|         "description": "",         |  | ||||||
|         "categories": ["", ""] |  | ||||||
|         } |  | ||||||
|     ] |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
							
								
								
									
										145
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,145 +0,0 @@ | |||||||
| /**
 |  | ||||||
|  * A simple personnal tutorial for my first baby steps with Rust. |  | ||||||
|  */ |  | ||||||
| use std::fs; |  | ||||||
| use std::fs::File; |  | ||||||
| use std::io::{BufReader, Cursor}; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| use chrono::DateTime; |  | ||||||
| use clap::Parser; |  | ||||||
| use json::object; |  | ||||||
| use rss::Channel; |  | ||||||
| 
 |  | ||||||
| // use log::debug;
 |  | ||||||
| use log::error; |  | ||||||
| use log::info; |  | ||||||
| // use log::warn;
 |  | ||||||
| 
 |  | ||||||
| #[derive(Parser, Debug)] |  | ||||||
| #[clap(author, version, about, long_about = None)] |  | ||||||
| struct Args { |  | ||||||
|     #[clap(short, long, required = true)] |  | ||||||
|     input: String, |  | ||||||
| 
 |  | ||||||
|     #[clap(short, long, default_value_t=String::new())] |  | ||||||
|     output: String, |  | ||||||
| 
 |  | ||||||
|     #[clap(short, long, default_value_t = false)] |  | ||||||
|     pretty: bool, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn main() -> std::io::Result<()> { |  | ||||||
|     env_logger::init(); |  | ||||||
| 
 |  | ||||||
|     let args = Args::parse(); |  | ||||||
| 
 |  | ||||||
|     // TODO handle remote http feed discovery by parsing index and search for the feed link
 |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
| 
 |  | ||||||
|     // output result
 |  | ||||||
|     let output_string: String = if args.pretty { |  | ||||||
|         data.pretty(4) |  | ||||||
|     } else { |  | ||||||
|         data.dump() |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if args.output.len() > 0 { |  | ||||||
|         let output_length = output_string.len(); |  | ||||||
|         let filename = args.output.to_string(); |  | ||||||
|         match fs::write(filename, output_string) { |  | ||||||
|             Ok(_) => { |  | ||||||
|                 info!("saving {} characters to {}", output_length, args.output); |  | ||||||
|             } |  | ||||||
|             Err(e) => { |  | ||||||
|                 error!("{:?}", e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } else { |  | ||||||
|         // no file name specified, dump json to stdout
 |  | ||||||
|         println!("{}", output_string); |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user