cp-cli 0.8.0

Human-first command-line client for competitive-programming platforms
Documentation
use crate::{
    domain::{
        Difficulty, Problem, ProblemList, ProblemQuery, ProblemSearch, ProblemSelector, ProblemTag,
    },
    error::Result,
};

#[cfg(feature = "leetcode")]
use crate::{
    domain::{ProblemId, ProblemSummary},
    error::Error,
};

pub(crate) async fn show(
    problem: ProblemSelector,
    progress: impl FnMut(usize, Option<u64>),
) -> Result<Problem> {
    #[cfg(feature = "leetcode")]
    {
        let mut progress = progress;
        let client = platform_leetcode::Client::new()?;
        let id = resolve_selector(&client, problem, &mut progress).await?;
        let problem = client.problem(id.as_ref(), progress).await?;
        from_leetcode(problem)
    }
    #[cfg(not(feature = "leetcode"))]
    {
        let _ = (problem, progress);
        Err(crate::Error::LeetCodeDisabled)
    }
}

#[cfg(feature = "leetcode")]
pub(crate) async fn resolve_selector(
    client: &platform_leetcode::Client,
    problem: ProblemSelector,
    progress: impl FnMut(usize, Option<u64>),
) -> Result<ProblemId> {
    match problem {
        ProblemSelector::Slug(id) => Ok(id),
        ProblemSelector::Number(number) => client
            .search(&number.to_string(), progress)
            .await?
            .problems
            .into_iter()
            .find(|problem| problem.number == number)
            .map(|problem| problem.id)
            .ok_or(Error::ProblemNumberNotFound)?
            .parse(),
    }
}

pub(crate) async fn daily(progress: impl FnMut(usize, Option<u64>)) -> Result<Problem> {
    #[cfg(feature = "leetcode")]
    {
        from_leetcode(platform_leetcode::Client::new()?.daily(progress).await?)
    }
    #[cfg(not(feature = "leetcode"))]
    {
        let _ = progress;
        Err(crate::Error::LeetCodeDisabled)
    }
}

pub(crate) async fn list(
    difficulty: Option<Difficulty>,
    tag: Option<ProblemTag>,
    progress: impl FnMut(usize, Option<u64>),
) -> Result<ProblemList> {
    #[cfg(feature = "leetcode")]
    {
        let list = platform_leetcode::Client::new()?
            .list(
                difficulty.map(to_leetcode_difficulty),
                tag.as_ref().map(AsRef::as_ref),
                progress,
            )
            .await?;
        Ok(ProblemList {
            difficulty,
            tag,
            total: list.total,
            results: summaries_from_leetcode(list.problems)?,
        })
    }
    #[cfg(not(feature = "leetcode"))]
    {
        let _ = (difficulty, tag, progress);
        Err(crate::Error::LeetCodeDisabled)
    }
}

pub(crate) async fn search(
    query: ProblemQuery,
    progress: impl FnMut(usize, Option<u64>),
) -> Result<ProblemSearch> {
    #[cfg(feature = "leetcode")]
    {
        let search = platform_leetcode::Client::new()?
            .search(query.as_ref(), progress)
            .await?;
        Ok(ProblemSearch {
            query,
            total: search.total,
            results: summaries_from_leetcode(search.problems)?,
        })
    }
    #[cfg(not(feature = "leetcode"))]
    {
        let _ = (query, progress);
        Err(crate::Error::LeetCodeDisabled)
    }
}

#[cfg(feature = "leetcode")]
fn summaries_from_leetcode(
    problems: Vec<platform_leetcode::ProblemSummary>,
) -> Result<Vec<ProblemSummary>> {
    problems
        .into_iter()
        .map(|problem| {
            Ok(ProblemSummary {
                number: problem.number,
                id: problem.id.parse()?,
                title: problem.title,
                difficulty: match problem.difficulty {
                    platform_leetcode::Difficulty::Easy => Difficulty::Easy,
                    platform_leetcode::Difficulty::Medium => Difficulty::Medium,
                    platform_leetcode::Difficulty::Hard => Difficulty::Hard,
                },
                paid_only: problem.paid_only,
            })
        })
        .collect()
}

#[cfg(feature = "leetcode")]
fn to_leetcode_difficulty(difficulty: Difficulty) -> platform_leetcode::Difficulty {
    match difficulty {
        Difficulty::Easy => platform_leetcode::Difficulty::Easy,
        Difficulty::Medium => platform_leetcode::Difficulty::Medium,
        Difficulty::Hard => platform_leetcode::Difficulty::Hard,
    }
}

#[cfg(feature = "leetcode")]
fn from_leetcode(problem: platform_leetcode::Problem) -> Result<Problem> {
    Ok(Problem {
        number: problem.number,
        id: problem.id.parse()?,
        title: problem.title,
        statement: problem.statement,
    })
}