Skip to content

Commit

Permalink
chore: move write op for readme in readme crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Angel-Dijoux committed Nov 26, 2024
1 parent 9746408 commit c011131
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 102 deletions.
104 changes: 51 additions & 53 deletions src/commands/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ use crate::{
},
error::{self, Result},
evaluate::evaluate,
file_utils::with_locked_file,
ipynb::{convert_submission_notebooks, convert_use_case_notebooks},
print::wrap_python_output,
python::LastRunResult,
readme::get_readme_path,
readme::{read_readme, write_readme},
};
use aqora_config::{AqoraUseCaseConfig, PyProject};
use aqora_config::{AqoraUseCaseConfig, PyProject, ReadMe};
use aqora_runner::{
pipeline::{EvaluateAllInfo, EvaluateInputInfo, EvaluationError, Pipeline, PipelineConfig},
python::PyEnv,
Expand All @@ -28,12 +27,11 @@ use pyo3::{exceptions::PyException, Python};
use serde::Serialize;
use std::{
collections::HashMap,
io::SeekFrom,
path::{Path, PathBuf},
pin::Pin,
sync::{atomic::AtomicU32, Arc},
};
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};

use url::Url;

#[derive(Args, Debug, Clone, Serialize)]
Expand Down Expand Up @@ -230,41 +228,49 @@ fn run_pipeline(
)
}

