///|
/// A generated artifact that can be attached to CI or contest submission.
pub(all) struct Artifact {
name : String
kind : String
path : String
media_type : String
description : String
required : Bool
} derive(Eq, Debug)
///|
/// Create a generic artifact descriptor.
pub fn Artifact::new(
name : String,
kind? : String = "file",
path? : String = "",
media_type? : String = "text/plain",
description? : String = "",
required? : Bool = false,
) -> Artifact {
{ name, kind, path, media_type, description, required }
}
///|
/// Create a Markdown artifact descriptor.
pub fn Artifact::markdown(
name : String,
path : String,
description? : String = "",
) -> Artifact {
Artifact::new(
name,
kind="markdown",
path~,
media_type="text/markdown",
description~,
required=true,
)
}
///|
/// Create a JSON artifact descriptor.
pub fn Artifact::json(
name : String,
path : String,
description? : String = "",
) -> Artifact {
Artifact::new(
name,
kind="json",
path~,
media_type="application/json",
description~,
required=false,
)
}
///|
/// Create a CSV artifact descriptor.
pub fn Artifact::csv(
name : String,
path : String,
description? : String = "",
) -> Artifact {
Artifact::new(
name,
kind="csv",
path~,
media_type="text/csv",
description~,
required=false,
)
}
///|
/// Create a zip artifact descriptor.
pub fn Artifact::zip(
name : String,
path : String,
description? : String = "",
) -> Artifact {
Artifact::new(
name,
kind="zip",
path~,
media_type="application/zip",
description~,
required=true,
)
}
///|
/// Whether the artifact has a usable path.
pub fn Artifact::has_path(self : Artifact) -> Bool {
self.path != ""
}
///|
/// Render an artifact as one Markdown table row.
pub fn Artifact::to_markdown_row(self : Artifact) -> String {
"| \{escape_markdown(self.name)} | \{self.kind} | `\{escape_markdown(self.path)}` | \{self.required} | \{escape_markdown(self.description)} |\n"
}
///|
/// Render an artifact as compact JSON.
pub fn Artifact::to_json(self : Artifact) -> String {
"{" +
"\"name\":\"\{escape_json(self.name)}\"," +
"\"kind\":\"\{escape_json(self.kind)}\"," +
"\"path\":\"\{escape_json(self.path)}\"," +
"\"media_type\":\"\{escape_json(self.media_type)}\"," +
"\"description\":\"\{escape_json(self.description)}\"," +
"\"required\":\{self.required}" +
"}"
}
///|
/// A bundle of generated or expected artifacts.
pub(all) struct ArtifactBundle {
title : String
artifacts : Array[Artifact]
} derive(Eq, Debug)
///|
/// Create an empty artifact bundle.
pub fn ArtifactBundle::new(
title? : String = "MoonBench Artifacts",
) -> ArtifactBundle {
{ title, artifacts: [] }
}
///|
/// Return a new bundle with one artifact appended.
pub fn ArtifactBundle::add(
self : ArtifactBundle,
artifact : Artifact,
) -> ArtifactBundle {
let artifacts = self.artifacts.copy()
artifacts.push(artifact)
{ ..self, artifacts, }
}
///|
/// Number of artifacts.
pub fn ArtifactBundle::count(self : ArtifactBundle) -> Int {
self.artifacts.length()
}
///|
/// Number of required artifacts.
pub fn ArtifactBundle::required_count(self : ArtifactBundle) -> Int {
for artifact in self.artifacts; acc = 0 {
if artifact.required {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Number of artifacts with a path.
pub fn ArtifactBundle::with_path_count(self : ArtifactBundle) -> Int {
for artifact in self.artifacts; acc = 0 {
if artifact.has_path() {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Whether every required artifact has a path.
pub fn ArtifactBundle::required_paths_ready(self : ArtifactBundle) -> Bool {
for artifact in self.artifacts {
if artifact.required && !artifact.has_path() {
return false
}
}
true
}
///|
/// Render artifact bundle as Markdown.
pub fn ArtifactBundle::to_markdown(self : ArtifactBundle) -> String {
let mut body = "## \{escape_markdown(self.title)}\n\n"
body = body + "- Total: \{self.count()}\n"
body = body + "- Required: \{self.required_count()}\n"
body = body + "- With path: \{self.with_path_count()}\n"
body = body + "- Required paths ready: \{self.required_paths_ready()}\n\n"
body = body + "| name | kind | path | required | description |\n"
body = body + "| --- | --- | --- | --- | --- |\n"
for artifact in self.artifacts {
body = body + artifact.to_markdown_row()
}
body
}
///|
/// Render artifact bundle as compact JSON.
pub fn ArtifactBundle::to_json(self : ArtifactBundle) -> String {
let mut body = "{"
body = body + "\"title\":\"\{escape_json(self.title)}\","
body = body + "\"total\":\{self.count()},"
body = body + "\"required\":\{self.required_count()},"
body = body + "\"with_path\":\{self.with_path_count()},"
body = body + "\"required_paths_ready\":\{self.required_paths_ready()},"
body = body + "\"artifacts\":["
for i in 0.. 0 {
body = body + ","
}
body = body + self.artifacts[i].to_json()
}
body + "]}"
}
///|
/// One Markdown section with a title and body.
pub(all) struct MarkdownSection {
title : String
level : Int
body : String
} derive(Eq, Debug)
///|
/// Create a Markdown section.
pub fn MarkdownSection::new(
title : String,
body? : String = "",
level? : Int = 2,
) -> MarkdownSection {
{ title, body, level: clamp_markdown_level(level) }
}
///|
fn clamp_markdown_level(level : Int) -> Int {
if level < 1 {
1
} else if level > 6 {
6
} else {
level
}
}
///|
fn heading_prefix(level : Int) -> String {
if level <= 1 {
"#"
} else if level == 2 {
"##"
} else if level == 3 {
"###"
} else if level == 4 {
"####"
} else if level == 5 {
"#####"
} else {
"######"
}
}
///|
/// Render a Markdown section.
pub fn MarkdownSection::render(self : MarkdownSection) -> String {
heading_prefix(self.level) + " " + self.title + "\n\n" + self.body + "\n"
}
///|
/// A composable Markdown document.
pub(all) struct MarkdownDocument {
title : String
intro : String
sections : Array[MarkdownSection]
} derive(Eq, Debug)
///|
/// Create a Markdown document.
pub fn MarkdownDocument::new(
title : String,
intro? : String = "",
) -> MarkdownDocument {
{ title, intro, sections: [] }
}
///|
/// Append a section.
pub fn MarkdownDocument::add_section(
self : MarkdownDocument,
section : MarkdownSection,
) -> MarkdownDocument {
let sections = self.sections.copy()
sections.push(section)
{ ..self, sections, }
}
///|
/// Append a section from title and body.
pub fn MarkdownDocument::section(
self : MarkdownDocument,
title : String,
body : String,
level? : Int = 2,
) -> MarkdownDocument {
self.add_section(MarkdownSection::new(title, body~, level~))
}
///|
/// Number of sections.
pub fn MarkdownDocument::section_count(self : MarkdownDocument) -> Int {
self.sections.length()
}
///|
/// Render the document.
pub fn MarkdownDocument::render(self : MarkdownDocument) -> String {
let mut body = "# \{self.title}\n\n"
if self.intro != "" {
body = body + self.intro + "\n\n"
}
for section in self.sections {
body = body + section.render() + "\n"
}
body
}
///|
/// Add a benchmark suite section.
pub fn MarkdownDocument::with_suite(
self : MarkdownDocument,
suite : BenchmarkSuite,
title? : String = "Benchmark Suite",
) -> MarkdownDocument {
self.section(title, suite.to_markdown(), level=2)
}
///|
/// Add a comparison report section.
pub fn MarkdownDocument::with_comparison(
self : MarkdownDocument,
report : ComparisonReport,
title? : String = "Baseline Comparison",
) -> MarkdownDocument {
self.section(title, report.to_markdown(), level=2)
}
///|
/// Add a quality gate section.
pub fn MarkdownDocument::with_gate(
self : MarkdownDocument,
gate : GateReport,
title? : String = "Quality Gate",
) -> MarkdownDocument {
self.section(title, gate.to_markdown(), level=2)
}
///|
/// Add an artifact bundle section.
pub fn MarkdownDocument::with_artifacts(
self : MarkdownDocument,
bundle : ArtifactBundle,
title? : String = "Artifacts",
) -> MarkdownDocument {
self.section(title, bundle.to_markdown(), level=2)
}
///|
/// A checklist item for release and contest submission.
pub(all) struct ChecklistItem {
id : String
title : String
description : String
done : Bool
evidence : String
required : Bool
} derive(Eq, Debug)
///|
/// Create a checklist item.
pub fn ChecklistItem::new(
id : String,
title : String,
description? : String = "",
done? : Bool = false,
evidence? : String = "",
required? : Bool = true,
) -> ChecklistItem {
{ id, title, description, done, evidence, required }
}
///|
/// Mark a checklist item as done.
pub fn ChecklistItem::complete(
self : ChecklistItem,
evidence : String,
) -> ChecklistItem {
{ ..self, done: true, evidence }
}
///|
/// Mark a checklist item as pending.
pub fn ChecklistItem::pending(
self : ChecklistItem,
evidence? : String = "",
) -> ChecklistItem {
{ ..self, done: false, evidence }
}
///|
/// Render a checklist item as Markdown row.
pub fn ChecklistItem::to_markdown_row(self : ChecklistItem) -> String {
let mark = if self.done { "x" } else { " " }
"| [\{mark}] | \{escape_markdown(self.id)} | \{escape_markdown(self.title)} | \{self.required} | \{escape_markdown(self.evidence)} |\n"
}
///|
/// Render a checklist item as JSON.
pub fn ChecklistItem::to_json(self : ChecklistItem) -> String {
"{" +
"\"id\":\"\{escape_json(self.id)}\"," +
"\"title\":\"\{escape_json(self.title)}\"," +
"\"description\":\"\{escape_json(self.description)}\"," +
"\"done\":\{self.done}," +
"\"evidence\":\"\{escape_json(self.evidence)}\"," +
"\"required\":\{self.required}" +
"}"
}
///|
/// A checklist with completion metrics.
pub(all) struct SubmissionChecklist {
title : String
items : Array[ChecklistItem]
} derive(Eq, Debug)
///|
/// Create an empty submission checklist.
pub fn SubmissionChecklist::new(
title? : String = "MoonBench Submission Checklist",
) -> SubmissionChecklist {
{ title, items: [] }
}
///|
/// Append a checklist item.
pub fn SubmissionChecklist::add(
self : SubmissionChecklist,
item : ChecklistItem,
) -> SubmissionChecklist {
let items = self.items.copy()
items.push(item)
{ ..self, items, }
}
///|
/// Number of checklist items.
pub fn SubmissionChecklist::count(self : SubmissionChecklist) -> Int {
self.items.length()
}
///|
/// Number of required checklist items.
pub fn SubmissionChecklist::required_count(self : SubmissionChecklist) -> Int {
for item in self.items; acc = 0 {
if item.required {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Number of completed items.
pub fn SubmissionChecklist::done_count(self : SubmissionChecklist) -> Int {
for item in self.items; acc = 0 {
if item.done {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Number of incomplete required items.
pub fn SubmissionChecklist::missing_required_count(
self : SubmissionChecklist,
) -> Int {
for item in self.items; acc = 0 {
if item.required && !item.done {
continue acc + 1
} else {
continue acc
}
} nobreak {
acc
}
}
///|
/// Whether all required items are complete.
pub fn SubmissionChecklist::ready(self : SubmissionChecklist) -> Bool {
self.missing_required_count() == 0
}
///|
/// Completion ratio for all items.
pub fn SubmissionChecklist::completion_ratio(
self : SubmissionChecklist,
) -> Double {
if self.items.length() == 0 {
0.0
} else {
self.done_count().to_double() / self.items.length().to_double()
}
}
///|
/// Render checklist as Markdown.
pub fn SubmissionChecklist::to_markdown(self : SubmissionChecklist) -> String {
let mut body = "## \{escape_markdown(self.title)}\n\n"
body = body + "- Total: \{self.count()}\n"
body = body + "- Required: \{self.required_count()}\n"
body = body + "- Done: \{self.done_count()}\n"
body = body + "- Missing required: \{self.missing_required_count()}\n"
body = body + "- Ready: \{self.ready()}\n\n"
body = body + "| done | id | title | required | evidence |\n"
body = body + "| --- | --- | --- | --- | --- |\n"
for item in self.items {
body = body + item.to_markdown_row()
}
body
}
///|
/// Render checklist as compact JSON.
pub fn SubmissionChecklist::to_json(self : SubmissionChecklist) -> String {
let mut body = "{"
body = body + "\"title\":\"\{escape_json(self.title)}\","
body = body + "\"total\":\{self.count()},"
body = body + "\"required\":\{self.required_count()},"
body = body + "\"done\":\{self.done_count()},"
body = body + "\"missing_required\":\{self.missing_required_count()},"
body = body + "\"ready\":\{self.ready()},"
body = body + "\"items\":["
for i in 0.. 0 {
body = body + ","
}
body = body + self.items[i].to_json()
}
body + "]}"
}
///|
/// Standard checklist for MoonBench contest submission.
pub fn SubmissionChecklist::moonbench_standard() -> SubmissionChecklist {
SubmissionChecklist::new()
.add(
ChecklistItem::new(
"public-repo",
"公开 GitHub 仓库",
done=true,
evidence="https://github.com/Han-Wentao/moonbench",
),
)
.add(
ChecklistItem::new(
"readme",
"中文 README 与 mooncakes 文档",
done=true,
evidence="README.md, README.mbt.md",
),
)
.add(
ChecklistItem::new(
"tests",
"MoonBit 测试可运行",
done=true,
evidence="moon test",
),
)
.add(
ChecklistItem::new(
"ci",
"GitHub Actions CI",
done=true,
evidence=".github/workflows/ci.yml",
),
)
.add(
ChecklistItem::new(
"examples",
"可运行示例",
done=true,
evidence="moon run cmd/main; moon run examples/basic",
),
)
.add(
ChecklistItem::new(
"mooncakes",
"mooncakes.io 发布",
done=true,
evidence="https://mooncakes.io/docs/Han-Wentao/moonbench",
),
)
.add(
ChecklistItem::new(
"gitlink",
"GitLink 仓库同步",
done=false,
evidence="用户最终提交前确认",
),
)
.add(
ChecklistItem::new(
"final-form",
"比赛作品提交表单",
done=false,
evidence="用户本人确认提交",
),
)
}
///|
/// Standard artifact bundle for MoonBench submission.
pub fn ArtifactBundle::moonbench_standard() -> ArtifactBundle {
ArtifactBundle::new(title="MoonBench Submission Artifacts")
.add(
Artifact::markdown(
"中文 README",
"README.md",
description="项目首页说明",
),
)
.add(
Artifact::markdown(
"Mooncakes 包文档",
"README.mbt.md",
description="发布到 mooncakes.io 的包文档",
),
)
.add(
Artifact::markdown(
"开发报告",
"docs/development-report.md",
description="中文开发报告,可导出 PDF 上传",
),
)
.add(
Artifact::markdown(
"项目方案",
"docs/project-proposal.md",
description="项目定位与技术路线",
),
)
.add(
Artifact::zip(
"源码提交包",
"_build/submission/moonbench-source-submission.zip",
description="最终提交可上传的源码包",
),
)
.add(
Artifact::zip(
"mooncakes 发布包",
"_build/publish/Han-Wentao-moonbench-0.2.0.zip",
description="moon package 生成的发布包",
),
)
}