Try to parse the cli commands

This commit is contained in:
seanomik 2022-06-15 22:29:15 -04:00
parent 5426c6186e
commit d2a22d6ee1
8 changed files with 3012 additions and 2949 deletions

12
.gitignore vendored
View File

@ -1,7 +1,7 @@
/target
.vscode
# Debug related directories that we don't want included
/torrents
/output
/target
.vscode
# Debug related directories that we don't want included
/torrents*
/output
config.toml

3992
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,17 @@
[package]
name = "cross-seed"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.19.2", features = ["full"] }
toml = "0.5.9"
lava_torrent = "0.7.0" # https://docs.rs/lava_torrent/0.7.0/lava_torrent/
torznab = "0.7.2" # https://docs.rs/torznab/0.7.2/torznab/
magnet-url = "2.0.0"
serde = { version = "1.0", features = ["derive"] }
figment = { version = "0.10", features = ["toml", "env"] }
wild = "2.0.4"
[package]
name = "cross-seed"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.19.2", features = ["full"] }
toml = "0.5.9"
lava_torrent = "0.7.0" # https://docs.rs/lava_torrent/0.7.0/lava_torrent/
torznab = "0.7.2" # https://docs.rs/torznab/0.7.2/torznab/
magnet-url = "2.0.0"
serde = { version = "1.0", features = ["derive"] }
figment = { version = "0.10", features = ["toml", "env"] }
wild = "2.0.4"
argmap = "1.1.2"

