Merge pull request #8 from Trivernis/develop

OR-Chains
main
Julius Riegel 3 years ago committed by GitHub
commit 4f72875787
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
[package]
name = "hydrus-api"
version = "0.5.0"
version = "0.6.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
license = "Apache-2.0"
@ -16,11 +16,12 @@ reqwest = {version = "0.11.4", features = ["json"]}
log = "0.4.14"
mime = "0.3.16"
chrono = "0.4.19"
regex = "1.5.4"
lazy_static = "1.4.0"
[dev-dependencies]
env_logger = "0.8.4"
maplit = "1.0.2"
lazy_static = "1.4.0"
[dev-dependencies.tokio]
version = "1.8.0"

@ -24,6 +24,7 @@ use hydrus_api::wrapper::service::ServiceName;
use hydrus_api::wrapper::hydrus_file::FileStatus;
use hydrus_api::wrapper::page::PageIdentifier;
use hydrus_api::wrapper::builders::search_builder::SortType;
use hydrus_api::wrapper::builders::or_chain_builder::OrChainBuilder;
use hydrus_api::wrapper::builders::tag_builder::{
SystemTagBuilder, Comparator
};
@ -38,6 +39,12 @@ async fn main() {
.add_tag(Tag::from("character:megumin"))
.add_tag(SystemTagBuilder::new().archive().build())
.add_tag(SystemTagBuilder::new().number_of_tags(Comparator::Greater, 12).build())
.add_or_chain(
OrChainBuilder::new()
.add_tag("summer".into())
.add_tag("winter".into())
.build(),
)
.sort(SortType::ModifiedTime)
.run().await.unwrap();

@ -22,11 +22,13 @@ use crate::api_core::managing_pages::{
};
use crate::api_core::searching_and_fetching_files::{
FileMetadata, FileMetadataResponse, FileSearchOptions, GetFile, SearchFiles,
SearchFilesResponse,
SearchFilesResponse, SearchQueryEntry,
};
use crate::api_core::Endpoint;
use crate::error::{Error, Result};
use crate::utils::{number_list_to_json_array, string_list_to_json_array};
use crate::utils::{
number_list_to_json_array, search_query_list_to_json_array, string_list_to_json_array,
};
use reqwest::Response;
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -225,12 +227,12 @@ impl Client {
/// Searches for files in the inbox, the archive or both
pub async fn search_files(
&self,
tags: Vec<String>,
query: Vec<SearchQueryEntry>,
options: FileSearchOptions,
) -> Result<SearchFilesResponse> {
log::trace!("Searching for files with tags {:?}", tags);
log::trace!("Searching for files with tags {:?}", query);
let mut args = options.into_query_args();
args.push(("tags", string_list_to_json_array(tags)));
args.push(("tags", search_query_list_to_json_array(query)));
self.get_and_parse::<SearchFiles, [(&str, String)]>(&args)
.await
}

@ -138,3 +138,18 @@ impl Endpoint for GetFile {
String::from("get_files/file")
}
}
#[derive(Clone, Debug)]
pub enum SearchQueryEntry {
Tag(String),
OrChain(Vec<String>),
}
impl<S> From<S> for SearchQueryEntry
where
S: ToString,
{
fn from(s: S) -> Self {
Self::Tag(s.to_string())
}
}

@ -14,6 +14,7 @@
//! use hydrus_api::wrapper::page::PageIdentifier;
//! use hydrus_api::wrapper::builders::tag_builder::{SystemTagBuilder, Comparator};
//! use hydrus_api::wrapper::builders::search_builder::SortType;
//! use hydrus_api::wrapper::builders::or_chain_builder::OrChainBuilder;
//!
//! # #[tokio::test]
//! # async fn doctest() {
@ -24,6 +25,12 @@
//! .add_tag(Tag::from("character:megumin"))
//! .add_tag(SystemTagBuilder::new().archive().build())
//! .add_tag(SystemTagBuilder::new().tag_namespace_as_number("page", Comparator::Equal, 5).negate().build())
//! .add_or_chain(
//! OrChainBuilder::new()
//! .add_tag("summer".into())
//! .add_tag("winter".into())
//! .build(),
//! )
//! .sort_by(SortType::NumberOfPixels)
//! .sort_descending()
//! .run().await.unwrap();

@ -1,7 +1,21 @@
use crate::api_core::common::FileIdentifier;
use crate::api_core::searching_and_fetching_files::SearchQueryEntry;
use crate::wrapper::tag::Tag;
use chrono::{Datelike, Duration};
/// Converts a list of Search Query entries into a json array
pub(crate) fn search_query_list_to_json_array(l: Vec<SearchQueryEntry>) -> String {
let entry_list: Vec<String> = l
.into_iter()
.map(|e| match e {
SearchQueryEntry::Tag(t) => format!("\"{}\"", t),
SearchQueryEntry::OrChain(c) => string_list_to_json_array(c),
})
.collect();
format!("[{}]", entry_list.join(","))
}
pub(crate) fn string_list_to_json_array(l: Vec<String>) -> String {
format!("[\"{}\"]", l.join("\",\""))
}

@ -1,4 +1,5 @@
pub mod import_builder;
pub mod tagging_builder;
pub mod tag_builder;
pub mod or_chain_builder;
pub mod search_builder;
pub mod tag_builder;
pub mod tagging_builder;

@ -0,0 +1,30 @@
use crate::wrapper::or_chain::OrChain;
use crate::wrapper::tag::Tag;
#[derive(Debug)]
pub struct OrChainBuilder {
tags: Vec<Tag>,
}
impl OrChainBuilder {
pub fn new() -> Self {
Self { tags: Vec::new() }
}
/// Adds a tag to the or expression
pub fn add_tag(mut self, tag: Tag) -> Self {
self.tags.push(tag);
self
}
/// Adds multiple tags to the or expression
pub fn add_tags(mut self, mut tags: Vec<Tag>) -> Self {
self.tags.append(&mut tags);
self
}
/// Builds the or chain
pub fn build(self) -> OrChain {
OrChain::new(self.tags)
}
}

@ -1,7 +1,7 @@
use crate::api_core::searching_and_fetching_files::FileSearchOptions;
use crate::api_core::searching_and_fetching_files::{FileSearchOptions, SearchQueryEntry};
use crate::error::Result;
use crate::utils::tag_list_to_string_list;
use crate::wrapper::hydrus_file::HydrusFile;
use crate::wrapper::or_chain::OrChain;
use crate::wrapper::service::ServiceName;
use crate::wrapper::tag::Tag;
use crate::Client;
@ -30,6 +30,7 @@ pub enum SortType {
pub struct SearchBuilder {
client: Client,
tags: Vec<Tag>,
or_chains: Vec<OrChain>,
options: FileSearchOptions,
}
@ -38,6 +39,7 @@ impl SearchBuilder {
Self {
client,
tags: Vec::new(),
or_chains: Vec::new(),
options: FileSearchOptions::new(),
}
}
@ -54,6 +56,12 @@ impl SearchBuilder {
self
}
/// Adds a new or chain
pub fn add_or_chain(mut self, chain: OrChain) -> Self {
self.or_chains.push(chain);
self
}
/// Sets the sort type
pub fn sort_by(mut self, sort_type: SortType) -> Self {
self.options = self.options.sort_type(sort_type as u8);
@ -101,9 +109,19 @@ impl SearchBuilder {
/// Runs the search
pub async fn run(self) -> Result<Vec<HydrusFile>> {
let client = self.client.clone();
let response = client
.search_files(tag_list_to_string_list(self.tags), self.options)
.await?;
let mut entries: Vec<SearchQueryEntry> = self
.tags
.into_iter()
.map(|t| SearchQueryEntry::Tag(t.to_string()))
.collect();
entries.append(
&mut self
.or_chains
.into_iter()
.map(|c| SearchQueryEntry::OrChain(c.into_string_list()))
.collect(),
);
let response = client.search_files(entries, self.options).await?;
let files = response
.file_ids
.into_iter()

@ -2,6 +2,7 @@ pub mod address;
pub mod builders;
pub mod hydrus;
pub mod hydrus_file;
pub mod or_chain;
pub mod page;
pub mod service;
pub mod tag;

@ -0,0 +1,54 @@
use crate::utils::tag_list_to_string_list;
use crate::wrapper::tag::Tag;
use lazy_static::lazy_static;
use regex::Regex;
#[derive(Clone, Debug, PartialOrd, PartialEq)]
pub struct OrChain {
tags: Vec<Tag>,
}
impl Eq for OrChain {}
impl OrChain {
/// Creates a new or chain directly from a list of tags
pub fn new(tags: Vec<Tag>) -> Self {
Self { tags }
}
/// Returns the tags of this or chain
pub fn tags(&self) -> &Vec<Tag> {
&self.tags
}
pub(crate) fn into_string_list(self) -> Vec<String> {
tag_list_to_string_list(self.tags)
}
}
impl<S> From<S> for OrChain
where
S: AsRef<str>,
{
fn from(s: S) -> Self {
lazy_static! {
static ref CHAIN_REGEX: Regex = Regex::new(r#"(\s|'|")or(\s|'|")"#).unwrap();
}
let s = s.as_ref().to_ascii_lowercase();
let tags = CHAIN_REGEX
.split(&s)
.map(|mut t| {
t = t
.trim_start()
.trim_start_matches("'")
.trim_start_matches("\"");
t = t.trim_end().trim_end_matches("'").trim_end_matches("\"");
t
})
.map(Tag::from)
.collect();
log::debug!("String parsed to or-chain {:?}", tags);
Self { tags }
}
}

@ -1,17 +1,20 @@
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialOrd, PartialEq)]
pub struct Tag {
pub negated: bool,
pub name: String,
pub namespace: Option<String>,
}
impl Eq for Tag {}
impl<S> From<S> for Tag
where
S: AsRef<str>,
{
fn from(value: S) -> Self {
let value = value.as_ref().trim();
let negated = value.strip_prefix("-").is_some();
let mut value = value.as_ref().trim();
let negated = value.starts_with("-");
value = value.trim_start_matches("-");
if let Some((namespace, tag)) = value.split_once(":") {
Self {
negated,

@ -1,7 +1,7 @@
use super::super::common;
use hydrus_api::api_core::common::FileIdentifier;
use hydrus_api::api_core::file_sort_type::SORT_FILE_PIXEL_COUNT;
use hydrus_api::api_core::searching_and_fetching_files::FileSearchOptions;
use hydrus_api::api_core::searching_and_fetching_files::{FileSearchOptions, SearchQueryEntry};
#[tokio::test]
async fn is_searches_files() {
@ -11,7 +11,13 @@ async fn is_searches_files() {
.tag_service_name("public tag repository")
.file_service_name("all known files");
client
.search_files(vec!["beach".to_string()], options)
.search_files(
vec![
"beach".into(),
SearchQueryEntry::OrChain(vec!["summer".to_string(), "winter".to_string()]),
],
options,
)
.await
.unwrap();
}

@ -2,6 +2,7 @@ mod test_address;
mod test_files;
mod test_hydrus;
mod test_import;
mod test_or_chain;
mod test_page;
mod test_service;
mod test_tags;

@ -1,6 +1,8 @@
use super::super::common;
use hydrus_api::api_core::adding_tags::TagAction;
use hydrus_api::wrapper::builders::or_chain_builder::OrChainBuilder;
use hydrus_api::wrapper::builders::search_builder::SortType;
use hydrus_api::wrapper::builders::tag_builder::TagBuilder;
use hydrus_api::wrapper::service::{ServiceName, ServiceType};
use hydrus_api::wrapper::url::UrlType;
@ -39,6 +41,13 @@ async fn it_searches() {
hydrus
.search()
.add_tag("character:megumin".into())
.add_or_chain(
OrChainBuilder::new()
.add_tag("summer".into())
.add_tag("winter".into())
.add_tag(TagBuilder::new("inside").negate().build())
.build(),
)
.sort_by(SortType::ModifiedTime)
.run()
.await

@ -0,0 +1,26 @@
use super::super::common;
use hydrus_api::wrapper::builders::or_chain_builder::OrChainBuilder;
use hydrus_api::wrapper::builders::tag_builder::TagBuilder;
use hydrus_api::wrapper::or_chain::OrChain;
#[test]
fn it_parses_from_string() {
common::setup();
let chain_string =
"'character:megumin' or 'character:aqua' OR '-character:hatsune miku'or 'terminator'";
let chain = OrChain::from(chain_string);
assert_eq!(
chain,
OrChainBuilder::new()
.add_tag("character:megumin".into())
.add_tag("character:aqua".into())
.add_tag(
TagBuilder::new("hatsune miku")
.namespace("character")
.negate()
.build()
)
.add_tag("terminator".into())
.build()
);
}
Loading…
Cancel
Save