Refactor directory structure; Fix warnings
This commit is contained in:
parent
3806865ae4
commit
b496edf404
328
src/app.rs
328
src/app.rs
@ -1,24 +1,22 @@
|
||||
use std::time::Duration;
|
||||
pub mod common;
|
||||
pub mod seeding;
|
||||
pub mod snowballing;
|
||||
|
||||
use ratatui::{crossterm::event::KeyCode, widgets::ListState};
|
||||
use log::{error, info, warn};
|
||||
use ratatui::crossterm::event::KeyCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tokio::{
|
||||
sync::mpsc::{self, error::SendError},
|
||||
time::sleep,
|
||||
};
|
||||
|
||||
use crate::snowballing::{
|
||||
Publication, SnowballingHistory, SnowballingStep, get_publication_by_id,
|
||||
use crate::literature::{
|
||||
Publication, SnowballingHistory, get_publication_by_id,
|
||||
};
|
||||
|
||||
use log::{error, info, warn};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq)]
|
||||
pub enum ActivePane {
|
||||
IncludedPublications,
|
||||
#[default]
|
||||
PendingPublications,
|
||||
}
|
||||
use seeding::{SeedingAction, SeedingComponent};
|
||||
use snowballing::{SnowballingAction, SnowballingComponent};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum StatusMessage {
|
||||
@ -33,42 +31,6 @@ impl Default for StatusMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableListState {
|
||||
pub offset: usize,
|
||||
pub selected: Option<usize>,
|
||||
}
|
||||
|
||||
pub mod liststate_serde {
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn serialize<S>(
|
||||
state: &ListState,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let surrogate = SerializableListState {
|
||||
offset: state.offset(),
|
||||
selected: state.selected(),
|
||||
};
|
||||
surrogate.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<ListState, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = SerializableListState::deserialize(deserializer)?;
|
||||
Ok(ListState::default()
|
||||
.with_offset(s.offset)
|
||||
.with_selected(s.selected))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq, Copy, Clone)]
|
||||
pub enum Tab {
|
||||
#[default]
|
||||
@ -76,27 +38,8 @@ pub enum Tab {
|
||||
Snowballing,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SeedingAction {
|
||||
EnterChar(char),
|
||||
EnterBackspace,
|
||||
ClearInput,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SnowballingAction {
|
||||
SelectLeftPane,
|
||||
SelectRightPane,
|
||||
SearchForSelected,
|
||||
NextItem,
|
||||
PrevItem,
|
||||
ShowIncludedPublication(Publication),
|
||||
ShowPendingPublication(Publication),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GlobalAction {
|
||||
TriggerNextSnowballingStep,
|
||||
ShowStatusMessage(StatusMessage),
|
||||
ClearStatusMessage,
|
||||
AddIncludedPublication(Publication),
|
||||
@ -112,6 +55,14 @@ pub enum Action {
|
||||
Global(GlobalAction),
|
||||
}
|
||||
|
||||
pub trait Component<T> {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: T,
|
||||
tx: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>>;
|
||||
}
|
||||
|
||||
impl From<GlobalAction> for Action {
|
||||
fn from(action: GlobalAction) -> Self {
|
||||
Action::Global(action)
|
||||
@ -130,175 +81,6 @@ impl From<SeedingAction> for Action {
|
||||
}
|
||||
}
|
||||
|
||||
trait Component<T> {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: T,
|
||||
tx: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct SeedingComponent {
|
||||
pub input: String,
|
||||
#[serde(skip)]
|
||||
pub included_publications: Vec<Publication>,
|
||||
}
|
||||
|
||||
impl Component<SeedingAction> for SeedingComponent {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: SeedingAction,
|
||||
action_tx: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>> {
|
||||
match action {
|
||||
SeedingAction::ClearInput => {
|
||||
self.input.clear();
|
||||
Ok(())
|
||||
}
|
||||
SeedingAction::EnterChar(c) => {
|
||||
self.input.push(c);
|
||||
Ok(())
|
||||
}
|
||||
SeedingAction::EnterBackspace => {
|
||||
if self.input.len() > 0 {
|
||||
self.input.truncate(self.input.len() - 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct SnowballingComponent {
|
||||
#[serde(with = "liststate_serde")]
|
||||
pub included_list_state: ListState,
|
||||
#[serde(with = "liststate_serde")]
|
||||
pub pending_list_state: ListState,
|
||||
pub active_pane: ActivePane,
|
||||
/// Local component copy of the included publications list
|
||||
#[serde(skip)]
|
||||
pub included_publications: Vec<Publication>,
|
||||
/// Local component copy of the pending publications list
|
||||
#[serde(skip)]
|
||||
pub pending_publications: Vec<Publication>,
|
||||
}
|
||||
|
||||
impl SnowballingComponent {
|
||||
fn next_list_item(
|
||||
list_state: &mut ListState,
|
||||
publications: &Vec<Publication>,
|
||||
) {
|
||||
let i = match list_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= publications.len().wrapping_sub(1) {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
list_state.select(Some(i));
|
||||
}
|
||||
|
||||
fn prev_list_item(
|
||||
list_state: &mut ListState,
|
||||
publications: &Vec<Publication>,
|
||||
) {
|
||||
let i = match list_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
publications.len().wrapping_sub(1)
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
list_state.select(Some(i));
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<SnowballingAction> for SnowballingComponent {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: SnowballingAction,
|
||||
action_tx: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>> {
|
||||
match action {
|
||||
SnowballingAction::SelectLeftPane => {
|
||||
self.active_pane = ActivePane::IncludedPublications;
|
||||
|
||||
if let None = self.included_list_state.selected() {
|
||||
self.included_list_state.select(Some(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SnowballingAction::SelectRightPane => {
|
||||
self.active_pane = ActivePane::IncludedPublications;
|
||||
|
||||
if let None = self.included_list_state.selected() {
|
||||
self.included_list_state.select(Some(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SnowballingAction::SearchForSelected => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
if let Some(idx) = self.included_list_state.selected() {
|
||||
open::that(&self.included_publications[idx].id)
|
||||
.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
if let Some(idx) = self.pending_list_state.selected() {
|
||||
open::that(&self.pending_publications[idx].id).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
SnowballingAction::NextItem => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
Self::next_list_item(
|
||||
&mut self.included_list_state,
|
||||
&self.included_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
Self::next_list_item(
|
||||
&mut self.pending_list_state,
|
||||
&self.pending_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
SnowballingAction::PrevItem => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
Self::prev_list_item(
|
||||
&mut self.included_list_state,
|
||||
&self.included_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
Self::prev_list_item(
|
||||
&mut self.pending_list_state,
|
||||
&self.pending_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct AppState {
|
||||
// UI state
|
||||
@ -327,6 +109,7 @@ pub struct App {
|
||||
pub action_tx: &'static mpsc::UnboundedSender<Action>,
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! status_info {
|
||||
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
||||
$action_tx.send(
|
||||
@ -335,6 +118,7 @@ macro_rules! status_info {
|
||||
)
|
||||
};
|
||||
}
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! status_warn {
|
||||
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
||||
$action_tx.send(
|
||||
@ -343,6 +127,7 @@ macro_rules! status_warn {
|
||||
)
|
||||
};
|
||||
}
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! status_error {
|
||||
($action_tx:expr, $text:expr $(, $args:expr)*) => {
|
||||
$action_tx.send(
|
||||
@ -403,76 +188,6 @@ impl App {
|
||||
self.state.status_message = StatusMessage::Info("".to_string());
|
||||
Ok(())
|
||||
}
|
||||
GlobalAction::TriggerNextSnowballingStep => {
|
||||
// if self.pending_publications.len() > 0 {
|
||||
// warn!(
|
||||
// "The next snowballing step can only be initiated \
|
||||
// after screening all pending publications"
|
||||
// );
|
||||
// self.set_status_message(StatusMessage::Warning(
|
||||
// "The next snowballing step can only be initiated \
|
||||
// after screening all pending publications"
|
||||
// .to_string(),
|
||||
// ));
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// match self.snowballing_step {
|
||||
// SnowballingStep::Forward => {
|
||||
// // TODO: Implement
|
||||
// }
|
||||
// SnowballingStep::Backward => {
|
||||
// self.set_status_message(StatusMessage::Info(
|
||||
// "Fetching references...".to_string(),
|
||||
// ));
|
||||
//
|
||||
// // TODO: Find a way to not clone the publications
|
||||
// for publication in self.included_publications.clone() {
|
||||
// // TODO: In addition to the referenced_works do
|
||||
// // an API call for citations
|
||||
// for reference in &publication.referenced_works {
|
||||
// let api_link = format!(
|
||||
// "https://api.openalex.org/{}",
|
||||
// &reference[21..]
|
||||
// );
|
||||
// let publ = get_publication_by_id(
|
||||
// &api_link,
|
||||
// "an.tsouchlos@gmail.com",
|
||||
// )
|
||||
// .await;
|
||||
//
|
||||
// match publ {
|
||||
// Ok(publ) => {
|
||||
// self.pending_publications.push(publ)
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!(
|
||||
// "Failed to get publication\
|
||||
// metadata using OpenAlex API: \
|
||||
// {}",
|
||||
// err
|
||||
// );
|
||||
//
|
||||
// self.set_status_message(
|
||||
// StatusMessage::Error(format!(
|
||||
// "Failed to get publication\
|
||||
// metadata using OpenAlex API: \
|
||||
// {}",
|
||||
// err
|
||||
// )),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// self.set_status_message(StatusMessage::Info(
|
||||
// "Done".to_string(),
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
GlobalAction::SubmitSeedLink => {
|
||||
if !self
|
||||
.state
|
||||
@ -543,7 +258,6 @@ impl App {
|
||||
.push(publ.clone());
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
src/app/common.rs
Normal file
0
src/app/common.rs
Normal file
47
src/app/seeding.rs
Normal file
47
src/app/seeding.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc::{self, error::SendError};
|
||||
|
||||
use crate::{
|
||||
app::{Action, Component},
|
||||
literature::Publication,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SeedingAction {
|
||||
EnterChar(char),
|
||||
EnterBackspace,
|
||||
ClearInput,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct SeedingComponent {
|
||||
pub input: String,
|
||||
#[serde(skip)]
|
||||
pub included_publications: Vec<Publication>,
|
||||
}
|
||||
|
||||
impl Component<SeedingAction> for SeedingComponent {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: SeedingAction,
|
||||
_: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>> {
|
||||
match action {
|
||||
SeedingAction::ClearInput => {
|
||||
self.input.clear();
|
||||
Ok(())
|
||||
}
|
||||
SeedingAction::EnterChar(c) => {
|
||||
self.input.push(c);
|
||||
Ok(())
|
||||
}
|
||||
SeedingAction::EnterBackspace => {
|
||||
if self.input.len() > 0 {
|
||||
self.input.truncate(self.input.len() - 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
189
src/app/snowballing.rs
Normal file
189
src/app/snowballing.rs
Normal file
@ -0,0 +1,189 @@
|
||||
use ratatui::widgets::ListState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
|
||||
use crate::app::{Action, Component};
|
||||
use crate::literature::Publication;
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq)]
|
||||
pub enum ActivePane {
|
||||
IncludedPublications,
|
||||
#[default]
|
||||
PendingPublications,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SnowballingAction {
|
||||
SelectLeftPane,
|
||||
SelectRightPane,
|
||||
SearchForSelected,
|
||||
NextItem,
|
||||
PrevItem,
|
||||
ShowIncludedPublication(Publication),
|
||||
ShowPendingPublication(Publication),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializableListState {
|
||||
pub offset: usize,
|
||||
pub selected: Option<usize>,
|
||||
}
|
||||
|
||||
pub mod liststate_serde {
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn serialize<S>(
|
||||
state: &ListState,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let surrogate = SerializableListState {
|
||||
offset: state.offset(),
|
||||
selected: state.selected(),
|
||||
};
|
||||
surrogate.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<ListState, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = SerializableListState::deserialize(deserializer)?;
|
||||
Ok(ListState::default()
|
||||
.with_offset(s.offset)
|
||||
.with_selected(s.selected))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct SnowballingComponent {
|
||||
#[serde(with = "liststate_serde")]
|
||||
pub included_list_state: ListState,
|
||||
#[serde(with = "liststate_serde")]
|
||||
pub pending_list_state: ListState,
|
||||
pub active_pane: ActivePane,
|
||||
/// Local component copy of the included publications list
|
||||
#[serde(skip)]
|
||||
pub included_publications: Vec<Publication>,
|
||||
/// Local component copy of the pending publications list
|
||||
#[serde(skip)]
|
||||
pub pending_publications: Vec<Publication>,
|
||||
}
|
||||
|
||||
impl SnowballingComponent {
|
||||
fn next_list_item(
|
||||
list_state: &mut ListState,
|
||||
publications: &Vec<Publication>,
|
||||
) {
|
||||
let i = match list_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= publications.len().wrapping_sub(1) {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
list_state.select(Some(i));
|
||||
}
|
||||
|
||||
fn prev_list_item(
|
||||
list_state: &mut ListState,
|
||||
publications: &Vec<Publication>,
|
||||
) {
|
||||
let i = match list_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
publications.len().wrapping_sub(1)
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
list_state.select(Some(i));
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<SnowballingAction> for SnowballingComponent {
|
||||
fn handle_action(
|
||||
&mut self,
|
||||
action: SnowballingAction,
|
||||
_: &mpsc::UnboundedSender<Action>,
|
||||
) -> Result<(), SendError<Action>> {
|
||||
match action {
|
||||
SnowballingAction::SelectLeftPane => {
|
||||
self.active_pane = ActivePane::IncludedPublications;
|
||||
|
||||
if let None = self.included_list_state.selected() {
|
||||
self.included_list_state.select(Some(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SnowballingAction::SelectRightPane => {
|
||||
self.active_pane = ActivePane::IncludedPublications;
|
||||
|
||||
if let None = self.included_list_state.selected() {
|
||||
self.included_list_state.select(Some(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SnowballingAction::SearchForSelected => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
if let Some(idx) = self.included_list_state.selected() {
|
||||
open::that(&self.included_publications[idx].id)
|
||||
.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
if let Some(idx) = self.pending_list_state.selected() {
|
||||
open::that(&self.pending_publications[idx].id).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
SnowballingAction::NextItem => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
Self::next_list_item(
|
||||
&mut self.included_list_state,
|
||||
&self.included_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
Self::next_list_item(
|
||||
&mut self.pending_list_state,
|
||||
&self.pending_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
SnowballingAction::PrevItem => match self.active_pane {
|
||||
ActivePane::IncludedPublications => {
|
||||
Self::prev_list_item(
|
||||
&mut self.included_list_state,
|
||||
&self.included_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ActivePane::PendingPublications => {
|
||||
Self::prev_list_item(
|
||||
&mut self.pending_list_state,
|
||||
&self.pending_publications,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
use std::{error::Error, io, time::Duration};
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
use log::error;
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
|
||||
@ -136,10 +136,10 @@ impl Publication {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OpenAlexResponse {
|
||||
pub results: Vec<Publication>,
|
||||
}
|
||||
// #[derive(Serialize, Deserialize, Debug)]
|
||||
// pub struct OpenAlexResponse {
|
||||
// pub results: Vec<Publication>,
|
||||
// }
|
||||
|
||||
pub async fn get_publication_by_id(
|
||||
api_link: &str,
|
||||
@ -159,24 +159,24 @@ pub async fn get_publication_by_id(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// TODO: Get all papers, not just the first page
|
||||
pub async fn get_citing_papers(
|
||||
target_id: &str,
|
||||
email: &str,
|
||||
) -> Result<Vec<Publication>, Error> {
|
||||
let url = format!(
|
||||
"https://api.openalex.org/works?filter=cites:{}&mailto={}",
|
||||
target_id, email
|
||||
);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.get(url)
|
||||
.header("User-Agent", "Rust-OpenAlex-Client/1.0")
|
||||
.send()
|
||||
.await?
|
||||
.json::<OpenAlexResponse>()
|
||||
.await?;
|
||||
|
||||
Ok(response.results)
|
||||
}
|
||||
// // TODO: Get all papers, not just the first page
|
||||
// pub async fn get_citing_papers(
|
||||
// target_id: &str,
|
||||
// email: &str,
|
||||
// ) -> Result<Vec<Publication>, Error> {
|
||||
// let url = format!(
|
||||
// "https://api.openalex.org/works?filter=cites:{}&mailto={}",
|
||||
// target_id, email
|
||||
// );
|
||||
//
|
||||
// let client = reqwest::Client::new();
|
||||
// let response = client
|
||||
// .get(url)
|
||||
// .header("User-Agent", "Rust-OpenAlex-Client/1.0")
|
||||
// .send()
|
||||
// .await?
|
||||
// .json::<OpenAlexResponse>()
|
||||
// .await?;
|
||||
//
|
||||
// Ok(response.results)
|
||||
// }
|
||||
10
src/main.rs
10
src/main.rs
@ -1,11 +1,11 @@
|
||||
mod app;
|
||||
mod literature;
|
||||
mod ui;
|
||||
|
||||
use log::error;
|
||||
use serde_json;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod app;
|
||||
mod ui;
|
||||
use crate::app::{App, AppState, StatusMessage};
|
||||
mod snowballing;
|
||||
use crate::app::AppState;
|
||||
|
||||
// use crate::snowballing::get_citing_papers;
|
||||
// #[tokio::main]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user