1348
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +1,157 @@
use std::sync::Arc;
use figment::{Provider, Metadata, Profile, Error};
use figment::value::{Map, Dict, Value, Tag};
use serde::Deserialize;
/// A provider that fetches its data from a given URL.
pub struct CliProvider {
/// The profile to emit data to if nesting is disabled.
profile: Option<Profile>,
args: Vec<std::string::String>,
}
impl CliProvider {
pub fn new() -> CliProvider {
CliProvider {
profile: None,
args: wild::args().collect(),
}
}
}
impl Provider for CliProvider {
/// Returns metadata with kind `Network`, custom source `self.url`,
/// and interpolator that returns a URL of `url/a/b/c` for key `a.b.c`.
fn metadata(&self) -> Metadata {
let args = &self.args;
Metadata::named("CLI Flags")
.source(args.join(" "))
//.source(args.map(|args| args.collect::<Vec<_>>().join(" ")).unwrap_or(String::default()))
/* .interpolater(move |profile, keys| match profile.is_custom() {
true => format!("{}/{}/{}", url, profile, keys.join("/")),
false => format!("{}/{}", url, keys.join("/")),
}) */
}
/// Fetches the data from `self.url`. Note that `Dict`, `Map`, and
/// `Profile` are `Deserialize`, so we can deserialized to them.
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
// Parse a `Value` from a `String`
fn parse_from_string(string: &String) -> Value {
// TODO: Other integer types
match string.parse::<i32>() {
Ok(i) => Value::Num(Tag::Default, figment::value::Num::I32(i)),
Err(_) => match string.parse::<bool>() {
Ok(b) => Value::Bool(Tag::Default, b),
Err(_) => Value::from(string.to_owned()),
},
}
}
fn fetch<'a, T: Deserialize<'a>>(args: &Vec<std::string::String>) -> Result<T, Error> {
let (args, argv) = argmap::parse(args.iter());
let mut dict = Dict::new();
for (key, vals) in argv {
let len = vals.len();
if len == 0 {
continue;
}
let key_vec: Vec<&str> = key.split(".").collect();
for key in key_vec.iter() {
dict.insert(key.to_owned(), Value::from(key.to_owned()));
}
if len == 1 {
dict.insert(key, parse_from_string(&vals[0]));
} else {
let mut values = Vec::new();
for val in &vals {
values.push(parse_from_string(val));
}
dict.insert(key, Value::Array(Tag::Default, values));
}
}
Ok(T::deserialize(dict).unwrap())
//Ok(T::deserialize(args.unwrap_or(&std::env::args()))?)
//Profile::default()
}
match &self.profile {
// Don't nest: `fetch` into a `Dict`.
Some(profile) => Ok(profile.collect(fetch(&self.args)?)),
// Nest: `fetch` into a `Map<Profile, Dict>`.
None => fetch(&self.args),
}
}
use std::sync::Arc;
use std::slice::Iter;
use figment::{Provider, Metadata, Profile, Error};
use figment::value::{Map, Dict, Value, Tag};
use serde::Deserialize;
/// A provider that fetches its data from a given URL.
pub struct CliProvider {
/// The profile to emit data to if nesting is disabled.
profile: Option<Profile>,
args: Vec<std::string::String>,
}
impl CliProvider {
pub fn new() -> CliProvider {
CliProvider {
profile: None,
args: wild::args().collect(),
}
}
}
impl Provider for CliProvider {
/// Returns metadata with kind `Network`, custom source `self.url`,
/// and interpolator that returns a URL of `url/a/b/c` for key `a.b.c`.
fn metadata(&self) -> Metadata {
let args = &self.args;
Metadata::named("CLI Flags")
.source(args.join(" "))
//.source(args.map(|args| args.collect::<Vec<_>>().join(" ")).unwrap_or(String::default()))
/* .interpolater(move |profile, keys| match profile.is_custom() {
true => format!("{}/{}/{}", url, profile, keys.join("/")),
false => format!("{}/{}", url, keys.join("/")),
}) */
}
/// Fetches the data from `self.url`. Note that `Dict`, `Map`, and
/// `Profile` are `Deserialize`, so we can deserialized to them.
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
// Parse a `Value` from a `String`
fn parse_from_string(string: &String) -> Value {
// TODO: Other integer types
match string.parse::<i32>() {
Ok(i) => Value::Num(Tag::Default, figment::value::Num::I32(i)),
Err(_) => match string.parse::<bool>() {
Ok(b) => Value::Bool(Tag::Default, b),
Err(_) => Value::from(string.to_owned()),
},
}
}
fn parse_keys(keys: &mut Iter<&str>, dict: &Dict, vals: &Vec<String>) -> Value {
let key = keys.next();
match key {
None => {
if vals.len() == 1 {
parse_from_string(&vals[0])
} else {
let mut values = Vec::new();
for val in vals.iter() {
values.push(parse_from_string(val));
}
Value::Array(Tag::Default, values)
}
},
Some(key) => {
let key = key.to_string();
println!("Key is {}", key);
println!("Dict is {:?}", dict);
match dict.get(&key) {
Some(val) => {
println!("Val is {:?}", val);
match val.as_dict() {
Some(dict) => parse_keys(keys, &dict, vals),
None => panic!("Expected a `Dict`, got some other value"),
}
//parse_keys(keys, &dict, vals)
},
None => {
let mut current_dict = Dict::new();
let val = parse_keys(keys, &current_dict, vals);
current_dict.insert(key.to_string(), val);
Value::from(current_dict)
}
}
/* let mut current_dict = Dict::new();
let val = parse_keys(keys, &current_dict, vals);
current_dict.insert(key.to_string(), val);
Value::from(current_dict) */
}
}
}
fn parse_cli(args: &Vec<std::string::String>)-> Result<Dict, Error> {
let (args, argv) = argmap::parse(args.iter());
let mut dict = Dict::new();
for (key, vals) in argv {
let len = vals.len();
if len == 0 {
continue;
}
let key_vec = key.split(".").collect::<Vec<_>>();
if key_vec.len() > 1 {
let mut key_iter = key_vec.iter();
//let key = key_iter.next();
let key = key_vec.first();
let val = parse_keys(&mut key_iter, &dict, &vals);
println!("Final val is {:?}", val);
dict.insert(key.unwrap().to_string(), val);
} else {
if len == 1 {
dict.insert(key, parse_from_string(&vals[0]));
} else {
let mut values = Vec::new();
for val in &vals {
values.push(parse_from_string(val));
}
dict.insert(key, Value::Array(Tag::Default, values));
}
}
println!("Dict: {:?}", dict);
}
Ok(dict)
}
match &self.profile {
// Don't nest: `fetch` into a `Dict`.
Some(profile) => Ok(profile.collect(parse_cli(&self.args)?)),
None => {
let mut map = Map::new();
map.insert(Profile::default(), parse_cli(&self.args)?);
Ok(map)
}
}
}
}

