///|
fn first_value(
matches : @argparse.Matches,
key : String,
fallback : String,
) -> String {
match matches.values.get(key) {
Some(values) => if values.length() > 0 { values[0] } else { fallback }
None => fallback
}
}
///|
fn flag_enabled(matches : @argparse.Matches, key : String) -> Bool {
match matches.flags.get(key) {
Some(value) => value
None => false
}
}
///|
fn choose_value(
cli_value : String,
config_value : String,
fallback : String,
) -> String {
let cli = cli_value.trim().to_owned()
if cli != "" {
cli
} else {
let config = config_value.trim().to_owned()
if config != "" {
config
} else {
fallback
}
}
}
///|
fn config_value(config_file : String, key : String) -> String {
let value = read_config_value(config_file, key)
if value.has_prefix("__MOONDOC_CONFIG_ERROR__") {
fail("cannot read config file: " + value[24:].to_owned())
return ""
}
value
}
///|
fn choose_theme(name : String) -> @lib.SiteTheme {
match name.trim().to_owned() {
"" | "default" => @lib.default_theme()
"package" => @lib.package_theme()
"api" | "api-reference" => @lib.api_reference_theme()
other => {
fail("unknown theme preset: \{other}; expected default, package, or api")
@lib.default_theme()
}
}
}
///|
fn stem(filename : String) -> String {
if filename.has_suffix(".md") {
filename[0:filename.length() - 3].to_owned()
} else {
filename
}
}
///|
fn split_path(path : String) -> (String, String) {
let mut last = -1
let mut i = 0
for ch in path {
if ch == '/' || ch == '\\' {
last = i
}
i = i + 1
}
let dir = StringBuilder()
let name = StringBuilder()
let mut j = 0
for ch in path {
if j < last {
dir.write_char(ch)
} else if j > last {
name.write_char(ch)
}
j = j + 1
}
if dir.to_string() == "" {
(".", name.to_string())
} else {
(dir.to_string(), name.to_string())
}
}
///|
fn load_pages(directory : String, api_file : String) -> Array[@lib.DocPage] {
let raw = read_markdown_directory(directory)
let pages : Array[@lib.DocPage] = []
let mut i = 0
while i + 1 < raw.length() {
let name = raw[i]
let page_stem = stem(name)
pages.push({ title: page_stem, slug: page_stem, source: raw[i + 1], })
i = i + 2
}
if api_file.trim().to_owned() != "" {
pages.push(@lib.mbti_to_page(read_text_file(api_file)))
}
pages
}
///|
fn write_manifest(directory : String, files : Array[@lib.OutputFile]) -> Int {
let paths : Array[String] = []
let contents : Array[String] = []
for file in files {
paths.push(file.path)
contents.push(file.content)
}
write_output_directory(directory, paths, contents)
}
///|
fn fail(message : String) -> Unit {
println("MoonDocKit input error: \{message}")
println(
"hint: run with --config examples/moondockit.json or pass --source and --output explicitly",
)
exit_failure(1)
}
///|
fn main {
let command = @argparse.Command(
"moondockit",
about="Build a static documentation site from a Markdown directory.",
version="0.1.0",
flags=[
@argparse.FlagArg(
"dry-run",
about="Validate and report generated outputs without writing files.",
),
@argparse.FlagArg(
"strict",
about="Fail when validation warnings are present.",
),
@argparse.FlagArg(
"ai-doc",
about="Generate AI-enhanced API documentation from .mbti file.",
),
@argparse.FlagArg(
"ai-quality",
about="Run AI quality assessment on all pages.",
),
@argparse.FlagArg(
"ai-tests",
about="Generate AI test stubs from .mbti file.",
),
@argparse.FlagArg(
"ai-summary",
about="Generate AI content summaries for all pages.",
),
@argparse.FlagArg(
"ai-xref",
about="Generate AI cross-reference map between pages.",
),
@argparse.FlagArg(
"ai-coverage",
about="Analyze API documentation coverage gaps.",
),
@argparse.FlagArg(
"ai-consistency",
about="Check documentation style and content consistency.",
),
@argparse.FlagArg(
"ai-seo",
about="Generate SEO metadata and search optimization report.",
),
@argparse.FlagArg(
"ai-recommend",
about="Generate content recommendations and learning paths.",
),
],
options=[
@argparse.OptionArg("config", about="Optional JSON configuration file.", default_values=[
"",
]),
@argparse.OptionArg(
"source",
short='s',
about="Directory containing Markdown files.",
default_values=[""],
),
@argparse.OptionArg("output", short='o', about="Destination directory.", default_values=[
"",
]),
@argparse.OptionArg(
"title",
about="Generated documentation site title.",
default_values=[""],
),
@argparse.OptionArg(
"site-url",
about="Optional public base URL for canonical metadata.",
default_values=[""],
),
@argparse.OptionArg(
"api",
about="Optional generated .mbti file to include as API documentation.",
default_values=[""],
),
@argparse.OptionArg(
"language",
about="HTML language code for generated pages.",
default_values=[""],
),
@argparse.OptionArg(
"description",
about="Site description used in generated metadata.",
default_values=[""],
),
@argparse.OptionArg(
"footer",
about="Markdown-style inline footer content.",
default_values=[""],
),
@argparse.OptionArg(
"theme",
about="Theme preset: default, package, or api.",
default_values=[""],
),
@argparse.OptionArg(
"doctest-output",
about="Write runnable MoonBit examples to a test source file.",
default_values=[""],
),
@argparse.OptionArg(
"doctest-package",
about="Package to import in the generated doc test file.",
default_values=[""],
),
],
)
let matches = command.parse(argv=cli_arguments()) catch {
error => {
println("\{error}")
exit_failure(2)
return
}
}
let config_file = first_value(matches, "config", "")
if config_file.trim().to_owned() != "" && !is_file(config_file) {
fail("config file not found: \{config_file}")
return
}
let source = choose_value(
first_value(matches, "source", ""),
config_value(config_file, "source"),
"",
)
let output = choose_value(
first_value(matches, "output", ""),
config_value(config_file, "output"),
"dist",
)
let title = choose_value(
first_value(matches, "title", ""),
config_value(config_file, "title"),
"MoonDocKit Site",
)
let site_url = choose_value(
first_value(matches, "site-url", ""),
config_value(config_file, "site_url"),
"",
)
let api_file = choose_value(
first_value(matches, "api", ""),
config_value(config_file, "api"),
"",
)
let language = choose_value(
first_value(matches, "language", ""),
config_value(config_file, "language"),
"en",
)
let description = choose_value(
first_value(matches, "description", ""),
config_value(config_file, "description"),
"",
)
let footer = choose_value(
first_value(matches, "footer", ""),
config_value(config_file, "footer"),
"Generated by `MoonDocKit`",
)
let theme_name = choose_value(
first_value(matches, "theme", ""),
config_value(config_file, "theme"),
"default",
)
let doctest_output = choose_value(
first_value(matches, "doctest-output", ""),
config_value(config_file, "doctest_output"),
"",
)
let doctest_package = choose_value(
first_value(matches, "doctest-package", ""),
config_value(config_file, "doctest_package"),
"",
)
let dry_run = flag_enabled(matches, "dry-run")
let strict = flag_enabled(matches, "strict")
let ai_doc_enabled = flag_enabled(matches, "ai-doc")
let ai_quality_enabled = flag_enabled(matches, "ai-quality")
let ai_tests_enabled = flag_enabled(matches, "ai-tests")
let ai_summary_enabled = flag_enabled(matches, "ai-summary")
let ai_xref_enabled = flag_enabled(matches, "ai-xref")
let ai_coverage_enabled = flag_enabled(matches, "ai-coverage")
let ai_consistency_enabled = flag_enabled(matches, "ai-consistency")
let ai_seo_enabled = flag_enabled(matches, "ai-seo")
let ai_recommend_enabled = flag_enabled(matches, "ai-recommend")
if source.trim().to_owned() == "" {
fail("source directory is required; pass --source or set source in config")
return
}
if !path_exists(source) {
fail("source directory not found: \{source}")
return
}
if !is_directory(source) {
fail("source path is not a directory: \{source}")
return
}
if api_file.trim().to_owned() != "" && !is_file(api_file) {
fail("API interface file not found: \{api_file}")
return
}
if path_exists(output) && !is_directory(output) {
fail("output path exists and is not a directory: \{output}")
return
}
let pages = load_pages(source, api_file)
if ai_doc_enabled && api_file.trim().to_owned() != "" {
let api_ref = @lib.parse_mbti(read_text_file(api_file))
let ai_config = @ai.default_ai_config()
let module_doc = @ai.generate_module_overview(ai_config, api_ref)
pages.push({
title: "AI Overview",
slug: "ai-overview",
source: @ai.module_overview_to_markdown(module_doc),
})
pages.push({
title: "AI API Reference",
slug: "ai-api-reference",
source: @ai.function_docs_to_markdown(module_doc.function_docs),
})
}
let site : @lib.DocSite = { title, pages, }
if doctest_output != "" {
let examples : Array[@lib.DocExample] = []
for page in pages {
for example in @lib.extract_doc_examples(page.source) {
examples.push({ index: examples.length(), code: example.code, })
}
}
let test_source = @lib.render_doc_test_file(examples, doctest_package)
let target = split_path(doctest_output)
let _ = write_output_directory(target.0, [target.1], [test_source])
println("doc tests: \{examples.length()} example(s) -> \{doctest_output}")
}
let validation = @lib.validate_site(site)
let diagnostics = @lib.diagnostics_to_text(validation)
let quality = @lib.evaluate_quality(site)
if !quality.passed {
println("MoonDocKit quality gate failed: \{quality.score}")
println(diagnostics)
println("hint: fix validation errors before publishing documentation")
exit_failure(1)
return
}
if strict && diagnostics != "ok" {
println("MoonDocKit strict validation failed")
println(diagnostics)
println("hint: remove warnings or run without --strict for local previews")
exit_failure(1)
return
}
let options : @lib.SiteOptions = {
language,
description: if description.trim().to_owned() == "" {
title + " documentation"
} else {
description
},
site_url,
footer,
}
let manifest = @lib.build_site_manifest_with_options(
site,
choose_theme(theme_name),
options,
)
if ai_quality_enabled {
let ai_config = @ai.default_ai_config()
let ai_options = @ai.default_quality_options()
let results = @ai.assess_site_quality(ai_config, site, ai_options)
let quality_report = StringBuilder()
quality_report.write_string("# AI Quality Report\n\n")
let mut idx = 0
while idx < results.length() {
let pair = results[idx]
let page_title = pair.0
let assessment = pair.1
quality_report.write_string("## ")
quality_report.write_string(page_title)
quality_report.write_string("\n\n")
quality_report.write_string(@ai.quality_to_markdown(assessment))
quality_report.write_string("\n\n")
idx = idx + 1
}
manifest.push({
path: "ai-quality-report.md",
content: quality_report.to_string(),
})
}
if ai_tests_enabled && api_file.trim().to_owned() != "" {
let api_ref = @lib.parse_mbti(read_text_file(api_file))
let ai_config = @ai.default_ai_config()
let suite = @ai.generate_symbol_tests(ai_config, api_ref)
let test_code = @ai.tests_to_moonbit(suite)
manifest.push({ path: "ai-generated-test.mbt", content: test_code, })
}
if ai_summary_enabled {
let summaries = @ai.summarize_site(site)
let summary_md = @ai.site_summary_to_markdown(title, summaries)
manifest.push({ path: "ai-site-summary.md", content: summary_md, })
}
if ai_quality_enabled {
let ai_config = @ai.default_ai_config()
let ai_options = @ai.default_quality_options()
let assessments = @ai.assess_site_quality(ai_config, site, ai_options)
let site_report = @ai.aggregate_site_quality(assessments)
let site_report_md = @ai.site_quality_to_markdown(site_report)
manifest.push({
path: "ai-site-quality-report.md",
content: site_report_md,
})
}
if ai_xref_enabled {
let xref_report = @ai.detect_cross_references(site)
let xref_md = @ai.xref_to_markdown(xref_report)
manifest.push({ path: "ai-cross-reference.md", content: xref_md, })
}
if ai_coverage_enabled && api_file.trim().to_owned() != "" {
let api_ref = @lib.parse_mbti(read_text_file(api_file))
let coverage = @ai.analyze_coverage(api_ref, site)
let coverage_md = @ai.coverage_to_markdown(coverage)
manifest.push({ path: "ai-coverage-report.md", content: coverage_md, })
}
if ai_consistency_enabled {
let consistency = @ai.check_consistency(site)
let consistency_md = @ai.consistency_to_markdown(consistency)
manifest.push({ path: "ai-consistency-report.md", content: consistency_md, })
}
if ai_seo_enabled {
let seo = @ai.generate_site_seo(site)
let seo_md = @ai.seo_to_markdown(seo)
manifest.push({ path: "ai-seo-report.md", content: seo_md, })
}
if ai_recommend_enabled {
let recs = @ai.generate_recommendations(site)
let recs_md = @ai.recommendations_to_markdown(recs)
manifest.push({ path: "ai-recommendations.md", content: recs_md, })
}
let report = @lib.inspect_manifest(manifest)
let written = if dry_run {
manifest.length()
} else {
write_manifest(output, manifest)
}
println("MoonDocKit build complete")
println("source: \{source}")
println("output: \{output}")
println("quality: \{quality.score}")
if dry_run {
println("dry-run: true")
}
if ai_doc_enabled {
println("ai-doc: enabled")
}
if ai_quality_enabled {
println("ai-quality: enabled")
}
if ai_tests_enabled {
println("ai-tests: enabled")
}
if ai_summary_enabled {
println("ai-summary: enabled")
}
if ai_xref_enabled {
println("ai-xref: enabled")
}
if ai_coverage_enabled {
println("ai-coverage: enabled")
}
if ai_consistency_enabled {
println("ai-consistency: enabled")
}
if ai_seo_enabled {
println("ai-seo: enabled")
}
if ai_recommend_enabled {
println("ai-recommend: enabled")
}
if diagnostics != "ok" {
println("diagnostics:")
println(diagnostics)
}
println("files: \{written}, bytes: \{report.total_bytes}")
}