//
// DocsCommand.swift
// Metrika
//
// Author: Simon-Pierre Boucher
// Contact: contact@spboucher.ai
// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.
//
import ArgumentParser
import Foundation
import ZQEngine
/// `metrika-cli docs --output docs/` — renders the shared command
/// reference (the same registry behind the app's Manual pane and the
/// console `help`) into a self-contained static site.
struct DocsCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "docs",
abstract: "Generate the static documentation site from the command reference."
)
@Option(name: .shortAndLong, help: "Output directory.")
var output: String = "docs"
func run() throws {
let directory = URL(fileURLWithPath: output, isDirectory: true)
try FileManager.default.createDirectory(
at: directory, withIntermediateDirectories: true
)
let html = Self.render()
try Data(html.utf8).write(to: directory.appendingPathComponent("index.html"))
print("wrote \(output)/index.html (\(ZQCommandReference.all.count) commands)")
}
static func escape(_ text: String) -> String {
text.replacingOccurrences(of: "&", with: "&")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: ">", with: ">")
}
static func render() -> String {
var nav = ""
var body = ""
for category in ZQCommandReference.categories {
let docs = ZQCommandReference.all.filter { $0.category == category }
guard !docs.isEmpty else { continue }
nav += "
\(escape(category))
"
body += "\(escape(category))
\n"
for doc in docs {
nav += "\(doc.verb) "
body += renderCommand(doc)
}
nav += "
"
}
return """
Metrika — Command Reference
Metrika
Command reference — Stata-class syntax, GPU-accelerated by Apple Silicon.
Every command follows one grammar:
command [varlist] [if] [in] [, options]
\(body)
"""
}
static func renderCommand(_ doc: ZQCommandDoc) -> String {
var html = ""
html += "\(escape(doc.verb))"
if let abbreviation = doc.abbreviation {
html += "abbreviation: \(escape(abbreviation))"
}
html += "
"
html += "\(escape(doc.summary))
"
html += "\(escape(doc.syntax))
"
if !doc.options.isEmpty {
html += ""
for option in doc.options {
html += "| \(escape(option.name)) | \(escape(option.meaning)) |
"
}
html += "
"
}
if !doc.examples.isEmpty {
html += ""
+ doc.examples.map { ". " + escape($0) }.joined(separator: "\n")
+ ""
}
if let notes = doc.notes {
html += "\(escape(notes))
"
}
html += ""
return html
}
}