View File

@ -1,87 +1,87 @@
use serde::{Deserialize,Serialize};
use std::path::Path;
use std::env;
use std::collections::HashMap;
use figment::{Figment, providers::{Format, Toml, Env}};
use figment::value::Value as FigmentValue;
use super::CliProvider;
#[derive(Deserialize, Serialize)]
pub struct Config {
/// The path of the torrents to search.
torrents_path: String,
/// The output path of the torrents.
output_path: Option<String>,
//pub indexers: HashMap<String, Indexer>,
/// Used for deserializing the indexers into a Vec<Indexer>.
#[serde(rename = "indexers")]
indexers_map: HashMap<String, FigmentValue>,
/// The indexers to search.
#[serde(skip)]
pub indexers: Vec<Indexer>,
}
#[derive(Deserialize, Serialize)]
pub struct Indexer {
#[serde(skip_deserializing)]
pub name: String,
pub enabled: Option<bool>,
pub url: String,
pub api_key: String,
}
// Allow dead code for functions. We should probably remove this later on.
#[allow(dead_code)]
impl Config {
pub fn new() -> Config {
// The path of the config file without the file extension
let path = match env::var("CROSS_SEED_CONFIG") {
Ok(path) => path,
Err(_) => "config".to_string(),
};
// TODO: Create a command line argument `Provider` (https://docs.rs/figment/0.10.6/figment/trait.Provider.html)
// TODO: Figure out priority
// Merge the config files
let figment = Figment::new()
.join(Toml::file(format!("{}.toml", path)))
.join(Env::prefixed("CROSS_SEED_"))
.join(CliProvider::new());
let mut config: Config = figment.extract().unwrap();
// Parse the indexers map into a vector.
for (name, value) in &mut config.indexers_map {
let mut indexer: Indexer = value.deserialize().unwrap();
indexer.name = name.to_owned();
config.indexers.push(indexer);
}
config
}
pub fn torrents_path(&self) -> &Path {
Path::new(&self.torrents_path)
}
pub fn torrents_path_str(&self) -> &String {
&self.torrents_path
}
pub fn output_path(&self) -> Option<&Path> {
match self.output_path {
Some(ref path) => Some(Path::new(path)),
None => None,
}
}
pub fn output_path_str(&self) -> Option<&String> {
self.output_path.as_ref()
}
use serde::{Deserialize,Serialize};
use std::path::Path;
use std::env;
use std::collections::HashMap;
use figment::{Figment, providers::{Format, Toml, Env}};
use figment::value::Value as FigmentValue;
use super::CliProvider;
#[derive(Deserialize, Serialize)]
pub struct Config {
/// The path of the torrents to search.
torrents_path: String,
/// The output path of the torrents.
output_path: Option<String>,
//pub indexers: HashMap<String, Indexer>,
/// Used for deserializing the indexers into a Vec<Indexer>.
#[serde(rename = "indexers")]
indexers_map: HashMap<String, FigmentValue>,
/// The indexers to search.
#[serde(skip)]
pub indexers: Vec<Indexer>,
}
#[derive(Deserialize, Serialize)]
pub struct Indexer {
#[serde(skip_deserializing)]
pub name: String,
pub enabled: Option<bool>,
pub url: String,
pub api_key: String,
}
// Allow dead code for functions. We should probably remove this later on.
#[allow(dead_code)]
impl Config {
pub fn new() -> Config {
// The path of the config file without the file extension
let path = match env::var("CROSS_SEED_CONFIG") {
Ok(path) => path,
Err(_) => "config".to_string(),
};
// TODO: Create a command line argument `Provider` (https://docs.rs/figment/0.10.6/figment/trait.Provider.html)
// TODO: Figure out priority
// Merge the config files
let figment = Figment::new()
.join(CliProvider::new())
.join(Env::prefixed("CROSS_SEED_"))
.join(Toml::file(format!("{}.toml", path)));
let mut config: Config = figment.extract().unwrap();
// Parse the indexers map into a vector.
for (name, value) in &mut config.indexers_map {
let mut indexer: Indexer = value.deserialize().unwrap();
indexer.name = name.to_owned();
config.indexers.push(indexer);
}
config
}
pub fn torrents_path(&self) -> &Path {
Path::new(&self.torrents_path)
}
pub fn torrents_path_str(&self) -> &String {
&self.torrents_path
}
pub fn output_path(&self) -> Option<&Path> {
match self.output_path {
Some(ref path) => Some(Path::new(path)),
None => None,
}
}
pub fn output_path_str(&self) -> Option<&String> {
self.output_path.as_ref()
}
}

View File

@ -1,5 +1,5 @@
pub mod config;
pub use config::Config;
pub mod cli_provider;
pub mod config;
pub use config::Config;
pub mod cli_provider;
pub use cli_provider::CliProvider;

View File

@ -1,75 +1,75 @@
mod config;
use config::Config;
use std::path::{Path, PathBuf};
use std::error::Error;
use lava_torrent::torrent::v1::Torrent;
use torznab::Client as TorznabClient;
fn read_torrents(path: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let mut torrents = Vec::new();
for entry in path.read_dir()? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let filename = path.file_name().unwrap().to_str().unwrap();
if filename.ends_with(".torrent") {
torrents.push(path);
}
} else {
let mut inner = read_torrents(&path)?;
torrents.append(&mut inner);
}
}
return Ok(torrents);
}
fn main() {
// Get config and debug the torrents
let config = Config::new();//.expect("Failed to get config");
println!("Searching torrents in: {}", config.torrents_path_str());
println!("Searching {} trackers: ", config.indexers.len());
for indexer in config.indexers.iter() {
println!(" {}: {}", indexer.name, indexer.url);
}
let torrents = read_torrents(config.torrents_path()).unwrap();
for torrent_path in torrents.iter() {
let torrent = Torrent::read_from_file(torrent_path).unwrap();
println!("{}:", torrent.name);
/* for indexer in config.indexers.iter() {
if indexer.enabled {
let client = TorznabClient::new(indexer.url.clone());
let results = client.search(&torrent).unwrap();
println!("{}", results);
}
} */
//TorznabClient
/*if let Some(announce) = torrent.announce {
println!(" Announce: {}", announce);
}
if let Some(announce_list) = torrent.announce_list {
println!(" Announce list:");
for announce in announce_list {
for ann in announce {
println!(" {}", ann);
}
}
}
println!(" Files:");
if let Some(files) = torrent.files {
for file in files.iter() {
println!(" {}", file.path.to_str().unwrap());
}
} */
}
mod config;
use config::Config;
use std::path::{Path, PathBuf};
use std::error::Error;
use lava_torrent::torrent::v1::Torrent;
use torznab::Client as TorznabClient;
fn read_torrents(path: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let mut torrents = Vec::new();
for entry in path.read_dir()? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let filename = path.file_name().unwrap().to_str().unwrap();
if filename.ends_with(".torrent") {
torrents.push(path);
}
} else {
let mut inner = read_torrents(&path)?;
torrents.append(&mut inner);
}
}
return Ok(torrents);
}
fn main() {
// Get config and debug the torrents
let config = Config::new();//.expect("Failed to get config");
println!("Searching torrents in: {}", config.torrents_path_str());
println!("Searching {} trackers: ", config.indexers.len());
for indexer in config.indexers.iter() {
println!(" {}: {}", indexer.name, indexer.url);
}
let torrents = read_torrents(config.torrents_path()).unwrap();
for torrent_path in torrents.iter() {
let torrent = Torrent::read_from_file(torrent_path).unwrap();
println!("{}:", torrent.name);
/* for indexer in config.indexers.iter() {
if indexer.enabled {
let client = TorznabClient::new(indexer.url.clone());
let results = client.search(&torrent).unwrap();
println!("{}", results);
}
} */
//TorznabClient
/*if let Some(announce) = torrent.announce {
println!(" Announce: {}", announce);
}
if let Some(announce_list) = torrent.announce_list {
println!(" Announce list:");
for announce in announce_list {
for ann in announce {
println!(" {}", ann);
}
}
}
println!(" Files:");
if let Some(files) = torrent.files {
for file in files.iter() {
println!(" {}", file.path.to_str().unwrap());
}
} */
}
}