// // 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))

" } 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 += "" } html += "
\(escape(option.name))\(escape(option.meaning))
" } if !doc.examples.isEmpty { html += "
"
                + doc.examples.map { ". " + escape($0) }.joined(separator: "\n")
                + "
" } if let notes = doc.notes { html += "

\(escape(notes))

" } html += "
" return html } }