Изучение Топаза

Первое приложение

Превратите планировщик в зафиксированный и протестированный пакет из двух модулей, затем запустите продукт Python без исходников и компилятора Топаза.

Результат: вы соберёте зафиксированный пакет из двух модулей, запустите выбранный тест и точку входа, а затем выполните продукт Python без проекта .tpz и компилятора Топаза.

Что нужно знать: завершите Ошибки и ресурсы, установите topaz и Python 3.11 или новее.

Начните с обычной заготовки

В каталоге, где должен появиться проект, выполните:

BASH
topaz init --root study-plan
cd study-plan

init отказывается перезаписывать существующий проект. Команда создаёт topaz.toml и src/main.tpz, но не файл блокировки и не тест. Замените исходник точки входа и добавьте модуль и выбранный тест, чтобы получить такую структуру:

study-plan/
├── topaz.toml
├── src/
│   ├── main.tpz
│   └── plan.tpz
└── tests/
    └── plan.tpz

Манифест заготовки:

TOML
[package]
name = "study-plan"
version = "0.1.0"
language = "5.10"
entry = "src/main.tpz"

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.10"

Создайте два модуля

В src/plan.tpz находятся предметная модель и переиспользуемые операции:

TOPAZ
export record StudyTask {
    title: string,
    minutes: int,
    done: bool = false,
}

function parseMinutes(text: string) -> Result<int, string> {
    match toInt(text) {
        case Some(value) if value > 0 => Ok(value)
        case _ => Err("minutes must be a positive integer")
    }
}

export function makeTask(
    title: string,
    minuteText: string,
    done: bool = false,
) -> Result<StudyTask, string> {
    let minutes = parseMinutes(minuteText)?
    Ok(StudyTask { title: title, minutes: minutes, done: done })
}

export function summarize(tasks: Array<StudyTask>) -> string {
    let mut remaining = 0
    for task in tasks {
        if !task.done {
            remaining = remaining + task.minutes
        }
    }
    "tasks={tasks.length}, remaining={remaining} minutes"
}

Другому модулю видны только имена с export. parseMinutes остаётся внутренней деталью.

src/main.tpz импортирует видимые имена и задаёт точку входа пакета:

TOPAZ
import src.plan { StudyTask, makeTask, summarize }

export function main(args: Array<string>, stdin: string) -> Result<int, string> {
    let tasks: Array<StudyTask> = [
        makeTask("Run first program", "10", true)?,
        makeTask("Build application", "25", false)?,
    ]
    print(summarize(tasks))
    Ok(0)
}

Явная функция main получает аргументы командной строки и стандартный ввод, а возвращает код завершения или строку ошибки. Приложение не использует входные данные, но сигнатура делает границу продукта видимой.

Добавьте tests/plan.tpz:

TOPAZ
import src.plan { StudyTask, makeTask, summarize }

match makeTask("Read syntax map", "15") {
    case Ok(task) => assert(task.minutes == 15, "valid minutes")
    case Err(message) => assert(false, message)
}

match makeTask("Read syntax map", "later") {
    case Ok(_) => assert(false, "invalid minutes were accepted")
    case Err(message) => assert(
        message == "minutes must be a positive integer",
        "invalid minutes",
    )
}

let tasks: Array<StudyTask> = [
    StudyTask { title: "Run first program", minutes: 10, done: true },
    StudyTask { title: "Build application", minutes: 25 },
]
assert(
    summarize(tasks) == "tasks=2, remaining=25 minutes",
    "summary",
)

Изолированный TestHost предоставляет каноническую тестовую функцию assert(...). Файл проверяет корректный ввод, неверный ввод и итоговую сводку, не меняя производственный код.

Зафиксируйте, отформатируйте, проверьте, протестируйте и запустите

Создайте блокировку зависимостей и изучите её:

BASH
topaz lock --root .

Полученный topaz.lock:

TOML
[[package]]
name = "study-plan"
version = "0.1.0"
source = "root"
manifest_hash = "sha256:780ff549a9fb9b09d27c62df8ce48e70c1fc7b33d6d429e6bb826685f42e7615"

Теперь по порядку выполните локальный цикл качества:

BASH
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --locked

fmt --check сообщает об отклонениях формата, не переписывая файлы; check проверяет разрешённый юнит; явный путь выбирает одну точку входа теста. Результат:

Вывод
tests/plan.tpz: test-ok

Запустите точку входа пакета:

BASH
topaz run --root . --locked

Она печатает:

Вывод
tasks=2, remaining=25 minutes

Соберите и запустите без проекта Топаза

По умолчанию заготовка выбирает цель native, но явная команда может выбрать другую. Соберите продукт Python из курса:

BASH
topaz build --target python --root . --locked --out-dir ../study-plan-product

Python выбран намеренно: во время выполнения нужен Python 3.11 или новее, зато начинающему не приходится устанавливать Rust. В управляемом каталоге продукта находятся ровно следующие файлы:

study-plan-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.py

Уберите весь проект Топаза, а затем запустите программу из каталога продукта:

BASH
cd ..
mv study-plan source-unavailable
cd study-plan-product
python3 program.py

В PowerShell замените команду перемещения на Rename-Item study-plan source-unavailable, а для запуска используйте python program.py. Результат побайтно совпадает с выводом пакета:

Вывод
tasks=2, remaining=25 minutes

В продукте остаются сгенерированный Python и его файл поддержки. «Без исходников» здесь означает отсутствие .tpz и независимость от проекта Топаза, компилятора, реестра и рабочего каталога сборки.

Различайте шесть границ

  • Файл — один исходный файл .tpz.
  • Модуль — пространство имён, которое файл открывает через export.
  • Юнит — набор файлов Топаза, разрешённых из точки входа и её импортов.
  • Пакет хранит идентичность продукта и зафиксированные зависимости в topaz.toml и topaz.lock.
  • Цель выбирает форму продукта, например Python.
  • Артефакт — управляемый результат сборки, который можно передать и запустить.

Выбор: цикл исходников или граница продукта?

Пока вы меняете проект Топаза, применяйте check, test и run для быстрой обратной связи на уровне исходника. Когда нужен точный поставляемый результат, используйте build, а затем проверяйте артефакт вне проекта. Запуск только внутри дерева исходников доказывает работу программы, но не границу поставки.

Попробуйте

В src/main.tpz замените строку минут второй задачи с "25" на "30". Какие ожидаемые результаты нужно изменить, чтобы весь цикл прошёл?

Показать ответ

Пакет и продукт Python должны напечатать tasks=2, remaining=30 minutes. Выбранный тест создаёт собственную задачу на 25 минут, поэтому его прежнее ожидание остаётся верным. Так видно, что тест владеет своей фикстурой и не зависит скрыто от данных main.

Готовы двигаться дальше, если

Вы можете объяснить шесть границ, пересоздать и потребовать файл блокировки, выполнить fmt --check, check, выбранный test и run, а затем запустить продукт Python после удаления проекта .tpz.

Используйте Краткий обзор синтаксиса как карту, а детали изучайте в разделах Модули и видимость, Цикл приложения и Артефакты и развёртывание.