Using oops-libs in a tool
Paths: oops-paths
Every tool resolves the same collection directory, OOPS,
in two roots:
| Root | Windows | macOS | Linux | Holds |
|---|---|---|---|---|
| data | %APPDATA%\OOPS |
~/Library/Application Support/OOPS |
~/.local/share/OOPS |
config, saves, established names |
| cache | %LOCALAPPDATA%\OOPS |
~/Library/Caches/OOPS |
~/.cache/OOPS |
logs, downloads, rebuildable material |
<data root>/
├── orbistoun.toml one config file per tool, named after it
└── titles/<id>/ per-title data shared between tools
<cache root>/
├── cache/ regenerable; deleting it is always safe
└── logs/
use oops_paths::Paths;
let paths = Paths::resolve("orbistoun");
paths.ensure_dirs()?;
let logs = paths.logs_dir();
let config = paths.config_file(); // <data root>/orbistoun.toml
let title = paths.title_dir("CUSA00001"); // <data root>/titles/CUSA00001Paths::resolve always answers. A tool that must not
write to the working directory when the machine offers nowhere else uses
Paths::resolve_with_options(app, Options::new().refusing()),
which returns None instead.
Overrides, highest first:
- Portable mode:
<APP>_PORTABLEorOOPS_PORTABLEset to1/true/yes/on, a.portabledirectory beside the executable, orportablein the executable's name. Both roots become<binary dir>/.portable. <APP>_DATA_DIRorOOPS_DATA_DIR: both roots become that directory.- The layout: the platform directories above, or
$HOME/.config/OOPSwithLayout::Home.
Only locations every tool needs are named. A file one tool owns, such as the hardware registry, is that tool's to name.
Logging: oops-log
let _guard = oops_log::Logging::new("pros")
.build(oops_build::line!())
.root(paths.data_root())
.init();
tracing::info!("registered");Hold the guard for the life of the program. Log with
tracing's macros.
Set the level with OOPS_LOG, or RUST_LOG,
in EnvFilter syntax:
$env:OOPS_LOG = "debug"
$env:OOPS_LOG = "info,pros_link=trace,orbistoun_hle=debug"What belongs at each level is in CONVENTIONS
section 6. The file feature adds
Logging::to_file(paths.logs_dir()); the otlp
feature adds Logging::to_otlp(endpoint).
Build stamp:
oops-build
build.rs:
fn main() {
oops_build::emit();
}The binary:
println!("{}", oops_build::line!());
// v0.1.0 - abc1234
// v0.1.0 - abc1234-dirty built from a modified tree
// v0.1.0 - built 2026-08-29 14:03 UTC no commit, e.g. outside a repositoryoops_build::line!() is a &'static str
and fits #[command(version = ...)].
stamp!().is_exact() says whether the build names a commit
someone else can check out.
Embedded documentation:
oops-docs
const DOCS: &[oops_docs::Doc] = &[oops_docs::Doc::new(
"running", "Running a title", "Loading, and what happens after",
include_str!("../../docs/features/running.md"),
)];
#[test]
fn the_registry_is_sound() {
assert_eq!(oops_docs::check(DOCS), Vec::<String>::new());
}Keep one DocsWindow, call open() from a
menu and show(ctx, DOCS) every frame. Register pages
written for users.
Fetch fallback policy:
oops-fetch
Pure fallback policy for tools fetching assets from multiple origins (such as local builds and remote mirrors):
use oops_fetch::{walk, Origin};
let origins = [
"https://mirror1.example.com/asset.bin",
"../sibling/dist/asset.bin",
"https://mirror2.example.com/asset.bin",
];
let attempt = walk(
&origins,
true, // has_digest
|origin| fetch_bytes(origin),
|bytes| check_sha256(bytes),
)?;
println!("Retrieved from {} (local: {})", attempt.used, attempt.is_local);- Local candidates are always attempted before remote mirrors.
- Remote-only sources with no checkable digest are refused before downloading bytes.
- When a digest is present, fetched data is verified before acceptance.
- Every failed candidate and its error reason is reported if all fail.