async fn update_score_badge_in_file(
path: PathBuf,
async fn update_score_badge_in_readme(
competition: Option<String>,
project_dir: impl AsRef<Path>,
readme: Option<&ReadMe>,
score: &Py<PyAny>,
url: Url,
) -> Result<()> {
let badge_url = build_shield_score_badge(
score,
url.join(&competition.map_or_else(
|| "competitons".to_string(),
|name| format!("competitions/{}", name),
))?,
url.join(&competition.unwrap_or_else(|| "competitions".to_string()))?,
)?;
with_locked_file(
|file| {
async move {
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
let updated_content = regex_replace_all!(
r"(?:<!--\s*aqora:score:start\s*-->.*?<!--\s*aqora:score:end\s*-->)|(!\[Aqora Score Badge\]\([^\)]+\))",
&contents,
badge_url.clone()
);
if updated_content != contents {
file.seek(SeekFrom::Start(0)).await?;
file.write_all(updated_content.as_bytes()).await?;
file.set_len(updated_content.len() as u64).await?;
}
Ok(())
}
.boxed()
},
path,
)
.await

if let Some(content) = read_readme(&project_dir, readme)
.await
.inspect_err(|e| {
tracing::warn!(
"Failed to read the README file: {}. Skipping badge update.",
e
)
})
.ok()
.flatten()
{
let updated_content = regex_replace_all!(
r"(?:<!--\s*aqora:score:start\s*-->.*?<!--\s*aqora:score:end\s*-->)|(!\[Aqora Score Badge\]\([^\)]+\))",
&content,
badge_url
);

if updated_content != content {
write_readme(&project_dir, &updated_content)
.await
.inspect_err(|e| {
tracing::warn!(
"Failed to write to the README file: {}. Skipping badge update.",
e
)
})
.ok();
}
}

Ok(())
}

pub async fn run_submission_tests(
Expand All @@ -278,17 +284,6 @@ pub async fn run_submission_tests(
.and_then(|aqora| aqora.as_submission())
.ok_or_else(|| error::user("Submission config is not valid", ""))?;
let project_config = read_project_config(&global.project).await?;
let project_readme_path = get_readme_path(
&global.project,
project.project.as_ref().and_then(|p| p.readme.as_ref()),
)
.await
.map_err(|err| {
error::user(
&format!("Could not read readme: {}", err),
"Please make sure the readme is valid",
)
})?;
let use_case_toml_path = project_use_case_toml_path(&global.project);
let data_path = project_data_dir(&global.project);
if !use_case_toml_path.exists() || !data_path.exists() {
Expand Down Expand Up @@ -423,15 +418,18 @@ pub async fn run_submission_tests(
"Score".if_supports_color(OwoStream::Stdout, |text| { text.bold() }),
score
));
if let Some(path) = project_readme_path {
update_score_badge_in_file(
global.project.join(path),
modified_use_case.competition,
&score,
global.aqora_url()?,
)
.await?;
};

update_score_badge_in_readme(
modified_use_case.competition,
&global.project,
project.project.as_ref().and_then(|p| p.readme.as_ref()),
&score,
global.aqora_url()?,
)
.await
.inspect_err(|_| tracing::warn!("Failed to add Aqora badge to README."))
.ok();

pipeline_pb.finish_and_clear();
Ok(score)
}
Expand Down
133 changes: 84 additions & 49 deletions src/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,79 @@ use aqora_config::ReadMe;
use mime::Mime;
use std::path::{Path, PathBuf};
use thiserror::Error;
use tokio::fs;

#[derive(Debug, Error)]
pub enum ReadmeError {
pub enum ReadMeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("README not found")]
#[error("Readme not found")]
NotFound,
#[error("README content type not supported. Only markdown and plaintext supported")]
#[error("Readme content type not supported. Only markdown and plaintext supported")]
ContentTypeNotSupported,
}

pub async fn get_readme_path(
project_dir: impl AsRef<Path>,
fn is_supported_mime(content_type: &str) -> Result<(), ReadMeError> {
let mime: Mime = content_type
.parse()
.map_err(|_| ReadMeError::ContentTypeNotSupported)?;
if mime.type_() == mime::TEXT && (mime.subtype() == mime::PLAIN || mime.subtype() == "markdown")
{
Ok(())
} else {
Err(ReadMeError::ContentTypeNotSupported)
}
}

fn is_supported_extension(extension: Option<&str>) -> bool {
match extension {
Some(ext) => {
let ext = ext.to_lowercase();
ext == "md" || ext == "txt"
}
None => true,
}
}

async fn find_readme_path(
project_dir: &Path,
readme: Option<&ReadMe>,
) -> Result<Option<PathBuf>, ReadmeError> {
let path = match readme {
Some(ReadMe::Table {
ref file,
text: _,
content_type,
}) => {
let path: Option<&Path> = file.as_deref().map(str::as_ref);
if let Some(content_type) = content_type {
let mime: Mime = content_type
.parse()
.map_err(|_| ReadmeError::ContentTypeNotSupported)?;
if !(mime.type_() == mime::TEXT
&& (mime.subtype() == mime::PLAIN || mime.subtype() == "markdown"))
{
return Err(ReadmeError::ContentTypeNotSupported);
) -> Result<Option<PathBuf>, ReadMeError> {
if let Some(readme) = readme {
match readme {
ReadMe::Table {
file,
text: _,
content_type,
} => {
if let Some(content_type) = content_type {
is_supported_mime(content_type)?;
}
if let Some(file) = file {
let path = project_dir.join(file);
if is_supported_extension(path.extension().and_then(|s| s.to_str())) {
return Ok(Some(path));
} else {
return Err(ReadMeError::ContentTypeNotSupported);
}
}
}
ReadMe::RelativePath(path) => {
let path = project_dir.join(path);
if is_supported_extension(path.extension().and_then(|s| s.to_str())) {
return Ok(Some(path));
} else {
return Err(ReadMeError::ContentTypeNotSupported);
}
}
path.map(|p| p.to_path_buf())
}
Some(ReadMe::RelativePath(ref path)) => Some(PathBuf::from(path)),
None => None,
};

if let Some(path) = path {
return Ok(Some(path));
}

let mut dir = tokio::fs::read_dir(&project_dir).await?;
let mut dir = fs::read_dir(project_dir).await?;
while let Some(entry) = dir.next_entry().await? {
match entry.file_name().to_string_lossy().to_lowercase().as_str() {
"readme.md" | "readme.txt" => {}
_ => continue,
}
let metadata = entry.metadata().await?;
if metadata.is_file() {
let file_name = entry.file_name().to_string_lossy().to_lowercase();
let readme_files = ["readme.md", "readme.txt", "readme"];
if readme_files.contains(&file_name.as_str()) && entry.metadata().await?.is_file() {
return Ok(Some(entry.path()));
}
}
Expand All @@ -62,24 +85,36 @@ pub async fn get_readme_path(
pub async fn read_readme(
project_dir: impl AsRef<Path>,
readme: Option<&ReadMe>,
) -> Result<Option<String>, ReadmeError> {
let path = match get_readme_path(project_dir, readme).await? {
Some(path) => path,
None => return Ok(None),
};
) -> Result<Option<String>, ReadMeError> {
let project_dir = project_dir.as_ref();

match path
.extension()
.map(|ext| ext.to_string_lossy().to_lowercase())
.as_deref()
if let Some(ReadMe::Table {
text: Some(text), ..
}) = readme
{
Some("md") | Some("txt") | None => {}
_ => return Err(ReadmeError::ContentTypeNotSupported),
return Ok(Some(text.clone()));
}

if !tokio::fs::try_exists(&path).await? {
return Err(ReadmeError::NotFound);
let path = find_readme_path(project_dir, readme).await?;

if let Some(path) = path {
if !fs::try_exists(&path).await? {
return Err(ReadMeError::NotFound);
}
let content = fs::read_to_string(path).await?;
Ok(Some(content))
} else {
Ok(None)
}
}

Ok(Some(tokio::fs::read_to_string(path).await?))
pub async fn write_readme(project_dir: impl AsRef<Path>, content: &str) -> Result<(), ReadMeError> {
let project_dir = project_dir.as_ref();
let existing_readme_path = find_readme_path(project_dir, None).await?;
if let Some(existing_path) = existing_readme_path {
fs::write(existing_path, content).await?;
} else {
return Err(ReadMeError::NotFound);
}
Ok(())
}

0 comments on commit c011131

Please sign in to comment.