Topazdocs
Начало

Цикл приложения

Создайте установленным Topaz многомодульный пакет и получите native- и Python-продукты, работающие без исходников.

Это руководство проводит один пакет от чистой установки Topaz через создание, разработку, статические проверки и документацию к прямому автономному запуску. Текущая идентичность продукта — Topaz 5.6.5, режим языка — topaz-5.6. Патч завершает инструментальный workflow, не изменяя грамматику v5.6.

Требования

Установите Topaz и проверьте точную идентичность продукта и языка:

BASH
curl -fsSL https://topaz.ooo/install.sh | sh
topaz version --verbose

Для native-сборки нужен Rust toolchain. Для выполнения Python-продукта нужен Python 3.11 или новее. Они не требуются для написания, проверки, контроля форматирования, тестирования и документирования пакета.

Создание пакета

В пустом рабочем каталоге создайте установленным CLI окончательный корень приложения:

BASH
topaz init --root release-inventory

Повторная команда отказывается перезаписывать scaffold. Итоговый пример имеет следующую структуру:

release-inventory/
  topaz.toml
  src/main.tpz
  src/policy.tpz
  src/report.tpz
  data/releases.csv
  data/policy.toml
  out/report.txt
registry/
  report_slug/1.0.0/topaz.toml
  report_slug/1.0.0/src/lib.tpz

Перед записью файлов ниже создайте каталоги, которых нет в начальном scaffold:

BASH
mkdir -p release-inventory/src release-inventory/data release-inventory/out
mkdir -p registry/report_slug/1.0.0/src

Замените scaffold-манифест следующим описанием пакета. Файловые capabilities разрешают чтение только под data и запись только под out; обычный выход за пределы корня и выход через symbolic link отклоняются.

TOML
[package]
name = "release_inventory"
version = "0.1.0"
language = "5.6"
entry = "src/main.tpz"

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.6"
report_slug = "1.0.0"

[capabilities.fs]
read = ["data"]
write = ["out"]

Entry выбирает режимы sample и негативных проверок, читает структурированные данные и записывает отчёт:

TOPAZ
import src.report { buildReport }
import std.fs

const SAMPLE_CSV = "name,status,score\nCore API,ready,91\nDraft Tool,draft,99\nCLI Pack,ready,84"
const SAMPLE_POLICY = "[policy]\nrequired_status = \"ready\"\nminimum_score = 85"

function renderSample(csv: string) -> Result<int, string> {
  let report = buildReport(csv, SAMPLE_POLICY)?
  print(report)
  Ok(0)
}

function hasArg(args: Array<string>, expected: string) -> bool {
  match args.indexOf(expected) {
    case Some(_) => true
    case None => false
  }
}

export function main(args: Array<string>, stdin: string) -> Result<int, string> {
  if hasArg(args, "--sample") {
    return renderSample(SAMPLE_CSV)
  }
  if hasArg(args, "--bad-sample") {
    return renderSample("name,status,score\nBroken Row,ready,nope")
  }
  if hasArg(args, "--probe-denied") {
    let secret = fs.readText("secret.txt")?
    print(secret)
    return Ok(0)
  }
  if hasArg(args, "--probe-symlink") {
    let secret = fs.readText("data/escape-link")?
    print(secret)
    return Ok(0)
  }

  let inventory = fs.readText("data/releases.csv")?
  let policy = fs.readText("data/policy.toml")?
  let report = buildReport(inventory, policy)?
  fs.writeText("out/report.txt", "{report}\n")?
  print(report)
  Ok(0)
}

Файл src/policy.tpz проверяет TOML-policy:

TOPAZ
export type Policy = { requiredStatus: string, minimumScore: int }

function field(obj: JSONValue, name: string) -> Result<JSONValue, string> {
  match obj.get(name) {
    case Some(value) => Ok(value)
    case None => Err("missing policy.{name}")
  }
}

export function parsePolicy(text: string) -> Result<Policy, string> {
  let document = TOML.toJson(TOML.parse(text)?)
  let policy = field(document, "policy")?
  let requiredValue = field(policy, "required_status")?
  let minimumValue = field(policy, "minimum_score")?
  let required = requiredValue.asString() ?? ""
  let minimum = minimumValue.asInt() ?? -1
  if required.byteLength() == 0 {
    return Err("policy.required_status must not be empty")
  }
  if minimum < 0 {
    return Err("policy.minimum_score must be a non-negative int")
  }
  Ok({ requiredStatus: required, minimumScore: minimum })
}

Файл src/report.tpz объединяет локальный модуль, коллекции, CSV-вход, детерминированные ошибки и зависимость пакета:

TOPAZ
import report_slug { slug }
import src.policy { Policy, parsePolicy }

function cell(row: Map<string, string>, name: string) -> Result<string, string> {
  match row.get(name) {
    case Some(value) => Ok(value)
    case None => Err("missing CSV column {name}")
  }
}

function acceptedLine(row: Map<string, string>, policy: Policy) -> Result<Option<string>, string> {
  let name = cell(row, "name")?
  let status = cell(row, "status")?
  let scoreText = cell(row, "score")?
  let score = match toInt(scoreText) {
    case Some(value) => value
    case None => return Err("invalid score `{scoreText}` for `{name}`")
  }
  if status != policy.requiredStatus || score < policy.minimumScore {
    return Ok(None)
  }
  Ok(Some("{slug(name)?}:{score}"))
}

export function buildReport(csvText: string, policyText: string) -> Result<string, string> {
  let policy = parsePolicy(policyText)?
  let rows = CSV.parseWithHeader(csvText)?
  let mut accepted: Array<string> = []
  for row in rows {
    match acceptedLine(row, policy)? {
      case Some(line) => accepted.push(line)
      case None => ()
    }
  }
  let lines = accepted.sorted()
  Ok("accepted={lines.length}\n{lines.join("\n")}")
}

Создайте manifest локальной registry-зависимости и её src/lib.tpz:

TOML
[package]
name = "report_slug"
version = "1.0.0"
language = "5.6"
entry = "src/lib.tpz"

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.6"

[exports]
module = "src/lib.tpz"
TOPAZ
export function slug(name: string) -> Result<string, string> {
  let spaces = Regex.compile(" +")?
  Ok(spaces.replaceAll(name.trim(), "-"))
}

Наконец, запишите runtime-входы и создайте каталог вывода:

# data/releases.csv
name,status,score
Core API,ready,91
CLI Pack,ready,84
Python Host,ready,88
Draft Tool,draft,99

# data/policy.toml
[policy]
required_status = "ready"
minimum_score = 85

Lock, vendoring и проверки разработки

Скопируйте зависимость в vendor, затем удалите registry. Последующие package-команды используют только lockfile и vendored bytes.

BASH
topaz vendor --root release-inventory --from registry
rm -rf registry
topaz check --root release-inventory --locked
topaz fmt --check --root release-inventory
topaz test --root release-inventory --locked -- --sample
topaz doc --root release-inventory --locked --out-dir docs-out

fmt --check вызывает тот же formatter, что и fmt, но ничего не записывает. Редактор может запустить topaz lsp --root release-inventory: сохранённый пакет разрешает vendored-модуль, а некорректное несохранённое изменение диагностируется из overlay без изменения файла на диске.

Запустите проверенный пакет и детерминированный случай некорректных данных:

BASH
topaz run --root release-inventory --locked
topaz run --root release-inventory --locked -- --bad-sample

Успешный отчёт выглядит так:

accepted=2
Core-API:91
Python-Host:88

Сборка двух продуктов

Native- и Python-продукты собираются разными командами:

BASH
topaz build --release --root release-inventory --locked --out-dir native-out
topaz build --target python --root release-inventory --locked --out-dir python-out

Native runtime-файл — native-out/target/release/program (program.exe в Windows). Python runtime-файлы — python-out/program.py и python-out/topaz_py_rt.py. Каждая сборка также создаёт topaz-artifact.json с toolchain 5.6.5, режимом topaz-5.6, target, runtime-требованиями и хешами managed files.

Скопируйте в отдельные runtime-каталоги только эти файлы, data/ и пустой доступный для записи out/. Затем удалите исходники приложения, registry, сгенерированную документацию и оба каталога сборки:

BASH
mkdir -p native-runtime/data native-runtime/out
mkdir -p python-runtime/data python-runtime/out
cp native-out/target/release/program native-runtime/program
cp python-out/program.py python-out/topaz_py_rt.py python-runtime/
cp release-inventory/data/* native-runtime/data/
cp release-inventory/data/* python-runtime/data/
rm -rf release-inventory registry docs-out native-out python-out

При отключённых сети и proxy запустите каждый продукт из его runtime-каталога, чтобы объявленные относительные корни data/ и out/ оставались действительными:

BASH
(cd native-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true ./program)
(cd python-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true python3 program.py)

Оба продукта создают одинаковый отчёт и выходной файл без checkout Topaz, исходников приложения, registry, дерева сборки Cargo и сети. Native-продукту нужны только executable и объявленные runtime-данные; Python-продукту также нужны topaz_py_rt.py и Python 3.11+.

Связанные документы